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 27KB

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