|
|
@@ -48,7 +48,7 @@ TextLayout::Run::Run() noexcept |
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
TextLayout::Run::Run (Range<int> range, const int numGlyphsToPreallocate)
|
|
|
|
TextLayout::Run::Run (Range<int> range, int numGlyphsToPreallocate)
|
|
|
|
: colour (0xff000000), stringRange (range)
|
|
|
|
{
|
|
|
|
glyphs.ensureStorageAllocated (numGlyphsToPreallocate);
|
|
|
@@ -94,31 +94,20 @@ Range<float> TextLayout::Line::getLineBoundsX() const noexcept |
|
|
|
Range<float> range;
|
|
|
|
bool isFirst = true;
|
|
|
|
|
|
|
|
for (int i = runs.size(); --i >= 0;)
|
|
|
|
for (auto* run : runs)
|
|
|
|
{
|
|
|
|
const Run& run = *runs.getUnchecked(i);
|
|
|
|
|
|
|
|
if (run.glyphs.size() > 0)
|
|
|
|
for (auto& glyph : run->glyphs)
|
|
|
|
{
|
|
|
|
float minX = run.glyphs.getReference(0).anchor.x;
|
|
|
|
float maxX = minX;
|
|
|
|
|
|
|
|
for (int j = run.glyphs.size(); --j >= 0;)
|
|
|
|
{
|
|
|
|
const Glyph& glyph = run.glyphs.getReference (j);
|
|
|
|
const float x = glyph.anchor.x;
|
|
|
|
minX = jmin (minX, x);
|
|
|
|
maxX = jmax (maxX, x + glyph.width);
|
|
|
|
}
|
|
|
|
Range<float> runRange (glyph.anchor.x, glyph.anchor.x + glyph.width);
|
|
|
|
|
|
|
|
if (isFirst)
|
|
|
|
{
|
|
|
|
isFirst = false;
|
|
|
|
range = Range<float> (minX, maxX);
|
|
|
|
range = runRange;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
range = range.getUnionWith (Range<float> (minX, maxX));
|
|
|
|
range = range.getUnionWith (runRange);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@@ -134,10 +123,10 @@ Range<float> TextLayout::Line::getLineBoundsY() const noexcept |
|
|
|
|
|
|
|
Rectangle<float> TextLayout::Line::getLineBounds() const noexcept
|
|
|
|
{
|
|
|
|
const Range<float> x (getLineBoundsX()),
|
|
|
|
y (getLineBoundsY());
|
|
|
|
auto x = getLineBoundsX();
|
|
|
|
auto y = getLineBoundsY();
|
|
|
|
|
|
|
|
return Rectangle<float> (x.getStart(), y.getStart(), x.getLength(), y.getLength());
|
|
|
|
return { x.getStart(), y.getStart(), x.getLength(), y.getLength() };
|
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
@@ -198,28 +187,40 @@ void TextLayout::addLine (Line* line) |
|
|
|
lines.add (line);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TextLayout::draw (Graphics& g, const Rectangle<float>& area) const
|
|
|
|
void TextLayout::draw (Graphics& g, Rectangle<float> area) const
|
|
|
|
{
|
|
|
|
const Point<float> origin (justification.appliedToRectangle (Rectangle<float> (width, getHeight()), area).getPosition());
|
|
|
|
auto origin = justification.appliedToRectangle (Rectangle<float> (width, getHeight()), area).getPosition();
|
|
|
|
|
|
|
|
LowLevelGraphicsContext& context = g.getInternalContext();
|
|
|
|
auto& context = g.getInternalContext();
|
|
|
|
|
|
|
|
for (int i = 0; i < lines.size(); ++i)
|
|
|
|
for (auto* line : lines)
|
|
|
|
{
|
|
|
|
const Line& line = getLine (i);
|
|
|
|
const Point<float> lineOrigin (origin + line.lineOrigin);
|
|
|
|
auto lineOrigin = origin + line->lineOrigin;
|
|
|
|
|
|
|
|
for (int j = 0; j < line.runs.size(); ++j)
|
|
|
|
for (auto* run : line->runs)
|
|
|
|
{
|
|
|
|
const Run& run = *line.runs.getUnchecked (j);
|
|
|
|
context.setFont (run.font);
|
|
|
|
context.setFill (run.colour);
|
|
|
|
context.setFont (run->font);
|
|
|
|
context.setFill (run->colour);
|
|
|
|
|
|
|
|
for (int k = 0; k < run.glyphs.size(); ++k)
|
|
|
|
{
|
|
|
|
const Glyph& glyph = run.glyphs.getReference (k);
|
|
|
|
for (auto& glyph : run->glyphs)
|
|
|
|
context.drawGlyph (glyph.glyphCode, AffineTransform::translation (lineOrigin.x + glyph.anchor.x,
|
|
|
|
lineOrigin.y + glyph.anchor.y));
|
|
|
|
|
|
|
|
if (run->font.isUnderlined())
|
|
|
|
{
|
|
|
|
Range<float> runExtent;
|
|
|
|
|
|
|
|
for (auto& glyph : run->glyphs)
|
|
|
|
{
|
|
|
|
Range<float> glyphRange (glyph.anchor.x, glyph.anchor.x + glyph.width);
|
|
|
|
|
|
|
|
runExtent = runExtent.isEmpty() ? glyphRange
|
|
|
|
: runExtent.getUnionWith (glyphRange);
|
|
|
|
}
|
|
|
|
|
|
|
|
const float lineThickness = run->font.getDescent() * 0.3f;
|
|
|
|
context.fillRect ({ runExtent.getStart() + lineOrigin.x, lineOrigin.y + lineThickness * 2.0f,
|
|
|
|
runExtent.getLength(), lineThickness });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@@ -287,7 +288,7 @@ namespace TextLayoutHelpers |
|
|
|
{
|
|
|
|
struct Token
|
|
|
|
{
|
|
|
|
Token (const String& t, const Font& f, Colour c, const bool whitespace)
|
|
|
|
Token (const String& t, const Font& f, Colour c, bool whitespace)
|
|
|
|
: text (t), font (f), colour (c),
|
|
|
|
area (font.getStringWidthFloat (t), f.getHeight()),
|
|
|
|
isWhitespace (whitespace),
|
|
|
@@ -308,7 +309,7 @@ namespace TextLayoutHelpers |
|
|
|
|
|
|
|
struct TokenList
|
|
|
|
{
|
|
|
|
TokenList() noexcept : totalLines (0) {}
|
|
|
|
TokenList() noexcept {}
|
|
|
|
|
|
|
|
void createLayout (const AttributedString& text, TextLayout& layout)
|
|
|
|
{
|
|
|
@@ -328,7 +329,7 @@ namespace TextLayoutHelpers |
|
|
|
|
|
|
|
for (int i = 0; i < tokens.size(); ++i)
|
|
|
|
{
|
|
|
|
const Token& t = *tokens.getUnchecked (i);
|
|
|
|
auto& t = *tokens.getUnchecked (i);
|
|
|
|
|
|
|
|
Array<int> newGlyphs;
|
|
|
|
Array<float> xOffsets;
|
|
|
@@ -364,19 +365,7 @@ namespace TextLayoutHelpers |
|
|
|
if (t.isWhitespace || t.isNewLine)
|
|
|
|
++charPosition;
|
|
|
|
|
|
|
|
const Token* const nextToken = tokens [i + 1];
|
|
|
|
|
|
|
|
if (nextToken == nullptr) // this is the last token
|
|
|
|
{
|
|
|
|
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
|
|
|
|
currentLine->stringRange = Range<int> (lineStartPosition, charPosition);
|
|
|
|
|
|
|
|
if (! needToSetLineOrigin)
|
|
|
|
layout.addLine (currentLine.release());
|
|
|
|
|
|
|
|
needToSetLineOrigin = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if (auto* nextToken = tokens [i + 1])
|
|
|
|
{
|
|
|
|
if (t.font != nextToken->font || t.colour != nextToken->colour)
|
|
|
|
{
|
|
|
@@ -400,6 +389,16 @@ namespace TextLayoutHelpers |
|
|
|
needToSetLineOrigin = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
|
|
|
|
currentLine->stringRange = Range<int> (lineStartPosition, charPosition);
|
|
|
|
|
|
|
|
if (! needToSetLineOrigin)
|
|
|
|
layout.addLine (currentLine.release());
|
|
|
|
|
|
|
|
needToSetLineOrigin = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((text.getJustification().getFlags() & (Justification::right | Justification::horizontallyCentred)) != 0)
|
|
|
@@ -423,7 +422,7 @@ namespace TextLayoutHelpers |
|
|
|
static void addRun (TextLayout::Line& glyphLine, TextLayout::Run* glyphRun,
|
|
|
|
const Token& t, const int start, const int end)
|
|
|
|
{
|
|
|
|
glyphRun->stringRange = Range<int> (start, end);
|
|
|
|
glyphRun->stringRange = { start, end };
|
|
|
|
glyphRun->font = t.font;
|
|
|
|
glyphRun->colour = t.colour;
|
|
|
|
glyphLine.ascent = jmax (glyphLine.ascent, t.font.getAscent());
|
|
|
@@ -441,7 +440,7 @@ namespace TextLayoutHelpers |
|
|
|
|
|
|
|
void appendText (const String& stringText, const Font& font, Colour colour)
|
|
|
|
{
|
|
|
|
String::CharPointerType t (stringText.getCharPointer());
|
|
|
|
auto t = stringText.getCharPointer();
|
|
|
|
String currentString;
|
|
|
|
int lastCharType = 0;
|
|
|
|
|
|
|
@@ -483,13 +482,13 @@ namespace TextLayoutHelpers |
|
|
|
|
|
|
|
for (i = 0; i < tokens.size(); ++i)
|
|
|
|
{
|
|
|
|
Token& t = *tokens.getUnchecked(i);
|
|
|
|
auto& t = *tokens.getUnchecked(i);
|
|
|
|
t.area.setPosition (x, y);
|
|
|
|
t.line = totalLines;
|
|
|
|
x += t.area.getWidth();
|
|
|
|
h = jmax (h, t.area.getHeight() + extraLineSpacing);
|
|
|
|
|
|
|
|
const Token* const nextTok = tokens[i + 1];
|
|
|
|
auto* nextTok = tokens[i + 1];
|
|
|
|
|
|
|
|
if (nextTok == nullptr)
|
|
|
|
break;
|
|
|
@@ -514,7 +513,7 @@ namespace TextLayoutHelpers |
|
|
|
{
|
|
|
|
while (--i >= 0)
|
|
|
|
{
|
|
|
|
Token& tok = *tokens.getUnchecked (i);
|
|
|
|
auto& tok = *tokens.getUnchecked (i);
|
|
|
|
|
|
|
|
if (tok.line == totalLines)
|
|
|
|
tok.lineHeight = height;
|
|
|
@@ -530,7 +529,7 @@ namespace TextLayoutHelpers |
|
|
|
|
|
|
|
for (int i = 0; i < numAttributes; ++i)
|
|
|
|
{
|
|
|
|
const AttributedString::Attribute& attr = text.getAttribute (i);
|
|
|
|
auto& attr = text.getAttribute (i);
|
|
|
|
|
|
|
|
appendText (text.getText().substring (attr.range.getStart(), attr.range.getEnd()),
|
|
|
|
attr.font, attr.colour);
|
|
|
@@ -539,7 +538,8 @@ namespace TextLayoutHelpers |
|
|
|
|
|
|
|
static String getTrimmedEndIfNotAllWhitespace (const String& s)
|
|
|
|
{
|
|
|
|
String trimmed (s.trimEnd());
|
|
|
|
auto trimmed = s.trimEnd();
|
|
|
|
|
|
|
|
if (trimmed.isEmpty() && s.isNotEmpty())
|
|
|
|
trimmed = s.replaceCharacters ("\r\n\t", " ");
|
|
|
|
|
|
|
@@ -547,7 +547,7 @@ namespace TextLayoutHelpers |
|
|
|
}
|
|
|
|
|
|
|
|
OwnedArray<Token> tokens;
|
|
|
|
int totalLines;
|
|
|
|
int totalLines = 0;
|
|
|
|
|
|
|
|
JUCE_DECLARE_NON_COPYABLE (TokenList)
|
|
|
|
};
|
|
|
@@ -562,15 +562,15 @@ void TextLayout::createStandardLayout (const AttributedString& text) |
|
|
|
|
|
|
|
void TextLayout::recalculateSize()
|
|
|
|
{
|
|
|
|
if (lines.size() > 0)
|
|
|
|
if (! lines.isEmpty())
|
|
|
|
{
|
|
|
|
Rectangle<float> bounds (lines.getFirst()->getLineBounds());
|
|
|
|
auto bounds = lines.getFirst()->getLineBounds();
|
|
|
|
|
|
|
|
for (int i = lines.size(); --i > 0;)
|
|
|
|
bounds = bounds.getUnion (lines.getUnchecked(i)->getLineBounds());
|
|
|
|
for (auto* line : lines)
|
|
|
|
bounds = bounds.getUnion (line->getLineBounds());
|
|
|
|
|
|
|
|
for (int i = lines.size(); --i >= 0;)
|
|
|
|
lines.getUnchecked(i)->lineOrigin.x -= bounds.getX();
|
|
|
|
for (auto* line : lines)
|
|
|
|
line->lineOrigin.x -= bounds.getX();
|
|
|
|
|
|
|
|
width = bounds.getWidth();
|
|
|
|
height = bounds.getHeight();
|
|
|
|