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.

758 lines
25KB

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