mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
GlyphArrangement: Use ShapedText
This commit is contained in:
parent
d84bacb3bb
commit
20afcb5bf3
2 changed files with 235 additions and 364 deletions
|
|
@ -153,83 +153,46 @@ void GlyphArrangement::addLineOfText (const Font& font, const String& text, floa
|
|||
addCurtailedLineOfText (font, text, xOffset, yOffset, 1.0e10f, false);
|
||||
}
|
||||
|
||||
static void addGlyphsFromShapedText (GlyphArrangement& ga, const ShapedText& st, float x, float y)
|
||||
{
|
||||
st.access ([&] (auto shapedGlyphs, auto positions, auto font, auto glyphRange, auto)
|
||||
{
|
||||
for (size_t i = 0; i < shapedGlyphs.size(); ++i)
|
||||
{
|
||||
const auto glyphIndex = (int64) i + glyphRange.getStart();
|
||||
|
||||
auto& glyph = shapedGlyphs[i];
|
||||
auto& position = positions[i];
|
||||
|
||||
PositionedGlyph pg { font,
|
||||
st.getText()[(int) st.getTextRange (glyphIndex).getStart()],
|
||||
(int) glyph.glyphId,
|
||||
position.getX() + x,
|
||||
position.getY() + y,
|
||||
glyph.advance.getX(),
|
||||
glyph.whitespace };
|
||||
|
||||
ga.addGlyph (std::move (pg));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void GlyphArrangement::addCurtailedLineOfText (const Font& font, const String& text,
|
||||
float xOffset, float yOffset,
|
||||
float maxWidthPixels, bool useEllipsis)
|
||||
{
|
||||
if (text.isNotEmpty())
|
||||
{
|
||||
Array<int> newGlyphs;
|
||||
Array<float> xOffsets;
|
||||
font.getGlyphPositions (text, newGlyphs, xOffsets);
|
||||
auto textLen = newGlyphs.size();
|
||||
glyphs.ensureStorageAllocated (glyphs.size() + textLen);
|
||||
auto options = ShapedText::Options{}.withMaxNumLines (1)
|
||||
.withMaxWidth (maxWidthPixels)
|
||||
.withFont (font)
|
||||
.withBaselineAtZero()
|
||||
.withTrailingWhitespacesShouldFit (false);
|
||||
|
||||
auto t = text.getCharPointer();
|
||||
if (useEllipsis)
|
||||
options = options.withEllipsis();
|
||||
|
||||
for (int i = 0; i < textLen; ++i)
|
||||
{
|
||||
auto nextX = xOffsets.getUnchecked (i + 1);
|
||||
ShapedText st { text, options };
|
||||
|
||||
if (nextX > maxWidthPixels + 1.0f)
|
||||
{
|
||||
// curtail the string if it's too wide..
|
||||
if (useEllipsis && textLen > 3 && glyphs.size() >= 3)
|
||||
insertEllipsis (font, xOffset + maxWidthPixels, 0, glyphs.size());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
auto thisX = xOffsets.getUnchecked (i);
|
||||
auto isWhitespace = isNonBreakingSpace (*t) || t.isWhitespace();
|
||||
|
||||
glyphs.add (PositionedGlyph (font, t.getAndAdvance(),
|
||||
newGlyphs.getUnchecked (i),
|
||||
xOffset + thisX, yOffset,
|
||||
nextX - thisX, isWhitespace));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int GlyphArrangement::insertEllipsis (const Font& font, float maxXPos, int startIndex, int endIndex)
|
||||
{
|
||||
int numDeleted = 0;
|
||||
|
||||
if (! glyphs.isEmpty())
|
||||
{
|
||||
Array<int> dotGlyphs;
|
||||
Array<float> dotXs;
|
||||
font.getGlyphPositions ("..", dotGlyphs, dotXs);
|
||||
|
||||
auto dx = dotXs[1];
|
||||
float xOffset = 0.0f, yOffset = 0.0f;
|
||||
|
||||
while (endIndex > startIndex)
|
||||
{
|
||||
auto& pg = glyphs.getReference (--endIndex);
|
||||
xOffset = pg.x;
|
||||
yOffset = pg.y;
|
||||
|
||||
glyphs.remove (endIndex);
|
||||
++numDeleted;
|
||||
|
||||
if (xOffset + dx * 3 <= maxXPos)
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 3; --i >= 0;)
|
||||
{
|
||||
glyphs.insert (endIndex++, PositionedGlyph (font, '.', dotGlyphs.getFirst(),
|
||||
xOffset, yOffset, dx, false));
|
||||
--numDeleted;
|
||||
xOffset += dx;
|
||||
|
||||
if (xOffset > maxXPos)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return numDeleted;
|
||||
addGlyphsFromShapedText (*this, st, xOffset, yOffset);
|
||||
}
|
||||
|
||||
void GlyphArrangement::addJustifiedText (const Font& font, const String& text,
|
||||
|
|
@ -237,88 +200,29 @@ void GlyphArrangement::addJustifiedText (const Font& font, const String& text,
|
|||
Justification horizontalLayout,
|
||||
float leading)
|
||||
{
|
||||
auto lineStartIndex = glyphs.size();
|
||||
addLineOfText (font, text, x, y);
|
||||
ShapedText st { text, ShapedText::Options{}.withMaxWidth (maxLineWidth)
|
||||
.withJustification (horizontalLayout)
|
||||
.withFont (font)
|
||||
.withLeading (1.0f + leading / font.getHeight())
|
||||
.withBaselineAtZero ()
|
||||
.withTrailingWhitespacesShouldFit (false) };
|
||||
|
||||
auto originalY = y;
|
||||
|
||||
while (lineStartIndex < glyphs.size())
|
||||
{
|
||||
int i = lineStartIndex;
|
||||
|
||||
if (glyphs.getReference (i).getCharacter() != '\n'
|
||||
&& glyphs.getReference (i).getCharacter() != '\r')
|
||||
++i;
|
||||
|
||||
auto lineMaxX = glyphs.getReference (lineStartIndex).getLeft() + maxLineWidth;
|
||||
int lastWordBreakIndex = -1;
|
||||
|
||||
while (i < glyphs.size())
|
||||
{
|
||||
auto& pg = glyphs.getReference (i);
|
||||
auto c = pg.getCharacter();
|
||||
|
||||
if (c == '\r' || c == '\n')
|
||||
{
|
||||
++i;
|
||||
|
||||
if (c == '\r' && i < glyphs.size()
|
||||
&& glyphs.getReference (i).getCharacter() == '\n')
|
||||
++i;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (pg.isWhitespace())
|
||||
{
|
||||
lastWordBreakIndex = i + 1;
|
||||
}
|
||||
else if (pg.getRight() - 0.0001f >= lineMaxX)
|
||||
{
|
||||
if (lastWordBreakIndex >= 0)
|
||||
i = lastWordBreakIndex;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
auto currentLineStartX = glyphs.getReference (lineStartIndex).getLeft();
|
||||
auto currentLineEndX = currentLineStartX;
|
||||
|
||||
for (int j = i; --j >= lineStartIndex;)
|
||||
{
|
||||
if (! glyphs.getReference (j).isWhitespace())
|
||||
{
|
||||
currentLineEndX = glyphs.getReference (j).getRight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float deltaX = 0.0f;
|
||||
|
||||
if (horizontalLayout.testFlags (Justification::horizontallyJustified))
|
||||
spreadOutLine (lineStartIndex, i - lineStartIndex, maxLineWidth);
|
||||
else if (horizontalLayout.testFlags (Justification::horizontallyCentred))
|
||||
deltaX = (maxLineWidth - (currentLineEndX - currentLineStartX)) * 0.5f;
|
||||
else if (horizontalLayout.testFlags (Justification::right))
|
||||
deltaX = maxLineWidth - (currentLineEndX - currentLineStartX);
|
||||
|
||||
moveRangeOfGlyphs (lineStartIndex, i - lineStartIndex,
|
||||
x + deltaX - currentLineStartX, y - originalY);
|
||||
|
||||
lineStartIndex = i;
|
||||
|
||||
y += font.getHeight() + leading;
|
||||
}
|
||||
addGlyphsFromShapedText (*this, st, x, y);
|
||||
}
|
||||
|
||||
void GlyphArrangement::addFittedText (const Font& f, const String& text,
|
||||
float x, float y, float width, float height,
|
||||
Justification layout, int maximumLines,
|
||||
void GlyphArrangement::addFittedText (const Font& f,
|
||||
const String& text,
|
||||
float x,
|
||||
float y,
|
||||
float width,
|
||||
float height,
|
||||
Justification layout,
|
||||
int maximumLines,
|
||||
float minimumHorizontalScale)
|
||||
{
|
||||
if (! layout.testFlags (Justification::bottom))
|
||||
layout = layout.getOnlyHorizontalFlags() | Justification::verticallyCentred;
|
||||
|
||||
if (approximatelyEqual (minimumHorizontalScale, 0.0f))
|
||||
minimumHorizontalScale = Font::getDefaultMinimumHorizontalScaleFactor();
|
||||
|
||||
|
|
@ -327,42 +231,194 @@ void GlyphArrangement::addFittedText (const Font& f, const String& text,
|
|||
|
||||
if (text.containsAnyOf ("\r\n"))
|
||||
{
|
||||
addLinesWithLineBreaks (text, f, x, y, width, height, layout);
|
||||
ShapedText st { text,
|
||||
ShapedText::Options{}
|
||||
.withMaxWidth (width)
|
||||
.withHeight (height)
|
||||
.withJustification (layout)
|
||||
.withFont (f)
|
||||
.withTrailingWhitespacesShouldFit (false) };
|
||||
|
||||
addGlyphsFromShapedText (*this, st, x, y);
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
const auto trimmed = text.trim();
|
||||
|
||||
// First attempt: try to squash the entire text on a single line
|
||||
{
|
||||
auto startIndex = glyphs.size();
|
||||
auto trimmed = text.trim();
|
||||
addLineOfText (f, trimmed, x, y);
|
||||
auto numGlyphs = glyphs.size() - startIndex;
|
||||
ShapedText st { trimmed, ShapedText::Options{}.withFont (f)
|
||||
.withMaxWidth (width)
|
||||
.withHeight (height)
|
||||
.withMaxNumLines (1)
|
||||
.withJustification (layout)
|
||||
.withTrailingWhitespacesShouldFit (false) };
|
||||
|
||||
if (numGlyphs > 0)
|
||||
const auto requiredWidths = st.getMinimumRequiredWidthForLines();
|
||||
|
||||
if (requiredWidths.empty() || requiredWidths.front() <= width)
|
||||
{
|
||||
auto lineWidth = glyphs.getReference (glyphs.size() - 1).getRight()
|
||||
- glyphs.getReference (startIndex).getLeft();
|
||||
addGlyphsFromShapedText (*this, st, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
if (lineWidth > 0)
|
||||
{
|
||||
if (lineWidth * minimumHorizontalScale < width)
|
||||
{
|
||||
if (lineWidth > width)
|
||||
stretchRangeOfGlyphs (startIndex, numGlyphs, width / lineWidth);
|
||||
// If we can fit the entire line, squash by just enough and insert
|
||||
if (requiredWidths.front() * minimumHorizontalScale < width)
|
||||
{
|
||||
ShapedText squashed { trimmed,
|
||||
ShapedText::Options{}
|
||||
.withFont (f.withHorizontalScale (width / (requiredWidths.front() + 0.01f)))
|
||||
.withMaxWidth (width)
|
||||
.withHeight (height)
|
||||
.withJustification (layout)
|
||||
.withTrailingWhitespacesShouldFit (false)};
|
||||
|
||||
justifyGlyphs (startIndex, numGlyphs, x, y, width, height, layout);
|
||||
}
|
||||
else if (maximumLines <= 1)
|
||||
{
|
||||
fitLineIntoSpace (startIndex, numGlyphs, x, y, width, height,
|
||||
f, layout, minimumHorizontalScale);
|
||||
}
|
||||
else
|
||||
{
|
||||
splitLines (trimmed, f, startIndex, x, y, width, height,
|
||||
maximumLines, lineWidth, layout, minimumHorizontalScale);
|
||||
}
|
||||
}
|
||||
addGlyphsFromShapedText (*this, squashed, x, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (maximumLines <= 1)
|
||||
{
|
||||
ShapedText squashed { trimmed,
|
||||
ShapedText::Options{}
|
||||
.withFont (f.withHorizontalScale (minimumHorizontalScale))
|
||||
.withMaxWidth (width)
|
||||
.withHeight (height)
|
||||
.withJustification (layout)
|
||||
.withMaxNumLines (1)
|
||||
.withEllipsis() };
|
||||
|
||||
addGlyphsFromShapedText (*this, squashed, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep reshaping the text constantly decreasing the fontsize and increasing the number of lines
|
||||
// until all text fits.
|
||||
|
||||
auto length = trimmed.length();
|
||||
int numLines = 1;
|
||||
|
||||
if (length <= 12 && ! trimmed.containsAnyOf (" -\t\r\n"))
|
||||
maximumLines = 1;
|
||||
|
||||
maximumLines = jmin (maximumLines, length);
|
||||
|
||||
auto font = f;
|
||||
float cumulativeLineLengths{};
|
||||
|
||||
while (numLines < maximumLines)
|
||||
{
|
||||
++numLines;
|
||||
auto newFontHeight = height / (float) numLines;
|
||||
|
||||
if (newFontHeight < font.getHeight())
|
||||
font.setHeight (jmax (8.0f, newFontHeight));
|
||||
|
||||
ShapedText squashed { trimmed,
|
||||
ShapedText::Options{}
|
||||
.withFont (font)
|
||||
.withMaxWidth (width)
|
||||
.withHeight (height)
|
||||
.withMaxNumLines (numLines)
|
||||
.withJustification (layout)
|
||||
.withTrailingWhitespacesShouldFit (false) };
|
||||
|
||||
const auto lineWidths = squashed.getMinimumRequiredWidthForLines();
|
||||
|
||||
if (lineWidths.empty() || lineWidths.back() <= width)
|
||||
{
|
||||
addGlyphsFromShapedText (*this, squashed, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
// We're trying to guess how much horizontal space the text would need to fit, and we
|
||||
// need to have an allowance for line end whitespaces which take up additional room
|
||||
// when not falling at the end of lines.
|
||||
cumulativeLineLengths = std::accumulate (lineWidths.begin(), lineWidths.end(), 0.0f)
|
||||
+ font.getHeight() * (float) numLines * 1.4f;
|
||||
|
||||
if (newFontHeight < 8.0f)
|
||||
break;
|
||||
}
|
||||
|
||||
// At this point we failed to fit the text by just increasing the number of lines and decreasing
|
||||
// the font size. Horizontal squashing is also necessary, for which horizontal justification is
|
||||
// enabled.
|
||||
layout = layout.getOnlyVerticalFlags() | Justification::horizontallyJustified;
|
||||
|
||||
//==============================================================================
|
||||
// We run an iterative interval halving algorithm to find the largest scale that can fit all
|
||||
// text
|
||||
auto makeShapedText = [&] (float horizontalScale)
|
||||
{
|
||||
return ShapedText { trimmed,
|
||||
ShapedText::Options{}
|
||||
.withFont (font.withHorizontalScale (horizontalScale))
|
||||
.withMaxWidth (width)
|
||||
.withHeight (height)
|
||||
.withMaxNumLines (numLines)
|
||||
.withJustification (layout)
|
||||
.withTrailingWhitespacesShouldFit (false)
|
||||
.withEllipsis() };
|
||||
};
|
||||
|
||||
auto lowerScaleBound = minimumHorizontalScale;
|
||||
auto upperScaleBound = std::max (minimumHorizontalScale, ((float) numLines * width) / cumulativeLineLengths);
|
||||
|
||||
const auto isFittingAllText = [width] (auto& shapedText)
|
||||
{
|
||||
// ShapedText guarantees that all lines maybe except the last - when there is a maximum line
|
||||
// limit - will fit the requested width
|
||||
return shapedText.getMinimumRequiredWidthForLines().back() <= width;
|
||||
};
|
||||
|
||||
if (auto st = makeShapedText (upperScaleBound);
|
||||
isFittingAllText (st)
|
||||
|| approximatelyEqual (upperScaleBound, minimumHorizontalScale))
|
||||
{
|
||||
addGlyphsFromShapedText (*this, st, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
struct Candidate
|
||||
{
|
||||
float scale{};
|
||||
ShapedText shapedText;
|
||||
};
|
||||
|
||||
Candidate candidate { minimumHorizontalScale, makeShapedText (minimumHorizontalScale) };
|
||||
|
||||
for (int i = 0, numApproximatingIterations = 3; i < numApproximatingIterations; ++i)
|
||||
{
|
||||
auto scale = (upperScaleBound - lowerScaleBound) / 2.0f;
|
||||
|
||||
if (auto st = makeShapedText (scale);
|
||||
isFittingAllText (st))
|
||||
{
|
||||
lowerScaleBound = std::max (lowerScaleBound, scale);
|
||||
|
||||
if (candidate.scale < scale)
|
||||
{
|
||||
candidate.scale = scale;
|
||||
candidate.shapedText = std::move (st);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
upperScaleBound = std::min (upperScaleBound, scale);
|
||||
}
|
||||
}
|
||||
|
||||
if (approximatelyEqual (candidate.scale, minimumHorizontalScale)
|
||||
|| candidate.shapedText.getMinimumRequiredWidthForLines().back() <= width)
|
||||
{
|
||||
addGlyphsFromShapedText (*this, candidate.shapedText, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
addGlyphsFromShapedText (*this, candidate.shapedText, x, y);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -380,49 +436,6 @@ void GlyphArrangement::moveRangeOfGlyphs (int startIndex, int num, const float d
|
|||
}
|
||||
}
|
||||
|
||||
void GlyphArrangement::addLinesWithLineBreaks (const String& text, const Font& f,
|
||||
float x, float y, float width, float height, Justification layout)
|
||||
{
|
||||
GlyphArrangement ga;
|
||||
ga.addJustifiedText (f, text, x, y, width, layout);
|
||||
|
||||
auto bb = ga.getBoundingBox (0, -1, false);
|
||||
auto dy = y - bb.getY();
|
||||
|
||||
if (layout.testFlags (Justification::verticallyCentred)) dy += (height - bb.getHeight()) * 0.5f;
|
||||
else if (layout.testFlags (Justification::bottom)) dy += (height - bb.getHeight());
|
||||
|
||||
ga.moveRangeOfGlyphs (0, -1, 0.0f, dy);
|
||||
|
||||
glyphs.addArray (ga.glyphs);
|
||||
}
|
||||
|
||||
int GlyphArrangement::fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font& font,
|
||||
Justification justification, float minimumHorizontalScale)
|
||||
{
|
||||
int numDeleted = 0;
|
||||
auto lineStartX = glyphs.getReference (start).getLeft();
|
||||
auto lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX;
|
||||
|
||||
if (lineWidth > w)
|
||||
{
|
||||
if (minimumHorizontalScale < 1.0f)
|
||||
{
|
||||
stretchRangeOfGlyphs (start, numGlyphs, jmax (minimumHorizontalScale, w / lineWidth));
|
||||
lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX - 0.5f;
|
||||
}
|
||||
|
||||
if (lineWidth > w)
|
||||
{
|
||||
numDeleted = insertEllipsis (font, lineStartX + w, start, start + numGlyphs);
|
||||
numGlyphs -= numDeleted;
|
||||
}
|
||||
}
|
||||
|
||||
justifyGlyphs (start, numGlyphs, x, y, w, h, justification);
|
||||
return numDeleted;
|
||||
}
|
||||
|
||||
void GlyphArrangement::stretchRangeOfGlyphs (int startIndex, int num, float horizontalScaleFactor)
|
||||
{
|
||||
jassert (startIndex >= 0);
|
||||
|
|
@ -556,145 +569,6 @@ void GlyphArrangement::spreadOutLine (int start, int num, float targetWidth)
|
|||
}
|
||||
}
|
||||
|
||||
static bool isBreakableGlyph (const PositionedGlyph& g) noexcept
|
||||
{
|
||||
return ! isNonBreakingSpace (g.getCharacter()) && (g.isWhitespace() || g.getCharacter() == '-');
|
||||
}
|
||||
|
||||
void GlyphArrangement::splitLines (const String& text, Font font, int startIndex,
|
||||
float x, float y, float width, float height, int maximumLines,
|
||||
float lineWidth, Justification layout, float minimumHorizontalScale)
|
||||
{
|
||||
auto length = text.length();
|
||||
auto originalStartIndex = startIndex;
|
||||
int numLines = 1;
|
||||
|
||||
if (length <= 12 && ! text.containsAnyOf (" -\t\r\n"))
|
||||
maximumLines = 1;
|
||||
|
||||
maximumLines = jmin (maximumLines, length);
|
||||
|
||||
while (numLines < maximumLines)
|
||||
{
|
||||
++numLines;
|
||||
auto newFontHeight = height / (float) numLines;
|
||||
|
||||
if (newFontHeight < font.getHeight())
|
||||
{
|
||||
font.setHeight (jmax (8.0f, newFontHeight));
|
||||
|
||||
removeRangeOfGlyphs (startIndex, -1);
|
||||
addLineOfText (font, text, x, y);
|
||||
|
||||
lineWidth = glyphs.getReference (glyphs.size() - 1).getRight()
|
||||
- glyphs.getReference (startIndex).getLeft();
|
||||
}
|
||||
|
||||
// Try to estimate the point at which there are enough lines to fit the text,
|
||||
// allowing for unevenness in the lengths due to differently sized words.
|
||||
const float lineLengthUnevennessAllowance = 80.0f;
|
||||
|
||||
if ((float) numLines > (lineWidth + lineLengthUnevennessAllowance) / width || newFontHeight < 8.0f)
|
||||
break;
|
||||
}
|
||||
|
||||
if (numLines < 1)
|
||||
numLines = 1;
|
||||
|
||||
int lineIndex = 0;
|
||||
auto lineY = y;
|
||||
auto widthPerLine = jmin (width / minimumHorizontalScale,
|
||||
lineWidth / (float) numLines);
|
||||
|
||||
while (lineY < y + height)
|
||||
{
|
||||
auto endIndex = startIndex;
|
||||
auto lineStartX = glyphs.getReference (startIndex).getLeft();
|
||||
auto lineBottomY = lineY + font.getHeight();
|
||||
|
||||
if (lineIndex++ >= numLines - 1
|
||||
|| lineBottomY >= y + height)
|
||||
{
|
||||
widthPerLine = width;
|
||||
endIndex = glyphs.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
while (endIndex < glyphs.size())
|
||||
{
|
||||
if (glyphs.getReference (endIndex).getRight() - lineStartX > widthPerLine)
|
||||
{
|
||||
// got to a point where the line's too long, so skip forward to find a
|
||||
// good place to break it..
|
||||
auto searchStartIndex = endIndex;
|
||||
|
||||
while (endIndex < glyphs.size())
|
||||
{
|
||||
auto& g = glyphs.getReference (endIndex);
|
||||
|
||||
if ((g.getRight() - lineStartX) * minimumHorizontalScale < width)
|
||||
{
|
||||
if (isBreakableGlyph (g))
|
||||
{
|
||||
++endIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// can't find a suitable break, so try looking backwards..
|
||||
endIndex = searchStartIndex;
|
||||
|
||||
for (int back = 1; back < jmin (7, endIndex - startIndex - 1); ++back)
|
||||
{
|
||||
if (isBreakableGlyph (glyphs.getReference (endIndex - back)))
|
||||
{
|
||||
endIndex -= back - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
++endIndex;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
++endIndex;
|
||||
}
|
||||
|
||||
auto wsStart = endIndex;
|
||||
auto wsEnd = endIndex;
|
||||
|
||||
while (wsStart > 0 && glyphs.getReference (wsStart - 1).isWhitespace())
|
||||
--wsStart;
|
||||
|
||||
while (wsEnd < glyphs.size() && glyphs.getReference (wsEnd).isWhitespace())
|
||||
++wsEnd;
|
||||
|
||||
removeRangeOfGlyphs (wsStart, wsEnd - wsStart);
|
||||
endIndex = jmax (wsStart, startIndex + 1);
|
||||
}
|
||||
|
||||
endIndex -= fitLineIntoSpace (startIndex, endIndex - startIndex,
|
||||
x, lineY, width, font.getHeight(), font,
|
||||
layout.getOnlyHorizontalFlags() | Justification::verticallyCentred,
|
||||
minimumHorizontalScale);
|
||||
|
||||
startIndex = endIndex;
|
||||
lineY = lineBottomY;
|
||||
|
||||
if (startIndex >= glyphs.size())
|
||||
break;
|
||||
}
|
||||
|
||||
justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex,
|
||||
x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void GlyphArrangement::drawGlyphUnderline (const Graphics& g,
|
||||
int i,
|
||||
|
|
@ -730,6 +604,10 @@ void GlyphArrangement::draw (const Graphics& g, AffineTransform transform) const
|
|||
glyphNumbers.reserve (static_cast<size_t> (glyphs.size()));
|
||||
positions.reserve (static_cast<size_t> (glyphs.size()));
|
||||
|
||||
auto& ctx = g.getInternalContext();
|
||||
ctx.saveState();
|
||||
const ScopeGuard guard { [&ctx] { ctx.restoreState(); } };
|
||||
|
||||
for (auto it = glyphs.begin(), end = glyphs.end(); it != end;)
|
||||
{
|
||||
const auto adjacent = std::adjacent_find (it, end, [] (const auto& a, const auto& b)
|
||||
|
|
@ -751,7 +629,6 @@ void GlyphArrangement::draw (const Graphics& g, AffineTransform transform) const
|
|||
return Point { x.x, x.y };
|
||||
});
|
||||
|
||||
auto& ctx = g.getInternalContext();
|
||||
ctx.setFont (it->font);
|
||||
ctx.drawGlyphs (glyphNumbers, positions, transform);
|
||||
|
||||
|
|
|
|||
|
|
@ -315,13 +315,7 @@ private:
|
|||
//==============================================================================
|
||||
Array<PositionedGlyph> glyphs;
|
||||
|
||||
int insertEllipsis (const Font&, float maxXPos, int startIndex, int endIndex);
|
||||
int fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font&,
|
||||
Justification, float minimumHorizontalScale);
|
||||
void spreadOutLine (int start, int numGlyphs, float targetWidth);
|
||||
void splitLines (const String&, Font, int start, float x, float y, float w, float h, int maxLines,
|
||||
float lineWidth, Justification, float minimumHorizontalScale);
|
||||
void addLinesWithLineBreaks (const String&, const Font&, float x, float y, float width, float height, Justification);
|
||||
void drawGlyphUnderline (const Graphics&, int, AffineTransform) const;
|
||||
|
||||
JUCE_LEAK_DETECTOR (GlyphArrangement)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue