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.

802 lines
27KB

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