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.

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