The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

792 lines
25KB

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