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