2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../../core/juce_StandardHeader.h"
30 #include "juce_GlyphArrangement.h"
31 #include "../contexts/juce_LowLevelGraphicsSoftwareRenderer.h"
32 #include "../imaging/juce_Image.h"
33 #include "../../../utilities/juce_DeletedAtShutdown.h"
36 //==============================================================================
37 PositionedGlyph::PositionedGlyph (const Font
& font_
, const juce_wchar character_
, const int glyph_
,
38 const float x_
, const float y_
, const float w_
, const bool whitespace_
)
39 : font (font_
), character (character_
), glyph (glyph_
),
40 x (x_
), y (y_
), w (w_
), whitespace (whitespace_
)
44 PositionedGlyph::PositionedGlyph (const PositionedGlyph
& other
)
45 : font (other
.font
), character (other
.character
), glyph (other
.glyph
),
46 x (other
.x
), y (other
.y
), w (other
.w
), whitespace (other
.whitespace
)
50 PositionedGlyph::~PositionedGlyph() {}
52 PositionedGlyph
& PositionedGlyph::operator= (const PositionedGlyph
& other
)
55 character
= other
.character
;
60 whitespace
= other
.whitespace
;
64 void PositionedGlyph::draw (const Graphics
& g
) const
68 LowLevelGraphicsContext
* const context
= g
.getInternalContext();
69 context
->setFont (font
);
70 context
->drawGlyph (glyph
, AffineTransform::translation (x
, y
));
74 void PositionedGlyph::draw (const Graphics
& g
,
75 const AffineTransform
& transform
) const
79 LowLevelGraphicsContext
* const context
= g
.getInternalContext();
80 context
->setFont (font
);
81 context
->drawGlyph (glyph
, AffineTransform::translation (x
, y
)
82 .followedBy (transform
));
86 void PositionedGlyph::createPath (Path
& path
) const
90 Typeface
* const t
= font
.getTypeface();
95 t
->getOutlineForGlyph (glyph
, p
);
97 path
.addPath (p
, AffineTransform::scale (font
.getHeight() * font
.getHorizontalScale(), font
.getHeight())
103 bool PositionedGlyph::hitTest (float px
, float py
) const
105 if (getBounds().contains (px
, py
) && ! isWhitespace())
107 Typeface
* const t
= font
.getTypeface();
112 t
->getOutlineForGlyph (glyph
, p
);
114 AffineTransform::translation (-x
, -y
)
115 .scaled (1.0f
/ (font
.getHeight() * font
.getHorizontalScale()), 1.0f
/ font
.getHeight())
116 .transformPoint (px
, py
);
118 return p
.contains (px
, py
);
125 void PositionedGlyph::moveBy (const float deltaX
,
133 //==============================================================================
134 GlyphArrangement::GlyphArrangement()
136 glyphs
.ensureStorageAllocated (128);
139 GlyphArrangement::GlyphArrangement (const GlyphArrangement
& other
)
141 addGlyphArrangement (other
);
144 GlyphArrangement
& GlyphArrangement::operator= (const GlyphArrangement
& other
)
149 addGlyphArrangement (other
);
155 GlyphArrangement::~GlyphArrangement()
159 //==============================================================================
160 void GlyphArrangement::clear()
165 PositionedGlyph
& GlyphArrangement::getGlyph (const int index
) const
167 jassert (isPositiveAndBelow (index
, glyphs
.size()));
169 return *glyphs
[index
];
172 //==============================================================================
173 void GlyphArrangement::addGlyphArrangement (const GlyphArrangement
& other
)
175 glyphs
.ensureStorageAllocated (glyphs
.size() + other
.glyphs
.size());
176 glyphs
.addCopiesOf (other
.glyphs
);
179 void GlyphArrangement::addGlyph (const PositionedGlyph
& glyph
)
181 glyphs
.add (new PositionedGlyph (glyph
));
184 void GlyphArrangement::removeRangeOfGlyphs (int startIndex
, const int num
)
186 glyphs
.removeRange (startIndex
, num
< 0 ? glyphs
.size() : num
);
189 //==============================================================================
190 void GlyphArrangement::addLineOfText (const Font
& font
,
195 addCurtailedLineOfText (font
, text
,
200 void GlyphArrangement::addCurtailedLineOfText (const Font
& font
,
204 const float maxWidthPixels
,
205 const bool useEllipsis
)
207 if (text
.isNotEmpty())
209 Array
<int> newGlyphs
;
210 Array
<float> xOffsets
;
211 font
.getGlyphPositions (text
, newGlyphs
, xOffsets
);
212 const int textLen
= newGlyphs
.size();
213 glyphs
.ensureStorageAllocated (glyphs
.size() + textLen
);
215 String::CharPointerType
t (text
.getCharPointer());
217 for (int i
= 0; i
< textLen
; ++i
)
219 const float thisX
= xOffsets
.getUnchecked (i
);
220 const float nextX
= xOffsets
.getUnchecked (i
+ 1);
222 if (nextX
> maxWidthPixels
+ 1.0f
)
224 // curtail the string if it's too wide..
225 if (useEllipsis
&& textLen
> 3 && glyphs
.size() >= 3)
226 insertEllipsis (font
, xOffset
+ maxWidthPixels
, 0, glyphs
.size());
232 const bool isWhitespace
= t
.isWhitespace();
234 glyphs
.add (new PositionedGlyph (font
, t
.getAndAdvance(),
235 newGlyphs
.getUnchecked(i
),
236 xOffset
+ thisX
, yOffset
,
237 nextX
- thisX
, isWhitespace
));
243 int GlyphArrangement::insertEllipsis (const Font
& font
, const float maxXPos
,
244 const int startIndex
, int endIndex
)
248 if (glyphs
.size() > 0)
250 Array
<int> dotGlyphs
;
252 font
.getGlyphPositions ("..", dotGlyphs
, dotXs
);
254 const float dx
= dotXs
[1];
255 float xOffset
= 0.0f
, yOffset
= 0.0f
;
257 while (endIndex
> startIndex
)
259 const PositionedGlyph
* pg
= glyphs
.getUnchecked (--endIndex
);
263 glyphs
.remove (endIndex
);
266 if (xOffset
+ dx
* 3 <= maxXPos
)
270 for (int i
= 3; --i
>= 0;)
272 glyphs
.insert (endIndex
++, new PositionedGlyph (font
, '.', dotGlyphs
.getFirst(),
273 xOffset
, yOffset
, dx
, false));
277 if (xOffset
> maxXPos
)
285 void GlyphArrangement::addJustifiedText (const Font
& font
,
288 const float maxLineWidth
,
289 const Justification
& horizontalLayout
)
291 int lineStartIndex
= glyphs
.size();
292 addLineOfText (font
, text
, x
, y
);
294 const float originalY
= y
;
296 while (lineStartIndex
< glyphs
.size())
298 int i
= lineStartIndex
;
300 if (glyphs
.getUnchecked(i
)->getCharacter() != '\n'
301 && glyphs
.getUnchecked(i
)->getCharacter() != '\r')
304 const float lineMaxX
= glyphs
.getUnchecked (lineStartIndex
)->getLeft() + maxLineWidth
;
305 int lastWordBreakIndex
= -1;
307 while (i
< glyphs
.size())
309 const PositionedGlyph
* pg
= glyphs
.getUnchecked (i
);
310 const juce_wchar c
= pg
->getCharacter();
312 if (c
== '\r' || c
== '\n')
316 if (c
== '\r' && i
< glyphs
.size()
317 && glyphs
.getUnchecked(i
)->getCharacter() == '\n')
322 else if (pg
->isWhitespace())
324 lastWordBreakIndex
= i
+ 1;
326 else if (pg
->getRight() - 0.0001f
>= lineMaxX
)
328 if (lastWordBreakIndex
>= 0)
329 i
= lastWordBreakIndex
;
337 const float currentLineStartX
= glyphs
.getUnchecked (lineStartIndex
)->getLeft();
338 float currentLineEndX
= currentLineStartX
;
340 for (int j
= i
; --j
>= lineStartIndex
;)
342 if (! glyphs
.getUnchecked (j
)->isWhitespace())
344 currentLineEndX
= glyphs
.getUnchecked (j
)->getRight();
351 if (horizontalLayout
.testFlags (Justification::horizontallyJustified
))
352 spreadOutLine (lineStartIndex
, i
- lineStartIndex
, maxLineWidth
);
353 else if (horizontalLayout
.testFlags (Justification::horizontallyCentred
))
354 deltaX
= (maxLineWidth
- (currentLineEndX
- currentLineStartX
)) * 0.5f
;
355 else if (horizontalLayout
.testFlags (Justification::right
))
356 deltaX
= maxLineWidth
- (currentLineEndX
- currentLineStartX
);
358 moveRangeOfGlyphs (lineStartIndex
, i
- lineStartIndex
,
359 x
+ deltaX
- currentLineStartX
, y
- originalY
);
363 y
+= font
.getHeight();
367 void GlyphArrangement::addFittedText (const Font
& f
,
369 const float x
, const float y
,
370 const float width
, const float height
,
371 const Justification
& layout
,
373 const float minimumHorizontalScale
)
375 // doesn't make much sense if this is outside a sensible range of 0.5 to 1.0
376 jassert (minimumHorizontalScale
> 0 && minimumHorizontalScale
<= 1.0f
);
378 if (text
.containsAnyOf ("\r\n"))
381 ga
.addJustifiedText (f
, text
, x
, y
, width
, layout
);
383 const Rectangle
<float> bb (ga
.getBoundingBox (0, -1, false));
385 float dy
= y
- bb
.getY();
387 if (layout
.testFlags (Justification::verticallyCentred
))
388 dy
+= (height
- bb
.getHeight()) * 0.5f
;
389 else if (layout
.testFlags (Justification::bottom
))
390 dy
+= height
- bb
.getHeight();
392 ga
.moveRangeOfGlyphs (0, -1, 0.0f
, dy
);
394 glyphs
.ensureStorageAllocated (glyphs
.size() + ga
.glyphs
.size());
396 for (int i
= 0; i
< ga
.glyphs
.size(); ++i
)
397 glyphs
.add (ga
.glyphs
.getUnchecked (i
));
399 ga
.glyphs
.clear (false);
403 int startIndex
= glyphs
.size();
404 addLineOfText (f
, text
.trim(), x
, y
);
406 if (glyphs
.size() > startIndex
)
408 float lineWidth
= glyphs
.getUnchecked (glyphs
.size() - 1)->getRight()
409 - glyphs
.getUnchecked (startIndex
)->getLeft();
414 if (lineWidth
* minimumHorizontalScale
< width
)
416 if (lineWidth
> width
)
417 stretchRangeOfGlyphs (startIndex
, glyphs
.size() - startIndex
,
420 justifyGlyphs (startIndex
, glyphs
.size() - startIndex
,
421 x
, y
, width
, height
, layout
);
423 else if (maximumLines
<= 1)
425 fitLineIntoSpace (startIndex
, glyphs
.size() - startIndex
,
426 x
, y
, width
, height
, f
, layout
, minimumHorizontalScale
);
431 String
txt (text
.trim());
432 const int length
= txt
.length();
433 const int originalStartIndex
= startIndex
;
436 if (length
<= 12 && ! txt
.containsAnyOf (" -\t\r\n"))
439 maximumLines
= jmin (maximumLines
, length
);
441 while (numLines
< maximumLines
)
445 const float newFontHeight
= height
/ (float) numLines
;
447 if (newFontHeight
< font
.getHeight())
449 font
.setHeight (jmax (8.0f
, newFontHeight
));
451 removeRangeOfGlyphs (startIndex
, -1);
452 addLineOfText (font
, txt
, x
, y
);
454 lineWidth
= glyphs
.getUnchecked (glyphs
.size() - 1)->getRight()
455 - glyphs
.getUnchecked (startIndex
)->getLeft();
458 if (numLines
> lineWidth
/ width
|| newFontHeight
< 8.0f
)
466 float widthPerLine
= lineWidth
/ numLines
;
467 int lastLineStartIndex
= 0;
469 for (int line
= 0; line
< numLines
; ++line
)
472 lastLineStartIndex
= i
;
473 float lineStartX
= glyphs
.getUnchecked (startIndex
)->getLeft();
475 if (line
== numLines
- 1)
477 widthPerLine
= width
;
482 while (i
< glyphs
.size())
484 lineWidth
= (glyphs
.getUnchecked (i
)->getRight() - lineStartX
);
486 if (lineWidth
> widthPerLine
)
488 // got to a point where the line's too long, so skip forward to find a
489 // good place to break it..
490 const int searchStartIndex
= i
;
492 while (i
< glyphs
.size())
494 if ((glyphs
.getUnchecked (i
)->getRight() - lineStartX
) * minimumHorizontalScale
< width
)
496 if (glyphs
.getUnchecked (i
)->isWhitespace()
497 || glyphs
.getUnchecked (i
)->getCharacter() == '-')
505 // can't find a suitable break, so try looking backwards..
506 i
= searchStartIndex
;
508 for (int back
= 1; back
< jmin (5, i
- startIndex
- 1); ++back
)
510 if (glyphs
.getUnchecked (i
- back
)->isWhitespace()
511 || glyphs
.getUnchecked (i
- back
)->getCharacter() == '-')
531 while (wsStart
> 0 && glyphs
.getUnchecked (wsStart
- 1)->isWhitespace())
536 while (wsEnd
< glyphs
.size() && glyphs
.getUnchecked (wsEnd
)->isWhitespace())
539 removeRangeOfGlyphs (wsStart
, wsEnd
- wsStart
);
540 i
= jmax (wsStart
, startIndex
+ 1);
543 i
-= fitLineIntoSpace (startIndex
, i
- startIndex
,
544 x
, lineY
, width
, font
.getHeight(), font
,
545 layout
.getOnlyHorizontalFlags() | Justification::verticallyCentred
,
546 minimumHorizontalScale
);
549 lineY
+= font
.getHeight();
551 if (startIndex
>= glyphs
.size())
555 justifyGlyphs (originalStartIndex
, glyphs
.size() - originalStartIndex
,
556 x
, y
, width
, height
, layout
.getFlags() & ~Justification::horizontallyJustified
);
561 //==============================================================================
562 void GlyphArrangement::moveRangeOfGlyphs (int startIndex
, int num
,
563 const float dx
, const float dy
)
565 jassert (startIndex
>= 0);
567 if (dx
!= 0.0f
|| dy
!= 0.0f
)
569 if (num
< 0 || startIndex
+ num
> glyphs
.size())
570 num
= glyphs
.size() - startIndex
;
573 glyphs
.getUnchecked (startIndex
++)->moveBy (dx
, dy
);
577 int GlyphArrangement::fitLineIntoSpace (int start
, int numGlyphs
, float x
, float y
, float w
, float h
, const Font
& font
,
578 const Justification
& justification
, float minimumHorizontalScale
)
581 const float lineStartX
= glyphs
.getUnchecked (start
)->getLeft();
582 float lineWidth
= glyphs
.getUnchecked (start
+ numGlyphs
- 1)->getRight() - lineStartX
;
586 if (minimumHorizontalScale
< 1.0f
)
588 stretchRangeOfGlyphs (start
, numGlyphs
, jmax (minimumHorizontalScale
, w
/ lineWidth
));
589 lineWidth
= glyphs
.getUnchecked (start
+ numGlyphs
- 1)->getRight() - lineStartX
- 0.5f
;
594 numDeleted
= insertEllipsis (font
, lineStartX
+ w
, start
, start
+ numGlyphs
);
595 numGlyphs
-= numDeleted
;
599 justifyGlyphs (start
, numGlyphs
, x
, y
, w
, h
, justification
);
603 void GlyphArrangement::stretchRangeOfGlyphs (int startIndex
, int num
,
604 const float horizontalScaleFactor
)
606 jassert (startIndex
>= 0);
608 if (num
< 0 || startIndex
+ num
> glyphs
.size())
609 num
= glyphs
.size() - startIndex
;
613 const float xAnchor
= glyphs
.getUnchecked (startIndex
)->getLeft();
617 PositionedGlyph
* const pg
= glyphs
.getUnchecked (startIndex
++);
619 pg
->x
= xAnchor
+ (pg
->x
- xAnchor
) * horizontalScaleFactor
;
620 pg
->font
.setHorizontalScale (pg
->font
.getHorizontalScale() * horizontalScaleFactor
);
621 pg
->w
*= horizontalScaleFactor
;
626 const Rectangle
<float> GlyphArrangement::getBoundingBox (int startIndex
, int num
, const bool includeWhitespace
) const
628 jassert (startIndex
>= 0);
630 if (num
< 0 || startIndex
+ num
> glyphs
.size())
631 num
= glyphs
.size() - startIndex
;
633 Rectangle
<float> result
;
637 const PositionedGlyph
* const pg
= glyphs
.getUnchecked (startIndex
++);
639 if (includeWhitespace
|| ! pg
->isWhitespace())
640 result
= result
.getUnion (pg
->getBounds());
646 void GlyphArrangement::justifyGlyphs (const int startIndex
, const int num
,
647 const float x
, const float y
, const float width
, const float height
,
648 const Justification
& justification
)
650 jassert (num
>= 0 && startIndex
>= 0);
652 if (glyphs
.size() > 0 && num
> 0)
654 const Rectangle
<float> bb (getBoundingBox (startIndex
, num
, ! justification
.testFlags (Justification::horizontallyJustified
655 | Justification::horizontallyCentred
)));
658 if (justification
.testFlags (Justification::horizontallyJustified
))
659 deltaX
= x
- bb
.getX();
660 else if (justification
.testFlags (Justification::horizontallyCentred
))
661 deltaX
= x
+ (width
- bb
.getWidth()) * 0.5f
- bb
.getX();
662 else if (justification
.testFlags (Justification::right
))
663 deltaX
= (x
+ width
) - bb
.getRight();
665 deltaX
= x
- bb
.getX();
669 if (justification
.testFlags (Justification::top
))
670 deltaY
= y
- bb
.getY();
671 else if (justification
.testFlags (Justification::bottom
))
672 deltaY
= (y
+ height
) - bb
.getBottom();
674 deltaY
= y
+ (height
- bb
.getHeight()) * 0.5f
- bb
.getY();
676 moveRangeOfGlyphs (startIndex
, num
, deltaX
, deltaY
);
678 if (justification
.testFlags (Justification::horizontallyJustified
))
681 float baseY
= glyphs
.getUnchecked (startIndex
)->getBaselineY();
684 for (i
= 0; i
< num
; ++i
)
686 const float glyphY
= glyphs
.getUnchecked (startIndex
+ i
)->getBaselineY();
690 spreadOutLine (startIndex
+ lineStart
, i
- lineStart
, width
);
698 spreadOutLine (startIndex
+ lineStart
, i
- lineStart
, width
);
703 void GlyphArrangement::spreadOutLine (const int start
, const int num
, const float targetWidth
)
705 if (start
+ num
< glyphs
.size()
706 && glyphs
.getUnchecked (start
+ num
- 1)->getCharacter() != '\r'
707 && glyphs
.getUnchecked (start
+ num
- 1)->getCharacter() != '\n')
712 for (int i
= 0; i
< num
; ++i
)
714 if (glyphs
.getUnchecked (start
+ i
)->isWhitespace())
725 numSpaces
-= spacesAtEnd
;
729 const float startX
= glyphs
.getUnchecked (start
)->getLeft();
730 const float endX
= glyphs
.getUnchecked (start
+ num
- 1 - spacesAtEnd
)->getRight();
732 const float extraPaddingBetweenWords
733 = (targetWidth
- (endX
- startX
)) / (float) numSpaces
;
737 for (int i
= 0; i
< num
; ++i
)
739 glyphs
.getUnchecked (start
+ i
)->moveBy (deltaX
, 0.0f
);
741 if (glyphs
.getUnchecked (start
+ i
)->isWhitespace())
742 deltaX
+= extraPaddingBetweenWords
;
748 //==============================================================================
749 void GlyphArrangement::draw (const Graphics
& g
) const
751 for (int i
= 0; i
< glyphs
.size(); ++i
)
753 const PositionedGlyph
* const pg
= glyphs
.getUnchecked(i
);
755 if (pg
->font
.isUnderlined())
757 const float lineThickness
= (pg
->font
.getDescent()) * 0.3f
;
759 float nextX
= pg
->x
+ pg
->w
;
761 if (i
< glyphs
.size() - 1 && glyphs
.getUnchecked (i
+ 1)->y
== pg
->y
)
762 nextX
= glyphs
.getUnchecked (i
+ 1)->x
;
764 g
.fillRect (pg
->x
, pg
->y
+ lineThickness
* 2.0f
,
765 nextX
- pg
->x
, lineThickness
);
772 void GlyphArrangement::draw (const Graphics
& g
, const AffineTransform
& transform
) const
774 for (int i
= 0; i
< glyphs
.size(); ++i
)
776 const PositionedGlyph
* const pg
= glyphs
.getUnchecked(i
);
778 if (pg
->font
.isUnderlined())
780 const float lineThickness
= (pg
->font
.getDescent()) * 0.3f
;
782 float nextX
= pg
->x
+ pg
->w
;
784 if (i
< glyphs
.size() - 1 && glyphs
.getUnchecked (i
+ 1)->y
== pg
->y
)
785 nextX
= glyphs
.getUnchecked (i
+ 1)->x
;
788 p
.addLineSegment (Line
<float> (pg
->x
, pg
->y
+ lineThickness
* 2.0f
,
789 nextX
, pg
->y
+ lineThickness
* 2.0f
),
792 g
.fillPath (p
, transform
);
795 pg
->draw (g
, transform
);
799 void GlyphArrangement::createPath (Path
& path
) const
801 for (int i
= 0; i
< glyphs
.size(); ++i
)
802 glyphs
.getUnchecked (i
)->createPath (path
);
805 int GlyphArrangement::findGlyphIndexAt (float x
, float y
) const
807 for (int i
= 0; i
< glyphs
.size(); ++i
)
808 if (glyphs
.getUnchecked (i
)->hitTest (x
, y
))