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.

751 lines
24KB

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