29static String substring (
const String& text, Range<int> range)
31 return text.
substring (range.getStart(), range.getEnd());
34TextLayout::Glyph::Glyph (
int glyph, Point<float> anch,
float w) noexcept
35 : glyphCode (glyph), anchor (anch), width (w)
40TextLayout::Run::Run (Range<int> range,
int numGlyphsToPreallocate)
43 glyphs.ensureStorageAllocated (numGlyphsToPreallocate);
51 for (
auto& glyph : glyphs)
53 Range<float> r (glyph.anchor.x, glyph.anchor.x + glyph.width);
71 float lead,
int numRunsToPreallocate)
72 : stringRange (range), lineOrigin (o),
73 ascent (asc), descent (desc), leading (lead)
75 runs.ensureStorageAllocated (numRunsToPreallocate);
78TextLayout::Line::Line (
const Line& other)
79 : stringRange (other.stringRange), lineOrigin (other.lineOrigin),
80 ascent (other.ascent), descent (other.descent), leading (other.leading)
82 runs.addCopiesOf (other.runs);
85TextLayout::Line& TextLayout::Line::operator= (
const Line& other)
97 for (
auto* run : runs)
99 auto runRange = run->getRunBoundsX();
112 return range + lineOrigin.x;
117 return { lineOrigin.y - ascent,
118 lineOrigin.y + descent };
123 auto x = getLineBoundsX();
124 auto y = getLineBoundsY();
126 return { x.getStart(), y.getStart(), x.getLength(), y.getLength() };
129void TextLayout::Line::swap (
Line& other)
noexcept
132 std::swap (other.stringRange, stringRange);
133 std::swap (other.lineOrigin, lineOrigin);
141 : width (0), height (0), justification (
Justification::topLeft)
146 : width (other.width), height (other.height),
147 justification (other.justification)
149 lines.addCopiesOf (other.lines);
153 : lines (std::move (other.lines)),
154 width (other.width), height (other.height),
155 justification (other.justification)
159TextLayout& TextLayout::operator= (TextLayout&& other)
noexcept
161 lines = std::move (other.lines);
163 height = other.height;
164 justification = other.justification;
168TextLayout& TextLayout::operator= (
const TextLayout& other)
171 height = other.height;
172 justification = other.justification;
174 lines.addCopiesOf (other.lines);
184 return *lines.getUnchecked (index);
189 lines.ensureStorageAllocated (numLinesNeeded);
201 auto& context = g.getInternalContext();
204 auto clip = context.getClipBounds();
205 auto clipTop = (
float) clip.getY() - origin.y;
206 auto clipBottom = (
float) clip.getBottom() - origin.y;
208 for (
auto& line : *
this)
210 auto lineRangeY = line.getLineBoundsY();
212 if (lineRangeY.getEnd() < clipTop)
215 if (lineRangeY.getStart() > clipBottom)
218 auto lineOrigin = origin + line.lineOrigin;
220 for (
auto* run : line.runs)
222 context.setFont (run->font);
223 context.setFill (run->colour);
225 for (
auto& glyph : run->glyphs)
227 lineOrigin.y + glyph.anchor.y));
229 if (run->font.isUnderlined())
231 auto runExtent = run->getRunBoundsX();
232 auto lineThickness = run->font.getDescent() * 0.3f;
234 context.fillRect ({ runExtent.getStart() + lineOrigin.x, lineOrigin.y + lineThickness * 2.0f,
235 runExtent.getLength(), lineThickness });
240 context.restoreState();
255 if (! createNativeLayout (text))
256 createStandardLayout (text);
268 auto minimumWidth = maxWidth / 2.0f;
269 auto bestWidth = maxWidth;
270 float bestLineProportion = 0.0f;
272 while (maxWidth > minimumWidth)
279 auto line1 = lines.getUnchecked (lines.size() - 1)->getLineBoundsX().getLength();
280 auto line2 = lines.getUnchecked (lines.size() - 2)->getLineBoundsX().getLength();
281 auto shortest =
jmin (line1, line2);
282 auto longest =
jmax (line1, line2);
283 auto prop = shortest > 0 ? longest / shortest : 1.0f;
285 if (prop > 0.9f && prop < 1.1f)
288 if (prop > bestLineProportion)
290 bestLineProportion = prop;
291 bestWidth = maxWidth;
302namespace TextLayoutHelpers
307 : text (t), font (f), colour (c),
308 area (font.getStringWidthFloat (t), f.
getHeight()),
309 isWhitespace (whitespace),
319 const bool isWhitespace, isNewLine;
335 int charPosition = 0;
336 int lineStartPosition = 0;
337 int runStartPosition = 0;
342 bool needToSetLineOrigin =
true;
344 for (
int i = 0; i < tokens.size(); ++i)
346 auto& t = *tokens.getUnchecked (i);
350 t.font.getGlyphPositions (getTrimmedEndIfNotAllWhitespace (t.text), newGlyphs, xOffsets);
355 const auto numGlyphs = newGlyphs.
size();
356 charPosition += numGlyphs;
359 && (! (t.isWhitespace || t.isNewLine) || needToSetLineOrigin))
361 currentRun->glyphs.ensureStorageAllocated (currentRun->glyphs.size() + newGlyphs.
size());
362 auto tokenOrigin = t.area.getPosition().translated (0, t.font.getAscent());
364 if (needToSetLineOrigin)
366 needToSetLineOrigin =
false;
367 currentLine->lineOrigin = tokenOrigin;
370 auto glyphOffset = tokenOrigin - currentLine->lineOrigin;
372 for (
int j = 0; j < newGlyphs.
size(); ++j)
376 glyphOffset.translated (x, 0),
381 if (
auto* nextToken = tokens[i + 1])
383 if (t.font != nextToken->font || t.colour != nextToken->colour)
385 addRun (*currentLine, currentRun.
release(), t, runStartPosition, charPosition);
386 runStartPosition = charPosition;
389 if (t.line != nextToken->line)
391 if (currentRun ==
nullptr)
394 addRun (*currentLine, currentRun.
release(), t, runStartPosition, charPosition);
395 currentLine->stringRange = { lineStartPosition, charPosition };
397 if (! needToSetLineOrigin)
398 layout.
addLine (std::move (currentLine));
400 runStartPosition = charPosition;
401 lineStartPosition = charPosition;
402 needToSetLineOrigin =
true;
407 addRun (*currentLine, currentRun.
release(), t, runStartPosition, charPosition);
408 currentLine->stringRange = { lineStartPosition, charPosition };
410 if (! needToSetLineOrigin)
411 layout.
addLine (std::move (currentLine));
413 needToSetLineOrigin =
true;
422 for (
auto& line : layout)
424 auto dx = totalW - line.getLineBoundsX().getLength();
429 line.lineOrigin.x += dx;
439 glyphRun->
font = t.font;
440 glyphRun->
colour = t.colour;
441 glyphLine.ascent =
jmax (glyphLine.ascent, t.font.
getAscent());
442 glyphLine.descent =
jmax (glyphLine.descent, t.font.
getDescent());
443 glyphLine.
runs.add (glyphRun);
446 static int getCharacterType (
juce_wchar c)
noexcept
448 if (c ==
'\r' || c ==
'\n')
458 int lastCharType = 0;
462 auto c = t.getAndAdvance();
467 auto charType = getCharacterType (c);
469 if (charType == 0 || charType != lastCharType)
472 tokens.add (
new Token (currentString, font, colour,
473 lastCharType == 2 || lastCharType == 0));
477 if (c ==
'\r' && *t ==
'\n')
478 currentString += t.getAndAdvance();
485 lastCharType = charType;
489 tokens.add (
new Token (currentString, font, colour, lastCharType == 2));
494 float x = 0, y = 0, h = 0;
497 for (i = 0; i < tokens.size(); ++i)
499 auto& t = *tokens.getUnchecked (i);
505 auto* nextTok = tokens[i + 1];
507 if (nextTok ==
nullptr)
510 bool tokenTooLarge = (x + nextTok->area.getWidth() > maxWidth);
514 setLastLineHeight (i + 1, h);
522 setLastLineHeight (
jmin (i + 1, tokens.size()), h);
526 void setLastLineHeight (
int i,
float height)
noexcept
530 auto& tok = *tokens.getUnchecked (i);
532 if (tok.line == totalLines)
533 tok.lineHeight = height;
542 tokens.ensureStorageAllocated (
jmax (64, numAttributes));
544 for (
int i = 0; i < numAttributes; ++i)
548 appendText (substring (text.
getText(), attr.range),
549 attr.font, attr.colour);
553 static String getTrimmedEndIfNotAllWhitespace (
const String& s)
574 l.createLayout (text, *
this);
579 if (! lines.isEmpty())
581 auto bounds = lines.getFirst()->getLineBounds();
583 for (
auto* line : lines)
584 bounds = bounds.getUnion (line->getLineBounds());
586 for (
auto* line : lines)
587 line->lineOrigin.x -= bounds.getX();
589 width = bounds.getWidth();
590 height = bounds.getHeight();
Holds a resizable array of primitive or copy-by-value objects.
ElementType getUnchecked(int index) const
Returns one of the elements in the array, without checking the index passed in.
int size() const noexcept
Returns the current number of elements in the array.
A text string with a set of colour/font settings that are associated with sub-ranges of the text.
WordWrap getWordWrap() const noexcept
Returns the word-wrapping behaviour.
float getLineSpacing() const noexcept
Returns the extra line-spacing distance.
const Attribute & getAttribute(int index) const noexcept
Returns one of the string's attributes.
int getNumAttributes() const noexcept
Returns the number of attributes that have been added to this string.
WordWrap
Types of word-wrap behaviour.
@ none
No word-wrapping: lines extend indefinitely.
const String & getText() const noexcept
Returns the complete text of this attributed string.
Justification getJustification() const noexcept
Returns the justification that should be used for laying-out the text.
static bool isWhitespace(char character) noexcept
Checks whether a character is whitespace.
Represents a colour, also including a transparency value.
Represents a particular font, including its size, style, etc.
float getDescent() const
Returns the amount that the font descends below its baseline, in pixels.
float getHeight() const noexcept
Returns the total height of this font, in pixels.
float getAscent() const
Returns the height of the font above its baseline, in pixels.
A graphics context, used for drawing a component or image.
Represents a type of justification to be used when positioning graphical items.
@ horizontallyCentred
Indicates that the item should be placed in the centre between the left and right sides of the availa...
@ right
Indicates that the item should be aligned against the right edge of the available space.
const Rectangle< ValueType > appliedToRectangle(const Rectangle< ValueType > &areaToAdjust, const Rectangle< ValueType > &targetSpace) const noexcept
Returns the new position of a rectangle that has been justified to fit within a given space.
int getFlags() const noexcept
Returns the raw flags that are set for this Justification object.
An array designed for holding objects.
A pair of (x, y) coordinates.
A general-purpose range object, that simply represents any linear range with a start and end point.
constexpr Range getUnionWith(Range other) const noexcept
Returns the smallest range that contains both this one and the other one.
Manages a rectangle and allows geometric operations to be performed on it.
ValueType getWidth() const noexcept
Returns the width of the rectangle.
void setPosition(Point< ValueType > newPos) noexcept
Changes the position of the rectangle's top-left corner (leaving its size unchanged).
ValueType getHeight() const noexcept
Returns the height of the rectangle.
CharPointerType getCharPointer() const noexcept
Returns the character pointer currently being used to store this string.
bool containsChar(juce_wchar character) const noexcept
Tests whether the string contains a particular character.
String trimEnd() const
Returns a copy of this string with any whitespace characters removed from the end.
String replaceCharacters(StringRef charactersToReplace, StringRef charactersToInsertInstead) const
Replaces a set of characters with another set.
static String charToString(juce_wchar character)
Creates a string from a single character.
String substring(int startIndex, int endIndex) const
Returns a subsection of the string.
bool isNotEmpty() const noexcept
Returns true if the string contains at least one character.
A line containing a sequence of glyph-runs.
Range< float > getLineBoundsY() const noexcept
Returns the Y position range which contains all the glyphs in this line.
Rectangle< float > getLineBounds() const noexcept
Returns the smallest rectangle which contains all the glyphs in this line.
Range< float > getLineBoundsX() const noexcept
Returns the X position range which contains all the glyphs in this line.
OwnedArray< Run > runs
The glyph-runs in this line.
A sequence of glyphs with a common font and colour.
Range< int > stringRange
The character range that this run represents in the original string that was used to create it.
Colour colour
The run's colour.
A Pre-formatted piece of text, which may contain multiple fonts and colours.
void recalculateSize()
If you modify the TextLayout after creating it, call this to compute the new dimensions of the conten...
void createLayoutWithBalancedLineLengths(const AttributedString &, float maxWidth)
Creates a layout, attempting to choose a width which results in lines of a similar length.
int getNumLines() const noexcept
Returns the number of lines in the layout.
float getWidth() const noexcept
Returns the maximum width of the content.
void ensureStorageAllocated(int numLinesNeeded)
Pre-allocates space for the specified number of lines.
void addLine(std::unique_ptr< Line >)
Adds a line to the layout.
void draw(Graphics &, Rectangle< float > area) const
Draws the layout within the specified area.
float getHeight() const noexcept
Returns the maximum height of the content.
void createLayout(const AttributedString &, float maxWidth)
Creates a layout from the given attributed string.
TextLayout()
Creates an empty layout.
Line & getLine(int index) const noexcept
Returns one of the lines.
wchar_t juce_wchar
A platform-independent 32-bit unicode character type.
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.