mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
TextLayout: Fixed some bugs setting stringRanges
This commit is contained in:
parent
76f3aec386
commit
edf99d171f
4 changed files with 170 additions and 31 deletions
|
|
@ -27,6 +27,11 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
static String substring (const String& text, Range<int> range)
|
||||
{
|
||||
return text.substring (range.getStart(), range.getEnd());
|
||||
}
|
||||
|
||||
TextLayout::Glyph::Glyph (int glyph, Point<float> anch, float w) noexcept
|
||||
: glyphCode (glyph), anchor (anch), width (w)
|
||||
{
|
||||
|
|
@ -207,9 +212,9 @@ void TextLayout::ensureStorageAllocated (int numLinesNeeded)
|
|||
lines.ensureStorageAllocated (numLinesNeeded);
|
||||
}
|
||||
|
||||
void TextLayout::addLine (Line* line)
|
||||
void TextLayout::addLine (std::unique_ptr<Line> line)
|
||||
{
|
||||
lines.add (line);
|
||||
lines.add (line.release());
|
||||
}
|
||||
|
||||
void TextLayout::draw (Graphics& g, Rectangle<float> area) const
|
||||
|
|
@ -221,9 +226,9 @@ void TextLayout::draw (Graphics& g, Rectangle<float> area) const
|
|||
auto clipTop = clip.getY() - origin.y;
|
||||
auto clipBottom = clip.getBottom() - origin.y;
|
||||
|
||||
for (auto* line : lines)
|
||||
for (auto& line : *this)
|
||||
{
|
||||
auto lineRangeY = line->getLineBoundsY();
|
||||
auto lineRangeY = line.getLineBoundsY();
|
||||
|
||||
if (lineRangeY.getEnd() < clipTop)
|
||||
continue;
|
||||
|
|
@ -231,9 +236,9 @@ void TextLayout::draw (Graphics& g, Rectangle<float> area) const
|
|||
if (lineRangeY.getStart() > clipBottom)
|
||||
break;
|
||||
|
||||
auto lineOrigin = origin + line->lineOrigin;
|
||||
auto lineOrigin = origin + line.lineOrigin;
|
||||
|
||||
for (auto* run : line->runs)
|
||||
for (auto* run : line.runs)
|
||||
{
|
||||
context.setFont (run->font);
|
||||
context.setFill (run->colour);
|
||||
|
|
@ -363,8 +368,8 @@ namespace TextLayoutHelpers
|
|||
Array<float> xOffsets;
|
||||
t.font.getGlyphPositions (getTrimmedEndIfNotAllWhitespace (t.text), newGlyphs, xOffsets);
|
||||
|
||||
if (currentRun == nullptr) currentRun .reset (new TextLayout::Run());
|
||||
if (currentLine == nullptr) currentLine.reset (new TextLayout::Line());
|
||||
if (currentRun == nullptr) currentRun = std::make_unique<TextLayout::Run>();
|
||||
if (currentLine == nullptr) currentLine = std::make_unique<TextLayout::Line>();
|
||||
|
||||
if (newGlyphs.size() > 0)
|
||||
{
|
||||
|
|
@ -389,9 +394,10 @@ namespace TextLayoutHelpers
|
|||
|
||||
charPosition += newGlyphs.size();
|
||||
}
|
||||
|
||||
if (t.isWhitespace || t.isNewLine)
|
||||
else if (t.isWhitespace || t.isNewLine)
|
||||
{
|
||||
++charPosition;
|
||||
}
|
||||
|
||||
if (auto* nextToken = tokens[i + 1])
|
||||
{
|
||||
|
|
@ -404,13 +410,13 @@ namespace TextLayoutHelpers
|
|||
if (t.line != nextToken->line)
|
||||
{
|
||||
if (currentRun == nullptr)
|
||||
currentRun.reset (new TextLayout::Run());
|
||||
currentRun = std::make_unique<TextLayout::Run>();
|
||||
|
||||
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
|
||||
currentLine->stringRange = { lineStartPosition, charPosition };
|
||||
|
||||
if (! needToSetLineOrigin)
|
||||
layout.addLine (currentLine.release());
|
||||
layout.addLine (std::move (currentLine));
|
||||
|
||||
runStartPosition = charPosition;
|
||||
lineStartPosition = charPosition;
|
||||
|
|
@ -423,7 +429,7 @@ namespace TextLayoutHelpers
|
|||
currentLine->stringRange = { lineStartPosition, charPosition };
|
||||
|
||||
if (! needToSetLineOrigin)
|
||||
layout.addLine (currentLine.release());
|
||||
layout.addLine (std::move (currentLine));
|
||||
|
||||
needToSetLineOrigin = true;
|
||||
}
|
||||
|
|
@ -434,14 +440,14 @@ namespace TextLayoutHelpers
|
|||
auto totalW = layout.getWidth();
|
||||
bool isCentred = (text.getJustification().getFlags() & Justification::horizontallyCentred) != 0;
|
||||
|
||||
for (int i = 0; i < layout.getNumLines(); ++i)
|
||||
for (auto& line : layout)
|
||||
{
|
||||
auto dx = totalW - layout.getLine(i).getLineBoundsX().getLength();
|
||||
auto dx = totalW - line.getLineBoundsX().getLength();
|
||||
|
||||
if (isCentred)
|
||||
dx /= 2.0f;
|
||||
|
||||
layout.getLine(i).lineOrigin.x += dx;
|
||||
line.lineOrigin.x += dx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -560,7 +566,7 @@ namespace TextLayoutHelpers
|
|||
{
|
||||
auto& attr = text.getAttribute (i);
|
||||
|
||||
appendText (text.getText().substring (attr.range.getStart(), attr.range.getEnd()),
|
||||
appendText (substring (text.getText(), attr.range),
|
||||
attr.font, attr.colour);
|
||||
}
|
||||
}
|
||||
|
|
@ -611,4 +617,66 @@ void TextLayout::recalculateSize()
|
|||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct TextLayoutTests : public UnitTest
|
||||
{
|
||||
TextLayoutTests()
|
||||
: UnitTest ("Text Layout", UnitTestCategories::text)
|
||||
{}
|
||||
|
||||
static TextLayout createLayout (StringRef text, float width)
|
||||
{
|
||||
Font fontToUse (12.0f);
|
||||
|
||||
AttributedString string (text);
|
||||
string.setFont (std::move (fontToUse));
|
||||
|
||||
TextLayout layout;
|
||||
layout.createLayout (std::move (string), width);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
void testLineBreaks (const String& line, float width, const StringArray& expected)
|
||||
{
|
||||
const auto layout = createLayout (line, width);
|
||||
|
||||
beginTest ("A line is split into expected pieces");
|
||||
{
|
||||
expectEquals (layout.getNumLines(), expected.size());
|
||||
|
||||
const auto limit = jmin (layout.getNumLines(), expected.size());
|
||||
|
||||
for (int i = 0; i != limit; ++i)
|
||||
expectEquals (substring (line, layout.getLine (i).stringRange), expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
const String shortLine ("hello world");
|
||||
testLineBreaks (shortLine, 1.0e7f, { shortLine });
|
||||
|
||||
testLineBreaks ("this line should be split",
|
||||
60.0f,
|
||||
{ "this line ",
|
||||
"should be ",
|
||||
"split" });
|
||||
|
||||
testLineBreaks ("these\nlines \nhave\n weird \n spacing ",
|
||||
80.0f,
|
||||
{ "these\n",
|
||||
"lines \n",
|
||||
"have\n",
|
||||
" weird \n",
|
||||
" spacing " });
|
||||
}
|
||||
};
|
||||
|
||||
static TextLayoutTests textLayoutTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -40,6 +40,59 @@ namespace juce
|
|||
*/
|
||||
class JUCE_API TextLayout final
|
||||
{
|
||||
private:
|
||||
template <typename Iterator>
|
||||
class DereferencingIterator
|
||||
{
|
||||
public:
|
||||
using value_type = typename std::remove_reference<decltype(**std::declval<Iterator>())>::type;
|
||||
using difference_type = typename std::iterator_traits<Iterator>::difference_type;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
using iterator_category = typename std::iterator_traits<Iterator>::iterator_category;
|
||||
|
||||
explicit DereferencingIterator (Iterator in) : iterator (std::move (in)) {}
|
||||
|
||||
DereferencingIterator& operator+= (difference_type distance)
|
||||
{
|
||||
iterator += distance;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend DereferencingIterator operator+ (DereferencingIterator i, difference_type d) { return i += d; }
|
||||
friend DereferencingIterator operator+ (difference_type d, DereferencingIterator i) { return i += d; }
|
||||
|
||||
DereferencingIterator& operator-= (difference_type distance)
|
||||
{
|
||||
iterator -= distance;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend DereferencingIterator operator- (DereferencingIterator i, difference_type d) { return i -= d; }
|
||||
|
||||
friend difference_type operator- (DereferencingIterator a, DereferencingIterator b) { return a.iterator - b.iterator; }
|
||||
|
||||
reference operator[] (difference_type d) const { return *iterator[d]; }
|
||||
|
||||
friend bool operator< (DereferencingIterator a, DereferencingIterator b) { return a.iterator < b.iterator; }
|
||||
friend bool operator<= (DereferencingIterator a, DereferencingIterator b) { return a.iterator <= b.iterator; }
|
||||
friend bool operator> (DereferencingIterator a, DereferencingIterator b) { return a.iterator > b.iterator; }
|
||||
friend bool operator>= (DereferencingIterator a, DereferencingIterator b) { return a.iterator >= b.iterator; }
|
||||
friend bool operator== (DereferencingIterator a, DereferencingIterator b) { return a.iterator == b.iterator; }
|
||||
friend bool operator!= (DereferencingIterator a, DereferencingIterator b) { return a.iterator != b.iterator; }
|
||||
|
||||
DereferencingIterator& operator++() { ++iterator; return *this; }
|
||||
DereferencingIterator& operator--() { --iterator; return *this; }
|
||||
DereferencingIterator operator++ (int) const { DereferencingIterator copy (*this); ++(*this); return copy; }
|
||||
DereferencingIterator operator-- (int) const { DereferencingIterator copy (*this); --(*this); return copy; }
|
||||
|
||||
reference operator* () const { return **iterator; }
|
||||
pointer operator->() const { return *iterator; }
|
||||
|
||||
private:
|
||||
Iterator iterator;
|
||||
};
|
||||
|
||||
public:
|
||||
/** Creates an empty layout.
|
||||
Having created a TextLayout, you can populate it using createLayout() or
|
||||
|
|
@ -180,11 +233,29 @@ public:
|
|||
|
||||
/** Adds a line to the layout. The layout will take ownership of this line object
|
||||
and will delete it when it is no longer needed. */
|
||||
void addLine (Line*);
|
||||
void addLine (std::unique_ptr<Line>);
|
||||
|
||||
/** Pre-allocates space for the specified number of lines. */
|
||||
void ensureStorageAllocated (int numLinesNeeded);
|
||||
|
||||
using iterator = DereferencingIterator< Line* const*>;
|
||||
using const_iterator = DereferencingIterator<const Line* const*>;
|
||||
|
||||
/** Returns an iterator over the lines of content */
|
||||
iterator begin() { return iterator (lines.begin()); }
|
||||
const_iterator begin() const { return const_iterator (lines.begin()); }
|
||||
const_iterator cbegin() const { return const_iterator (lines.begin()); }
|
||||
|
||||
/** Returns an iterator over the lines of content */
|
||||
iterator end() { return iterator (lines.end()); }
|
||||
const_iterator end() const { return const_iterator (lines.end()); }
|
||||
const_iterator cend() const { return const_iterator (lines.end()); }
|
||||
|
||||
/** If you modify the TextLayout after creating it, call this to compute
|
||||
the new dimensions of the content.
|
||||
*/
|
||||
void recalculateSize();
|
||||
|
||||
private:
|
||||
OwnedArray<Line> lines;
|
||||
float width, height;
|
||||
|
|
@ -192,7 +263,6 @@ private:
|
|||
|
||||
void createStandardLayout (const AttributedString&);
|
||||
bool createNativeLayout (const AttributedString&);
|
||||
void recalculateSize();
|
||||
|
||||
JUCE_LEAK_DETECTOR (TextLayout)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -387,19 +387,18 @@ namespace CoreTextTypeLayout
|
|||
auto numRuns = CFArrayGetCount (runs);
|
||||
|
||||
auto cfrlineStringRange = CTLineGetStringRange (line);
|
||||
auto lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length - 1;
|
||||
auto lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length;
|
||||
Range<int> lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd);
|
||||
|
||||
LineInfo lineInfo (frame, line, i);
|
||||
|
||||
auto glyphLine = new TextLayout::Line (lineStringRange,
|
||||
Point<float> ((float) lineInfo.origin.x,
|
||||
(float) (boundsHeight - lineInfo.origin.y)),
|
||||
(float) lineInfo.ascent,
|
||||
(float) lineInfo.descent,
|
||||
(float) lineInfo.leading,
|
||||
(int) numRuns);
|
||||
glyphLayout.addLine (glyphLine);
|
||||
auto glyphLine = std::make_unique<TextLayout::Line> (lineStringRange,
|
||||
Point<float> ((float) lineInfo.origin.x,
|
||||
(float) (boundsHeight - lineInfo.origin.y)),
|
||||
(float) lineInfo.ascent,
|
||||
(float) lineInfo.descent,
|
||||
(float) lineInfo.leading,
|
||||
(int) numRuns);
|
||||
|
||||
for (CFIndex j = 0; j < numRuns; ++j)
|
||||
{
|
||||
|
|
@ -457,6 +456,8 @@ namespace CoreTextTypeLayout
|
|||
(float) positions.points[k].y),
|
||||
(float) advances.advances[k].width));
|
||||
}
|
||||
|
||||
glyphLayout.addLine (std::move (glyphLine));
|
||||
}
|
||||
|
||||
CFRelease (frame);
|
||||
|
|
|
|||
|
|
@ -100,10 +100,10 @@ namespace DirectWriteTypeLayout
|
|||
if (currentLine >= layout->getNumLines())
|
||||
{
|
||||
jassert (currentLine == layout->getNumLines());
|
||||
auto line = new TextLayout::Line();
|
||||
layout->addLine (line);
|
||||
|
||||
auto line = std::make_unique<TextLayout::Line>();
|
||||
line->lineOrigin = Point<float> (baselineOriginX, baselineOriginY);
|
||||
|
||||
layout->addLine (std::move (line));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue