Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

juce_GlyphArrangement.cpp 26KB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. PositionedGlyph::PositionedGlyph() noexcept
  18. : character (0), glyph (0), x (0), y (0), w (0), whitespace (false)
  19. {
  20. }
  21. PositionedGlyph::PositionedGlyph (const Font& font_, const juce_wchar character_, const int glyph_,
  22. const float x_, const float y_, const float w_, const bool whitespace_)
  23. : font (font_), character (character_), glyph (glyph_),
  24. x (x_), y (y_), w (w_), whitespace (whitespace_)
  25. {
  26. }
  27. PositionedGlyph::PositionedGlyph (const PositionedGlyph& other)
  28. : font (other.font), character (other.character), glyph (other.glyph),
  29. x (other.x), y (other.y), w (other.w), whitespace (other.whitespace)
  30. {
  31. }
  32. #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
  33. PositionedGlyph::PositionedGlyph (PositionedGlyph&& other) noexcept
  34. : font (static_cast<Font&&> (other.font)),
  35. character (other.character), glyph (other.glyph),
  36. x (other.x), y (other.y), w (other.w), whitespace (other.whitespace)
  37. {
  38. }
  39. PositionedGlyph& PositionedGlyph::operator= (PositionedGlyph&& other) noexcept
  40. {
  41. font = static_cast<Font&&> (other.font);
  42. character = other.character;
  43. glyph = other.glyph;
  44. x = other.x;
  45. y = other.y;
  46. w = other.w;
  47. whitespace = other.whitespace;
  48. return *this;
  49. }
  50. #endif
  51. PositionedGlyph::~PositionedGlyph() {}
  52. PositionedGlyph& PositionedGlyph::operator= (const PositionedGlyph& other)
  53. {
  54. font = other.font;
  55. character = other.character;
  56. glyph = other.glyph;
  57. x = other.x;
  58. y = other.y;
  59. w = other.w;
  60. whitespace = other.whitespace;
  61. return *this;
  62. }
  63. static inline void drawGlyphWithFont (const Graphics& g, int glyph, const Font& font, const AffineTransform& t)
  64. {
  65. LowLevelGraphicsContext& context = g.getInternalContext();
  66. context.setFont (font);
  67. context.drawGlyph (glyph, t);
  68. }
  69. void PositionedGlyph::draw (const Graphics& g) const
  70. {
  71. if (! isWhitespace())
  72. drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y));
  73. }
  74. void PositionedGlyph::draw (const Graphics& g, const AffineTransform& transform) const
  75. {
  76. if (! isWhitespace())
  77. drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y).followedBy (transform));
  78. }
  79. void PositionedGlyph::createPath (Path& path) const
  80. {
  81. if (! isWhitespace())
  82. {
  83. if (Typeface* const t = font.getTypeface())
  84. {
  85. Path p;
  86. t->getOutlineForGlyph (glyph, p);
  87. path.addPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight())
  88. .translated (x, y));
  89. }
  90. }
  91. }
  92. bool PositionedGlyph::hitTest (float px, float py) const
  93. {
  94. if (getBounds().contains (px, py) && ! isWhitespace())
  95. {
  96. if (Typeface* const t = font.getTypeface())
  97. {
  98. Path p;
  99. t->getOutlineForGlyph (glyph, p);
  100. AffineTransform::translation (-x, -y)
  101. .scaled (1.0f / (font.getHeight() * font.getHorizontalScale()), 1.0f / font.getHeight())
  102. .transformPoint (px, py);
  103. return p.contains (px, py);
  104. }
  105. }
  106. return false;
  107. }
  108. void PositionedGlyph::moveBy (const float deltaX,
  109. const float deltaY)
  110. {
  111. x += deltaX;
  112. y += deltaY;
  113. }
  114. //==============================================================================
  115. GlyphArrangement::GlyphArrangement()
  116. {
  117. glyphs.ensureStorageAllocated (128);
  118. }
  119. GlyphArrangement::GlyphArrangement (const GlyphArrangement& other)
  120. : glyphs (other.glyphs)
  121. {
  122. }
  123. GlyphArrangement& GlyphArrangement::operator= (const GlyphArrangement& other)
  124. {
  125. glyphs = other.glyphs;
  126. return *this;
  127. }
  128. GlyphArrangement::~GlyphArrangement()
  129. {
  130. }
  131. //==============================================================================
  132. void GlyphArrangement::clear()
  133. {
  134. glyphs.clear();
  135. }
  136. PositionedGlyph& GlyphArrangement::getGlyph (const int index) const noexcept
  137. {
  138. return glyphs.getReference (index);
  139. }
  140. //==============================================================================
  141. void GlyphArrangement::addGlyphArrangement (const GlyphArrangement& other)
  142. {
  143. glyphs.addArray (other.glyphs);
  144. }
  145. void GlyphArrangement::addGlyph (const PositionedGlyph& glyph)
  146. {
  147. glyphs.add (glyph);
  148. }
  149. void GlyphArrangement::removeRangeOfGlyphs (int startIndex, const int num)
  150. {
  151. glyphs.removeRange (startIndex, num < 0 ? glyphs.size() : num);
  152. }
  153. //==============================================================================
  154. void GlyphArrangement::addLineOfText (const Font& font,
  155. const String& text,
  156. const float xOffset,
  157. const float yOffset)
  158. {
  159. addCurtailedLineOfText (font, text, xOffset, yOffset, 1.0e10f, false);
  160. }
  161. void GlyphArrangement::addCurtailedLineOfText (const Font& font,
  162. const String& text,
  163. const float xOffset,
  164. const float yOffset,
  165. const float maxWidthPixels,
  166. const bool useEllipsis)
  167. {
  168. if (text.isNotEmpty())
  169. {
  170. Array<int> newGlyphs;
  171. Array<float> xOffsets;
  172. font.getGlyphPositions (text, newGlyphs, xOffsets);
  173. const int textLen = newGlyphs.size();
  174. glyphs.ensureStorageAllocated (glyphs.size() + textLen);
  175. String::CharPointerType t (text.getCharPointer());
  176. for (int i = 0; i < textLen; ++i)
  177. {
  178. const float nextX = xOffsets.getUnchecked (i + 1);
  179. if (nextX > maxWidthPixels + 1.0f)
  180. {
  181. // curtail the string if it's too wide..
  182. if (useEllipsis && textLen > 3 && glyphs.size() >= 3)
  183. insertEllipsis (font, xOffset + maxWidthPixels, 0, glyphs.size());
  184. break;
  185. }
  186. else
  187. {
  188. const float thisX = xOffsets.getUnchecked (i);
  189. const bool isWhitespace = t.isWhitespace();
  190. glyphs.add (PositionedGlyph (font, t.getAndAdvance(),
  191. newGlyphs.getUnchecked(i),
  192. xOffset + thisX, yOffset,
  193. nextX - thisX, isWhitespace));
  194. }
  195. }
  196. }
  197. }
  198. int GlyphArrangement::insertEllipsis (const Font& font, const float maxXPos,
  199. const int startIndex, int endIndex)
  200. {
  201. int numDeleted = 0;
  202. if (glyphs.size() > 0)
  203. {
  204. Array<int> dotGlyphs;
  205. Array<float> dotXs;
  206. font.getGlyphPositions ("..", dotGlyphs, dotXs);
  207. const float dx = dotXs[1];
  208. float xOffset = 0.0f, yOffset = 0.0f;
  209. while (endIndex > startIndex)
  210. {
  211. const PositionedGlyph& pg = glyphs.getReference (--endIndex);
  212. xOffset = pg.x;
  213. yOffset = pg.y;
  214. glyphs.remove (endIndex);
  215. ++numDeleted;
  216. if (xOffset + dx * 3 <= maxXPos)
  217. break;
  218. }
  219. for (int i = 3; --i >= 0;)
  220. {
  221. glyphs.insert (endIndex++, PositionedGlyph (font, '.', dotGlyphs.getFirst(),
  222. xOffset, yOffset, dx, false));
  223. --numDeleted;
  224. xOffset += dx;
  225. if (xOffset > maxXPos)
  226. break;
  227. }
  228. }
  229. return numDeleted;
  230. }
  231. void GlyphArrangement::addJustifiedText (const Font& font,
  232. const String& text,
  233. float x, float y,
  234. const float maxLineWidth,
  235. Justification horizontalLayout)
  236. {
  237. int lineStartIndex = glyphs.size();
  238. addLineOfText (font, text, x, y);
  239. const float originalY = y;
  240. while (lineStartIndex < glyphs.size())
  241. {
  242. int i = lineStartIndex;
  243. if (glyphs.getReference(i).getCharacter() != '\n'
  244. && glyphs.getReference(i).getCharacter() != '\r')
  245. ++i;
  246. const float lineMaxX = glyphs.getReference (lineStartIndex).getLeft() + maxLineWidth;
  247. int lastWordBreakIndex = -1;
  248. while (i < glyphs.size())
  249. {
  250. const PositionedGlyph& pg = glyphs.getReference (i);
  251. const juce_wchar c = pg.getCharacter();
  252. if (c == '\r' || c == '\n')
  253. {
  254. ++i;
  255. if (c == '\r' && i < glyphs.size()
  256. && glyphs.getReference(i).getCharacter() == '\n')
  257. ++i;
  258. break;
  259. }
  260. else if (pg.isWhitespace())
  261. {
  262. lastWordBreakIndex = i + 1;
  263. }
  264. else if (pg.getRight() - 0.0001f >= lineMaxX)
  265. {
  266. if (lastWordBreakIndex >= 0)
  267. i = lastWordBreakIndex;
  268. break;
  269. }
  270. ++i;
  271. }
  272. const float currentLineStartX = glyphs.getReference (lineStartIndex).getLeft();
  273. float currentLineEndX = currentLineStartX;
  274. for (int j = i; --j >= lineStartIndex;)
  275. {
  276. if (! glyphs.getReference (j).isWhitespace())
  277. {
  278. currentLineEndX = glyphs.getReference (j).getRight();
  279. break;
  280. }
  281. }
  282. float deltaX = 0.0f;
  283. if (horizontalLayout.testFlags (Justification::horizontallyJustified))
  284. spreadOutLine (lineStartIndex, i - lineStartIndex, maxLineWidth);
  285. else if (horizontalLayout.testFlags (Justification::horizontallyCentred))
  286. deltaX = (maxLineWidth - (currentLineEndX - currentLineStartX)) * 0.5f;
  287. else if (horizontalLayout.testFlags (Justification::right))
  288. deltaX = maxLineWidth - (currentLineEndX - currentLineStartX);
  289. moveRangeOfGlyphs (lineStartIndex, i - lineStartIndex,
  290. x + deltaX - currentLineStartX, y - originalY);
  291. lineStartIndex = i;
  292. y += font.getHeight();
  293. }
  294. }
  295. void GlyphArrangement::addFittedText (const Font& f,
  296. const String& text,
  297. const float x, const float y,
  298. const float width, const float height,
  299. Justification layout,
  300. int maximumLines,
  301. const float minimumHorizontalScale)
  302. {
  303. // doesn't make much sense if this is outside a sensible range of 0.5 to 1.0
  304. jassert (minimumHorizontalScale > 0 && minimumHorizontalScale <= 1.0f);
  305. if (text.containsAnyOf ("\r\n"))
  306. {
  307. addLinesWithLineBreaks (text, f, x, y, width, height, layout);
  308. }
  309. else
  310. {
  311. const int startIndex = glyphs.size();
  312. const String trimmed (text.trim());
  313. addLineOfText (f, trimmed, x, y);
  314. const int numGlyphs = glyphs.size() - startIndex;
  315. if (numGlyphs > 0)
  316. {
  317. const float lineWidth = glyphs.getReference (glyphs.size() - 1).getRight()
  318. - glyphs.getReference (startIndex).getLeft();
  319. if (lineWidth > 0)
  320. {
  321. if (lineWidth * minimumHorizontalScale < width)
  322. {
  323. if (lineWidth > width)
  324. stretchRangeOfGlyphs (startIndex, numGlyphs, width / lineWidth);
  325. justifyGlyphs (startIndex, numGlyphs, x, y, width, height, layout);
  326. }
  327. else if (maximumLines <= 1)
  328. {
  329. fitLineIntoSpace (startIndex, numGlyphs, x, y, width, height,
  330. f, layout, minimumHorizontalScale);
  331. }
  332. else
  333. {
  334. splitLines (trimmed, f, startIndex, x, y, width, height,
  335. maximumLines, lineWidth, layout, minimumHorizontalScale);
  336. }
  337. }
  338. }
  339. }
  340. }
  341. //==============================================================================
  342. void GlyphArrangement::moveRangeOfGlyphs (int startIndex, int num, const float dx, const float dy)
  343. {
  344. jassert (startIndex >= 0);
  345. if (dx != 0.0f || dy != 0.0f)
  346. {
  347. if (num < 0 || startIndex + num > glyphs.size())
  348. num = glyphs.size() - startIndex;
  349. while (--num >= 0)
  350. glyphs.getReference (startIndex++).moveBy (dx, dy);
  351. }
  352. }
  353. void GlyphArrangement::addLinesWithLineBreaks (const String& text, const Font& f,
  354. float x, float y, float width, float height, Justification layout)
  355. {
  356. GlyphArrangement ga;
  357. ga.addJustifiedText (f, text, x, y, width, layout);
  358. const Rectangle<float> bb (ga.getBoundingBox (0, -1, false));
  359. float dy = y - bb.getY();
  360. if (layout.testFlags (Justification::verticallyCentred)) dy += (height - bb.getHeight()) * 0.5f;
  361. else if (layout.testFlags (Justification::bottom)) dy += (height - bb.getHeight());
  362. ga.moveRangeOfGlyphs (0, -1, 0.0f, dy);
  363. glyphs.addArray (ga.glyphs);
  364. }
  365. int GlyphArrangement::fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font& font,
  366. Justification justification, float minimumHorizontalScale)
  367. {
  368. int numDeleted = 0;
  369. const float lineStartX = glyphs.getReference (start).getLeft();
  370. float lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX;
  371. if (lineWidth > w)
  372. {
  373. if (minimumHorizontalScale < 1.0f)
  374. {
  375. stretchRangeOfGlyphs (start, numGlyphs, jmax (minimumHorizontalScale, w / lineWidth));
  376. lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX - 0.5f;
  377. }
  378. if (lineWidth > w)
  379. {
  380. numDeleted = insertEllipsis (font, lineStartX + w, start, start + numGlyphs);
  381. numGlyphs -= numDeleted;
  382. }
  383. }
  384. justifyGlyphs (start, numGlyphs, x, y, w, h, justification);
  385. return numDeleted;
  386. }
  387. void GlyphArrangement::stretchRangeOfGlyphs (int startIndex, int num,
  388. const float horizontalScaleFactor)
  389. {
  390. jassert (startIndex >= 0);
  391. if (num < 0 || startIndex + num > glyphs.size())
  392. num = glyphs.size() - startIndex;
  393. if (num > 0)
  394. {
  395. const float xAnchor = glyphs.getReference (startIndex).getLeft();
  396. while (--num >= 0)
  397. {
  398. PositionedGlyph& pg = glyphs.getReference (startIndex++);
  399. pg.x = xAnchor + (pg.x - xAnchor) * horizontalScaleFactor;
  400. pg.font.setHorizontalScale (pg.font.getHorizontalScale() * horizontalScaleFactor);
  401. pg.w *= horizontalScaleFactor;
  402. }
  403. }
  404. }
  405. Rectangle<float> GlyphArrangement::getBoundingBox (int startIndex, int num, const bool includeWhitespace) const
  406. {
  407. jassert (startIndex >= 0);
  408. if (num < 0 || startIndex + num > glyphs.size())
  409. num = glyphs.size() - startIndex;
  410. Rectangle<float> result;
  411. while (--num >= 0)
  412. {
  413. const PositionedGlyph& pg = glyphs.getReference (startIndex++);
  414. if (includeWhitespace || ! pg.isWhitespace())
  415. result = result.getUnion (pg.getBounds());
  416. }
  417. return result;
  418. }
  419. void GlyphArrangement::justifyGlyphs (const int startIndex, const int num,
  420. const float x, const float y, const float width, const float height,
  421. Justification justification)
  422. {
  423. jassert (num >= 0 && startIndex >= 0);
  424. if (glyphs.size() > 0 && num > 0)
  425. {
  426. const Rectangle<float> bb (getBoundingBox (startIndex, num, ! justification.testFlags (Justification::horizontallyJustified
  427. | Justification::horizontallyCentred)));
  428. float deltaX = 0.0f, deltaY = 0.0f;
  429. if (justification.testFlags (Justification::horizontallyJustified)) deltaX = x - bb.getX();
  430. else if (justification.testFlags (Justification::horizontallyCentred)) deltaX = x + (width - bb.getWidth()) * 0.5f - bb.getX();
  431. else if (justification.testFlags (Justification::right)) deltaX = x + width - bb.getRight();
  432. else deltaX = x - bb.getX();
  433. if (justification.testFlags (Justification::top)) deltaY = y - bb.getY();
  434. else if (justification.testFlags (Justification::bottom)) deltaY = y + height - bb.getBottom();
  435. else deltaY = y + (height - bb.getHeight()) * 0.5f - bb.getY();
  436. moveRangeOfGlyphs (startIndex, num, deltaX, deltaY);
  437. if (justification.testFlags (Justification::horizontallyJustified))
  438. {
  439. int lineStart = 0;
  440. float baseY = glyphs.getReference (startIndex).getBaselineY();
  441. int i;
  442. for (i = 0; i < num; ++i)
  443. {
  444. const float glyphY = glyphs.getReference (startIndex + i).getBaselineY();
  445. if (glyphY != baseY)
  446. {
  447. spreadOutLine (startIndex + lineStart, i - lineStart, width);
  448. lineStart = i;
  449. baseY = glyphY;
  450. }
  451. }
  452. if (i > lineStart)
  453. spreadOutLine (startIndex + lineStart, i - lineStart, width);
  454. }
  455. }
  456. }
  457. void GlyphArrangement::spreadOutLine (const int start, const int num, const float targetWidth)
  458. {
  459. if (start + num < glyphs.size()
  460. && glyphs.getReference (start + num - 1).getCharacter() != '\r'
  461. && glyphs.getReference (start + num - 1).getCharacter() != '\n')
  462. {
  463. int numSpaces = 0;
  464. int spacesAtEnd = 0;
  465. for (int i = 0; i < num; ++i)
  466. {
  467. if (glyphs.getReference (start + i).isWhitespace())
  468. {
  469. ++spacesAtEnd;
  470. ++numSpaces;
  471. }
  472. else
  473. {
  474. spacesAtEnd = 0;
  475. }
  476. }
  477. numSpaces -= spacesAtEnd;
  478. if (numSpaces > 0)
  479. {
  480. const float startX = glyphs.getReference (start).getLeft();
  481. const float endX = glyphs.getReference (start + num - 1 - spacesAtEnd).getRight();
  482. const float extraPaddingBetweenWords
  483. = (targetWidth - (endX - startX)) / (float) numSpaces;
  484. float deltaX = 0.0f;
  485. for (int i = 0; i < num; ++i)
  486. {
  487. glyphs.getReference (start + i).moveBy (deltaX, 0.0f);
  488. if (glyphs.getReference (start + i).isWhitespace())
  489. deltaX += extraPaddingBetweenWords;
  490. }
  491. }
  492. }
  493. }
  494. void GlyphArrangement::splitLines (const String& text, Font font, int startIndex,
  495. float x, float y, float width, float height, int maximumLines,
  496. float lineWidth, Justification layout, float minimumHorizontalScale)
  497. {
  498. const int length = text.length();
  499. const int originalStartIndex = startIndex;
  500. int numLines = 1;
  501. if (length <= 12 && ! text.containsAnyOf (" -\t\r\n"))
  502. maximumLines = 1;
  503. maximumLines = jmin (maximumLines, length);
  504. while (numLines < maximumLines)
  505. {
  506. ++numLines;
  507. const float newFontHeight = height / (float) numLines;
  508. if (newFontHeight < font.getHeight())
  509. {
  510. font.setHeight (jmax (8.0f, newFontHeight));
  511. removeRangeOfGlyphs (startIndex, -1);
  512. addLineOfText (font, text, x, y);
  513. lineWidth = glyphs.getReference (glyphs.size() - 1).getRight()
  514. - glyphs.getReference (startIndex).getLeft();
  515. }
  516. // Try to estimate the point at which there are enough lines to fit the text,
  517. // allowing for unevenness in the lengths due to differently sized words.
  518. const float lineLengthUnevennessAllowance = 80.0f;
  519. if (numLines > (lineWidth + lineLengthUnevennessAllowance) / width || newFontHeight < 8.0f)
  520. break;
  521. }
  522. if (numLines < 1)
  523. numLines = 1;
  524. float lineY = y;
  525. float widthPerLine = lineWidth / numLines;
  526. for (int line = 0; line < numLines; ++line)
  527. {
  528. int i = startIndex;
  529. float lineStartX = glyphs.getReference (startIndex).getLeft();
  530. if (line == numLines - 1)
  531. {
  532. widthPerLine = width;
  533. i = glyphs.size();
  534. }
  535. else
  536. {
  537. while (i < glyphs.size())
  538. {
  539. lineWidth = (glyphs.getReference (i).getRight() - lineStartX);
  540. if (lineWidth > widthPerLine)
  541. {
  542. // got to a point where the line's too long, so skip forward to find a
  543. // good place to break it..
  544. const int searchStartIndex = i;
  545. while (i < glyphs.size())
  546. {
  547. if ((glyphs.getReference (i).getRight() - lineStartX) * minimumHorizontalScale < width)
  548. {
  549. if (glyphs.getReference (i).isWhitespace()
  550. || glyphs.getReference (i).getCharacter() == '-')
  551. {
  552. ++i;
  553. break;
  554. }
  555. }
  556. else
  557. {
  558. // can't find a suitable break, so try looking backwards..
  559. i = searchStartIndex;
  560. for (int back = 1; back < jmin (7, i - startIndex - 1); ++back)
  561. {
  562. if (glyphs.getReference (i - back).isWhitespace()
  563. || glyphs.getReference (i - back).getCharacter() == '-')
  564. {
  565. i -= back - 1;
  566. break;
  567. }
  568. }
  569. break;
  570. }
  571. ++i;
  572. }
  573. break;
  574. }
  575. ++i;
  576. }
  577. int wsStart = i;
  578. while (wsStart > 0 && glyphs.getReference (wsStart - 1).isWhitespace())
  579. --wsStart;
  580. int wsEnd = i;
  581. while (wsEnd < glyphs.size() && glyphs.getReference (wsEnd).isWhitespace())
  582. ++wsEnd;
  583. removeRangeOfGlyphs (wsStart, wsEnd - wsStart);
  584. i = jmax (wsStart, startIndex + 1);
  585. }
  586. i -= fitLineIntoSpace (startIndex, i - startIndex,
  587. x, lineY, width, font.getHeight(), font,
  588. layout.getOnlyHorizontalFlags() | Justification::verticallyCentred,
  589. minimumHorizontalScale);
  590. startIndex = i;
  591. lineY += font.getHeight();
  592. if (startIndex >= glyphs.size())
  593. break;
  594. }
  595. justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex,
  596. x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified);
  597. }
  598. //==============================================================================
  599. void GlyphArrangement::drawGlyphUnderline (const Graphics& g, const PositionedGlyph& pg,
  600. const int i, const AffineTransform& transform) const
  601. {
  602. const float lineThickness = (pg.font.getDescent()) * 0.3f;
  603. float nextX = pg.x + pg.w;
  604. if (i < glyphs.size() - 1 && glyphs.getReference (i + 1).y == pg.y)
  605. nextX = glyphs.getReference (i + 1).x;
  606. Path p;
  607. p.addRectangle (pg.x, pg.y + lineThickness * 2.0f, nextX - pg.x, lineThickness);
  608. g.fillPath (p, transform);
  609. }
  610. void GlyphArrangement::draw (const Graphics& g) const
  611. {
  612. for (int i = 0; i < glyphs.size(); ++i)
  613. {
  614. const PositionedGlyph& pg = glyphs.getReference(i);
  615. if (pg.font.isUnderlined())
  616. drawGlyphUnderline (g, pg, i, AffineTransform::identity);
  617. pg.draw (g);
  618. }
  619. }
  620. void GlyphArrangement::draw (const Graphics& g, const AffineTransform& transform) const
  621. {
  622. for (int i = 0; i < glyphs.size(); ++i)
  623. {
  624. const PositionedGlyph& pg = glyphs.getReference(i);
  625. if (pg.font.isUnderlined())
  626. drawGlyphUnderline (g, pg, i, transform);
  627. pg.draw (g, transform);
  628. }
  629. }
  630. void GlyphArrangement::createPath (Path& path) const
  631. {
  632. for (int i = 0; i < glyphs.size(); ++i)
  633. glyphs.getReference (i).createPath (path);
  634. }
  635. int GlyphArrangement::findGlyphIndexAt (const float x, const float y) const
  636. {
  637. for (int i = 0; i < glyphs.size(); ++i)
  638. if (glyphs.getReference (i).hitTest (x, y))
  639. return i;
  640. return -1;
  641. }