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

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
9 years ago
9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  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 (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 (Graphics& g) const
  70. {
  71. if (! isWhitespace())
  72. drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y));
  73. }
  74. void PositionedGlyph::draw (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. const float thisX = xOffsets.getUnchecked (i);
  187. const bool isWhitespace = t.isWhitespace();
  188. glyphs.add (PositionedGlyph (font, t.getAndAdvance(),
  189. newGlyphs.getUnchecked(i),
  190. xOffset + thisX, yOffset,
  191. nextX - thisX, isWhitespace));
  192. }
  193. }
  194. }
  195. int GlyphArrangement::insertEllipsis (const Font& font, const float maxXPos,
  196. const int startIndex, int endIndex)
  197. {
  198. int numDeleted = 0;
  199. if (glyphs.size() > 0)
  200. {
  201. Array<int> dotGlyphs;
  202. Array<float> dotXs;
  203. font.getGlyphPositions ("..", dotGlyphs, dotXs);
  204. const float dx = dotXs[1];
  205. float xOffset = 0.0f, yOffset = 0.0f;
  206. while (endIndex > startIndex)
  207. {
  208. const PositionedGlyph& pg = glyphs.getReference (--endIndex);
  209. xOffset = pg.x;
  210. yOffset = pg.y;
  211. glyphs.remove (endIndex);
  212. ++numDeleted;
  213. if (xOffset + dx * 3 <= maxXPos)
  214. break;
  215. }
  216. for (int i = 3; --i >= 0;)
  217. {
  218. glyphs.insert (endIndex++, PositionedGlyph (font, '.', dotGlyphs.getFirst(),
  219. xOffset, yOffset, dx, false));
  220. --numDeleted;
  221. xOffset += dx;
  222. if (xOffset > maxXPos)
  223. break;
  224. }
  225. }
  226. return numDeleted;
  227. }
  228. void GlyphArrangement::addJustifiedText (const Font& font,
  229. const String& text,
  230. float x, float y,
  231. const float maxLineWidth,
  232. Justification horizontalLayout)
  233. {
  234. int lineStartIndex = glyphs.size();
  235. addLineOfText (font, text, x, y);
  236. const float originalY = y;
  237. while (lineStartIndex < glyphs.size())
  238. {
  239. int i = lineStartIndex;
  240. if (glyphs.getReference(i).getCharacter() != '\n'
  241. && glyphs.getReference(i).getCharacter() != '\r')
  242. ++i;
  243. const float lineMaxX = glyphs.getReference (lineStartIndex).getLeft() + maxLineWidth;
  244. int lastWordBreakIndex = -1;
  245. while (i < glyphs.size())
  246. {
  247. const PositionedGlyph& pg = glyphs.getReference (i);
  248. const juce_wchar c = pg.getCharacter();
  249. if (c == '\r' || c == '\n')
  250. {
  251. ++i;
  252. if (c == '\r' && i < glyphs.size()
  253. && glyphs.getReference(i).getCharacter() == '\n')
  254. ++i;
  255. break;
  256. }
  257. if (pg.isWhitespace())
  258. {
  259. lastWordBreakIndex = i + 1;
  260. }
  261. else if (pg.getRight() - 0.0001f >= lineMaxX)
  262. {
  263. if (lastWordBreakIndex >= 0)
  264. i = lastWordBreakIndex;
  265. break;
  266. }
  267. ++i;
  268. }
  269. const float currentLineStartX = glyphs.getReference (lineStartIndex).getLeft();
  270. float currentLineEndX = currentLineStartX;
  271. for (int j = i; --j >= lineStartIndex;)
  272. {
  273. if (! glyphs.getReference (j).isWhitespace())
  274. {
  275. currentLineEndX = glyphs.getReference (j).getRight();
  276. break;
  277. }
  278. }
  279. float deltaX = 0.0f;
  280. if (horizontalLayout.testFlags (Justification::horizontallyJustified))
  281. spreadOutLine (lineStartIndex, i - lineStartIndex, maxLineWidth);
  282. else if (horizontalLayout.testFlags (Justification::horizontallyCentred))
  283. deltaX = (maxLineWidth - (currentLineEndX - currentLineStartX)) * 0.5f;
  284. else if (horizontalLayout.testFlags (Justification::right))
  285. deltaX = maxLineWidth - (currentLineEndX - currentLineStartX);
  286. moveRangeOfGlyphs (lineStartIndex, i - lineStartIndex,
  287. x + deltaX - currentLineStartX, y - originalY);
  288. lineStartIndex = i;
  289. y += font.getHeight();
  290. }
  291. }
  292. void GlyphArrangement::addFittedText (const Font& f,
  293. const String& text,
  294. const float x, const float y,
  295. const float width, const float height,
  296. Justification layout,
  297. int maximumLines,
  298. float minimumHorizontalScale)
  299. {
  300. if (minimumHorizontalScale == 0.0f)
  301. minimumHorizontalScale = Font::getDefaultMinimumHorizontalScaleFactor();
  302. // doesn't make much sense if this is outside a sensible range of 0.5 to 1.0
  303. jassert (minimumHorizontalScale > 0 && minimumHorizontalScale <= 1.0f);
  304. if (text.containsAnyOf ("\r\n"))
  305. {
  306. addLinesWithLineBreaks (text, f, x, y, width, height, layout);
  307. }
  308. else
  309. {
  310. const int startIndex = glyphs.size();
  311. const String trimmed (text.trim());
  312. addLineOfText (f, trimmed, x, y);
  313. const int numGlyphs = glyphs.size() - startIndex;
  314. if (numGlyphs > 0)
  315. {
  316. const float lineWidth = glyphs.getReference (glyphs.size() - 1).getRight()
  317. - glyphs.getReference (startIndex).getLeft();
  318. if (lineWidth > 0)
  319. {
  320. if (lineWidth * minimumHorizontalScale < width)
  321. {
  322. if (lineWidth > width)
  323. stretchRangeOfGlyphs (startIndex, numGlyphs, width / lineWidth);
  324. justifyGlyphs (startIndex, numGlyphs, x, y, width, height, layout);
  325. }
  326. else if (maximumLines <= 1)
  327. {
  328. fitLineIntoSpace (startIndex, numGlyphs, x, y, width, height,
  329. f, layout, minimumHorizontalScale);
  330. }
  331. else
  332. {
  333. splitLines (trimmed, f, startIndex, x, y, width, height,
  334. maximumLines, lineWidth, layout, minimumHorizontalScale);
  335. }
  336. }
  337. }
  338. }
  339. }
  340. //==============================================================================
  341. void GlyphArrangement::moveRangeOfGlyphs (int startIndex, int num, const float dx, const float dy)
  342. {
  343. jassert (startIndex >= 0);
  344. if (dx != 0.0f || dy != 0.0f)
  345. {
  346. if (num < 0 || startIndex + num > glyphs.size())
  347. num = glyphs.size() - startIndex;
  348. while (--num >= 0)
  349. glyphs.getReference (startIndex++).moveBy (dx, dy);
  350. }
  351. }
  352. void GlyphArrangement::addLinesWithLineBreaks (const String& text, const Font& f,
  353. float x, float y, float width, float height, Justification layout)
  354. {
  355. GlyphArrangement ga;
  356. ga.addJustifiedText (f, text, x, y, width, layout);
  357. const Rectangle<float> bb (ga.getBoundingBox (0, -1, false));
  358. float dy = y - bb.getY();
  359. if (layout.testFlags (Justification::verticallyCentred)) dy += (height - bb.getHeight()) * 0.5f;
  360. else if (layout.testFlags (Justification::bottom)) dy += (height - bb.getHeight());
  361. ga.moveRangeOfGlyphs (0, -1, 0.0f, dy);
  362. glyphs.addArray (ga.glyphs);
  363. }
  364. int GlyphArrangement::fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font& font,
  365. Justification justification, float minimumHorizontalScale)
  366. {
  367. int numDeleted = 0;
  368. const float lineStartX = glyphs.getReference (start).getLeft();
  369. float lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX;
  370. if (lineWidth > w)
  371. {
  372. if (minimumHorizontalScale < 1.0f)
  373. {
  374. stretchRangeOfGlyphs (start, numGlyphs, jmax (minimumHorizontalScale, w / lineWidth));
  375. lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX - 0.5f;
  376. }
  377. if (lineWidth > w)
  378. {
  379. numDeleted = insertEllipsis (font, lineStartX + w, start, start + numGlyphs);
  380. numGlyphs -= numDeleted;
  381. }
  382. }
  383. justifyGlyphs (start, numGlyphs, x, y, w, h, justification);
  384. return numDeleted;
  385. }
  386. void GlyphArrangement::stretchRangeOfGlyphs (int startIndex, int num,
  387. const float horizontalScaleFactor)
  388. {
  389. jassert (startIndex >= 0);
  390. if (num < 0 || startIndex + num > glyphs.size())
  391. num = glyphs.size() - startIndex;
  392. if (num > 0)
  393. {
  394. const float xAnchor = glyphs.getReference (startIndex).getLeft();
  395. while (--num >= 0)
  396. {
  397. PositionedGlyph& pg = glyphs.getReference (startIndex++);
  398. pg.x = xAnchor + (pg.x - xAnchor) * horizontalScaleFactor;
  399. pg.font.setHorizontalScale (pg.font.getHorizontalScale() * horizontalScaleFactor);
  400. pg.w *= horizontalScaleFactor;
  401. }
  402. }
  403. }
  404. Rectangle<float> GlyphArrangement::getBoundingBox (int startIndex, int num, const bool includeWhitespace) const
  405. {
  406. jassert (startIndex >= 0);
  407. if (num < 0 || startIndex + num > glyphs.size())
  408. num = glyphs.size() - startIndex;
  409. Rectangle<float> result;
  410. while (--num >= 0)
  411. {
  412. const PositionedGlyph& pg = glyphs.getReference (startIndex++);
  413. if (includeWhitespace || ! pg.isWhitespace())
  414. result = result.getUnion (pg.getBounds());
  415. }
  416. return result;
  417. }
  418. void GlyphArrangement::justifyGlyphs (const int startIndex, const int num,
  419. const float x, const float y, const float width, const float height,
  420. Justification justification)
  421. {
  422. jassert (num >= 0 && startIndex >= 0);
  423. if (glyphs.size() > 0 && num > 0)
  424. {
  425. const Rectangle<float> bb (getBoundingBox (startIndex, num, ! justification.testFlags (Justification::horizontallyJustified
  426. | Justification::horizontallyCentred)));
  427. float deltaX = 0.0f, deltaY = 0.0f;
  428. if (justification.testFlags (Justification::horizontallyJustified)) deltaX = x - bb.getX();
  429. else if (justification.testFlags (Justification::horizontallyCentred)) deltaX = x + (width - bb.getWidth()) * 0.5f - bb.getX();
  430. else if (justification.testFlags (Justification::right)) deltaX = x + width - bb.getRight();
  431. else deltaX = x - bb.getX();
  432. if (justification.testFlags (Justification::top)) deltaY = y - bb.getY();
  433. else if (justification.testFlags (Justification::bottom)) deltaY = y + height - bb.getBottom();
  434. else deltaY = y + (height - bb.getHeight()) * 0.5f - bb.getY();
  435. moveRangeOfGlyphs (startIndex, num, deltaX, deltaY);
  436. if (justification.testFlags (Justification::horizontallyJustified))
  437. {
  438. int lineStart = 0;
  439. float baseY = glyphs.getReference (startIndex).getBaselineY();
  440. int i;
  441. for (i = 0; i < num; ++i)
  442. {
  443. const float glyphY = glyphs.getReference (startIndex + i).getBaselineY();
  444. if (glyphY != baseY)
  445. {
  446. spreadOutLine (startIndex + lineStart, i - lineStart, width);
  447. lineStart = i;
  448. baseY = glyphY;
  449. }
  450. }
  451. if (i > lineStart)
  452. spreadOutLine (startIndex + lineStart, i - lineStart, width);
  453. }
  454. }
  455. }
  456. void GlyphArrangement::spreadOutLine (const int start, const int num, const float targetWidth)
  457. {
  458. if (start + num < glyphs.size()
  459. && glyphs.getReference (start + num - 1).getCharacter() != '\r'
  460. && glyphs.getReference (start + num - 1).getCharacter() != '\n')
  461. {
  462. int numSpaces = 0;
  463. int spacesAtEnd = 0;
  464. for (int i = 0; i < num; ++i)
  465. {
  466. if (glyphs.getReference (start + i).isWhitespace())
  467. {
  468. ++spacesAtEnd;
  469. ++numSpaces;
  470. }
  471. else
  472. {
  473. spacesAtEnd = 0;
  474. }
  475. }
  476. numSpaces -= spacesAtEnd;
  477. if (numSpaces > 0)
  478. {
  479. const float startX = glyphs.getReference (start).getLeft();
  480. const float endX = glyphs.getReference (start + num - 1 - spacesAtEnd).getRight();
  481. const float extraPaddingBetweenWords
  482. = (targetWidth - (endX - startX)) / (float) numSpaces;
  483. float deltaX = 0.0f;
  484. for (int i = 0; i < num; ++i)
  485. {
  486. glyphs.getReference (start + i).moveBy (deltaX, 0.0f);
  487. if (glyphs.getReference (start + i).isWhitespace())
  488. deltaX += extraPaddingBetweenWords;
  489. }
  490. }
  491. }
  492. }
  493. void GlyphArrangement::splitLines (const String& text, Font font, int startIndex,
  494. float x, float y, float width, float height, int maximumLines,
  495. float lineWidth, Justification layout, float minimumHorizontalScale)
  496. {
  497. const int length = text.length();
  498. const int originalStartIndex = startIndex;
  499. int numLines = 1;
  500. if (length <= 12 && ! text.containsAnyOf (" -\t\r\n"))
  501. maximumLines = 1;
  502. maximumLines = jmin (maximumLines, length);
  503. while (numLines < maximumLines)
  504. {
  505. ++numLines;
  506. const float newFontHeight = height / (float) numLines;
  507. if (newFontHeight < font.getHeight())
  508. {
  509. font.setHeight (jmax (8.0f, newFontHeight));
  510. removeRangeOfGlyphs (startIndex, -1);
  511. addLineOfText (font, text, x, y);
  512. lineWidth = glyphs.getReference (glyphs.size() - 1).getRight()
  513. - glyphs.getReference (startIndex).getLeft();
  514. }
  515. // Try to estimate the point at which there are enough lines to fit the text,
  516. // allowing for unevenness in the lengths due to differently sized words.
  517. const float lineLengthUnevennessAllowance = 80.0f;
  518. if (numLines > (lineWidth + lineLengthUnevennessAllowance) / width || newFontHeight < 8.0f)
  519. break;
  520. }
  521. if (numLines < 1)
  522. numLines = 1;
  523. float lineY = y;
  524. float widthPerLine = lineWidth / numLines;
  525. for (int line = 0; line < numLines; ++line)
  526. {
  527. int i = startIndex;
  528. float lineStartX = glyphs.getReference (startIndex).getLeft();
  529. if (line == numLines - 1)
  530. {
  531. widthPerLine = width;
  532. i = glyphs.size();
  533. }
  534. else
  535. {
  536. while (i < glyphs.size())
  537. {
  538. lineWidth = (glyphs.getReference (i).getRight() - lineStartX);
  539. if (lineWidth > widthPerLine)
  540. {
  541. // got to a point where the line's too long, so skip forward to find a
  542. // good place to break it..
  543. const int searchStartIndex = i;
  544. while (i < glyphs.size())
  545. {
  546. if ((glyphs.getReference (i).getRight() - lineStartX) * minimumHorizontalScale < width)
  547. {
  548. if (glyphs.getReference (i).isWhitespace()
  549. || glyphs.getReference (i).getCharacter() == '-')
  550. {
  551. ++i;
  552. break;
  553. }
  554. }
  555. else
  556. {
  557. // can't find a suitable break, so try looking backwards..
  558. i = searchStartIndex;
  559. for (int back = 1; back < jmin (7, i - startIndex - 1); ++back)
  560. {
  561. if (glyphs.getReference (i - back).isWhitespace()
  562. || glyphs.getReference (i - back).getCharacter() == '-')
  563. {
  564. i -= back - 1;
  565. break;
  566. }
  567. }
  568. break;
  569. }
  570. ++i;
  571. }
  572. break;
  573. }
  574. ++i;
  575. }
  576. int wsStart = i;
  577. while (wsStart > 0 && glyphs.getReference (wsStart - 1).isWhitespace())
  578. --wsStart;
  579. int wsEnd = i;
  580. while (wsEnd < glyphs.size() && glyphs.getReference (wsEnd).isWhitespace())
  581. ++wsEnd;
  582. removeRangeOfGlyphs (wsStart, wsEnd - wsStart);
  583. i = jmax (wsStart, startIndex + 1);
  584. }
  585. i -= fitLineIntoSpace (startIndex, i - startIndex,
  586. x, lineY, width, font.getHeight(), font,
  587. layout.getOnlyHorizontalFlags() | Justification::verticallyCentred,
  588. minimumHorizontalScale);
  589. startIndex = i;
  590. lineY += font.getHeight();
  591. if (startIndex >= glyphs.size())
  592. break;
  593. }
  594. justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex,
  595. x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified);
  596. }
  597. //==============================================================================
  598. void GlyphArrangement::drawGlyphUnderline (const Graphics& g, const PositionedGlyph& pg,
  599. const int i, const AffineTransform& transform) const
  600. {
  601. const float lineThickness = (pg.font.getDescent()) * 0.3f;
  602. float nextX = pg.x + pg.w;
  603. if (i < glyphs.size() - 1 && glyphs.getReference (i + 1).y == pg.y)
  604. nextX = glyphs.getReference (i + 1).x;
  605. Path p;
  606. p.addRectangle (pg.x, pg.y + lineThickness * 2.0f, nextX - pg.x, lineThickness);
  607. g.fillPath (p, transform);
  608. }
  609. void GlyphArrangement::draw (const Graphics& g) const
  610. {
  611. draw (g, AffineTransform());
  612. }
  613. void GlyphArrangement::draw (const Graphics& g, const AffineTransform& transform) const
  614. {
  615. LowLevelGraphicsContext& context = g.getInternalContext();
  616. Font lastFont (context.getFont());
  617. bool needToRestore = false;
  618. for (int i = 0; i < glyphs.size(); ++i)
  619. {
  620. const PositionedGlyph& pg = glyphs.getReference(i);
  621. if (pg.font.isUnderlined())
  622. drawGlyphUnderline (g, pg, i, transform);
  623. if (! pg.isWhitespace())
  624. {
  625. if (lastFont != pg.font)
  626. {
  627. lastFont = pg.font;
  628. if (! needToRestore)
  629. {
  630. needToRestore = true;
  631. context.saveState();
  632. }
  633. context.setFont (lastFont);
  634. }
  635. context.drawGlyph (pg.glyph, AffineTransform::translation (pg.x, pg.y).followedBy (transform));
  636. }
  637. }
  638. if (needToRestore)
  639. context.restoreState();
  640. }
  641. void GlyphArrangement::createPath (Path& path) const
  642. {
  643. for (int i = 0; i < glyphs.size(); ++i)
  644. glyphs.getReference (i).createPath (path);
  645. }
  646. int GlyphArrangement::findGlyphIndexAt (const float x, const float y) const
  647. {
  648. for (int i = 0; i < glyphs.size(); ++i)
  649. if (glyphs.getReference (i).hitTest (x, y))
  650. return i;
  651. return -1;
  652. }