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

7 years ago
10 years ago
9 years ago
9 years ago
9 years ago
10 years ago
9 years ago
9 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
9 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
7 years ago
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