mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Ranges: ShapedText: Use out params for improving TextEditor performance
This commit is contained in:
parent
a4f73a37af
commit
bc093fa64c
10 changed files with 494 additions and 335 deletions
|
|
@ -44,7 +44,7 @@ static int64 getNumLeadingWhitespaces (Span<const ShapedGlyph> glyphs)
|
|||
{
|
||||
const auto it = std::find_if_not (glyphs.begin(),
|
||||
glyphs.end(),
|
||||
[&] (const auto& g) { return g.whitespace; });
|
||||
[&] (const auto& g) { return g.isWhitespace(); });
|
||||
|
||||
return (int64) std::distance (glyphs.begin(), it);
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ static int64 getNumTrailingWhitespaces (Span<const ShapedGlyph> glyphs)
|
|||
|
||||
int64 trailingWhitespaces = 0;
|
||||
|
||||
for (auto it = glyphs.end(); --it >= glyphs.begin() && it->whitespace;)
|
||||
for (auto it = glyphs.end(); --it >= glyphs.begin() && it->isWhitespace();)
|
||||
++trailingWhitespaces;
|
||||
|
||||
return trailingWhitespaces;
|
||||
|
|
@ -71,7 +71,7 @@ static NumWhitespaces getNumWhitespaces (Span<const ShapedGlyph> glyphs)
|
|||
{
|
||||
const auto total = std::count_if (glyphs.begin(),
|
||||
glyphs.end(),
|
||||
[] (const auto& g) { return g.whitespace; });
|
||||
[] (const auto& g) { return g.isWhitespace(); });
|
||||
|
||||
return { total, getNumLeadingWhitespaces (glyphs), getNumTrailingWhitespaces (glyphs) };
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@ static LineLength getMainAxisLineLength (Span<const ShapedGlyph> glyphs)
|
|||
if (glyphs.empty())
|
||||
return {};
|
||||
|
||||
for (auto it = glyphs.end(); --it >= glyphs.begin() && it->whitespace;)
|
||||
for (auto it = glyphs.end(); --it >= glyphs.begin() && it->isWhitespace();)
|
||||
trailingWhitespacesLength += it->advance.getX();
|
||||
|
||||
return { total, total - trailingWhitespacesLength };
|
||||
|
|
@ -108,7 +108,7 @@ static float getMainAxisLineLength (Span<const ShapedGlyph> glyphs, bool trailin
|
|||
|
||||
struct MainAxisLineAlignment
|
||||
{
|
||||
float anchor{}, extraWhitespaceAdvance{};
|
||||
float anchor{}, extraWhitespaceAdvance{}, effectiveLineLength;
|
||||
Range<int64> stretchableWhitespaces;
|
||||
};
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ static MainAxisLineAlignment getMainAxisLineAlignment (Justification justificati
|
|||
: 0.0f;
|
||||
}();
|
||||
|
||||
return { mainAxisLineOffset, extraWhitespaceAdvance, stretchableWhitespaces };
|
||||
return { mainAxisLineOffset, extraWhitespaceAdvance, effectiveLineLength, stretchableWhitespaces };
|
||||
}
|
||||
|
||||
struct LineInfo
|
||||
|
|
@ -276,6 +276,10 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
options.getHeight(),
|
||||
leading);
|
||||
|
||||
detail::Ranges::Operations ops;
|
||||
|
||||
std::optional<float> top;
|
||||
|
||||
for (const auto [lineIndex, lineInfo] : enumerate (lineInfos))
|
||||
{
|
||||
const auto lineNumber = shapedText.getLineNumbers().getItem ((size_t) lineIndex);
|
||||
|
|
@ -284,24 +288,35 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
const auto maxDescent = lineInfo.lineHeight - lineInfo.maxAscent;
|
||||
const auto nextLineTop = baseline + (1.0f + leading) * maxDescent + options.getAdditiveLineSpacing();
|
||||
|
||||
linesMetrics.set<detail::MergeEqualItems::no> (range,
|
||||
{ lineNumber.value,
|
||||
{ lineInfo.mainAxisLineAlignment.anchor, baseline },
|
||||
lineInfo.maxAscent,
|
||||
lineInfo.lineHeight - lineInfo.maxAscent,
|
||||
nextLineTop });
|
||||
if (! top.has_value())
|
||||
top = baseline - (1.0f + leading) * lineInfo.maxAscent;
|
||||
|
||||
whitespaceStretch.set (range, 0.0f);
|
||||
linesMetrics.set (range,
|
||||
{ lineNumber.value,
|
||||
{ lineInfo.mainAxisLineAlignment.anchor, baseline },
|
||||
lineInfo.maxAscent,
|
||||
lineInfo.lineHeight - lineInfo.maxAscent,
|
||||
lineInfo.mainAxisLineAlignment.effectiveLineLength + lineInfo.mainAxisLineAlignment.extraWhitespaceAdvance,
|
||||
*top,
|
||||
nextLineTop },
|
||||
ops,
|
||||
MergeEqualItemsNo{});
|
||||
|
||||
whitespaceStretch.set (range, 0.0f, ops);
|
||||
const auto stretchRange = lineInfo.mainAxisLineAlignment.stretchableWhitespaces + range.getStart();
|
||||
|
||||
whitespaceStretch.set (stretchRange,
|
||||
lineInfo.mainAxisLineAlignment.extraWhitespaceAdvance);
|
||||
lineInfo.mainAxisLineAlignment.extraWhitespaceAdvance,
|
||||
ops);
|
||||
|
||||
ops.clear();
|
||||
|
||||
const auto nextLineMaxAscent = lineIndex < (int) lineInfos.size() - 1 ? lineInfos[(size_t) lineIndex + 1].maxAscent : 0.0f;
|
||||
baseline = nextLineTop + (1.0f + leading) * nextLineMaxAscent;
|
||||
top = nextLineTop;
|
||||
}
|
||||
|
||||
rangesToDraw.set ({ 0, (int64) shapedText.getGlyphs().size() }, DrawType::normal);
|
||||
rangesToDraw.set ({ 0, (int64) shapedText.getGlyphs().size() }, DrawType::normal, ops);
|
||||
|
||||
//==============================================================================
|
||||
// Everything above this line should work well given none of the lines were too
|
||||
|
|
@ -372,11 +387,13 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
|
||||
const auto eraseLastLineFromRangesToDraw = [&]
|
||||
{
|
||||
rangesToDraw.eraseFrom (lastLineGlyphRange.getStart());
|
||||
rangesToDraw.eraseFrom (lastLineGlyphRange.getStart(), ops);
|
||||
ops.clear();
|
||||
};
|
||||
|
||||
eraseLastLineFromRangesToDraw();
|
||||
rangesToDraw.set (lastLineVisibleRangeWithoutEllipsis, DrawType::normal);
|
||||
rangesToDraw.set (lastLineVisibleRangeWithoutEllipsis, DrawType::normal, ops);
|
||||
ops.clear();
|
||||
|
||||
if (options.getEllipsis().isEmpty())
|
||||
{
|
||||
|
|
@ -401,12 +418,15 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
options.getTrailingWhitespacesShouldFit()));
|
||||
|
||||
eraseLastLineFromRangesToDraw();
|
||||
rangesToDraw.set (lastLineVisibleRange, DrawType::normal);
|
||||
rangesToDraw.set (lastLineVisibleRange, DrawType::normal, ops);
|
||||
ops.clear();
|
||||
|
||||
if (cutoffAtFront)
|
||||
rangesToDraw.set (Range<int64>::withStartAndLength (lastLineVisibleRange.getStart() - 1, 1), DrawType::ellipsis);
|
||||
rangesToDraw.set (Range<int64>::withStartAndLength (lastLineVisibleRange.getStart() - 1, 1), DrawType::ellipsis, ops);
|
||||
else
|
||||
rangesToDraw.set (Range<int64>::withStartAndLength (lastLineVisibleRange.getEnd(), 1), DrawType::ellipsis);
|
||||
rangesToDraw.set (Range<int64>::withStartAndLength (lastLineVisibleRange.getEnd(), 1), DrawType::ellipsis, ops);
|
||||
|
||||
ops.clear();
|
||||
|
||||
const auto lineWithEllipsisGlyphs = [&]
|
||||
{
|
||||
|
|
@ -444,9 +464,9 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
}();
|
||||
|
||||
lastLineMetrics.value.anchor.setX (realign.anchor);
|
||||
whitespaceStretch.set (lastLineGlyphRange, 0.0f);
|
||||
whitespaceStretch.set (lastLineGlyphRange, 0.0f, ops);
|
||||
whitespaceStretch.set (realign.stretchableWhitespaces + lastLineVisibleRange.getStart(),
|
||||
realign.extraWhitespaceAdvance);
|
||||
realign.extraWhitespaceAdvance, ops);
|
||||
}
|
||||
|
||||
int64 JustifiedText::getGlyphIndexAt (Point<float> p) const
|
||||
|
|
@ -476,8 +496,8 @@ int64 JustifiedText::getGlyphIndexAt (Point<float> p) const
|
|||
for (const auto& glyph : glyphsInLine)
|
||||
{
|
||||
if ( p.getX() <= glyphX
|
||||
|| glyph.newline
|
||||
|| (glyphIndex - lineIt->range.getStart() == (int64) glyphsInLine.size() - 1 && glyph.whitespace))
|
||||
|| glyph.isNewline()
|
||||
|| (glyphIndex - lineIt->range.getStart() == (int64) glyphsInLine.size() - 1 && glyph.isWhitespace()))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
@ -489,7 +509,7 @@ int64 JustifiedText::getGlyphIndexAt (Point<float> p) const
|
|||
return glyphIndex;
|
||||
}
|
||||
|
||||
Point<float> JustifiedText::getGlyphAnchor (int64 index) const
|
||||
GlyphAnchorResult JustifiedText::getGlyphAnchor (int64 index) const
|
||||
{
|
||||
jassert (index >= 0);
|
||||
|
||||
|
|
@ -499,19 +519,70 @@ Point<float> JustifiedText::getGlyphAnchor (int64 index) const
|
|||
const auto lineItem = linesMetrics.getItemWithEnclosingRange (index).value_or (linesMetrics.back());
|
||||
const auto indexInLine = index - lineItem.range.getStart();
|
||||
|
||||
auto anchor = lineItem.value.anchor;
|
||||
GlyphAnchorResult anchor { lineItem.value.anchor, lineItem.value.maxAscent, lineItem.value.maxDescent };
|
||||
|
||||
for (auto [i, glyph] : enumerate (shapedText.getGlyphs (lineItem.range), int64{}))
|
||||
{
|
||||
if (i == indexInLine)
|
||||
return anchor + glyph.offset;
|
||||
{
|
||||
anchor.anchor += glyph.offset;
|
||||
return anchor;
|
||||
}
|
||||
|
||||
anchor += glyph.advance;
|
||||
anchor.anchor += glyph.advance;
|
||||
}
|
||||
|
||||
return anchor;
|
||||
}
|
||||
|
||||
RectangleList<float> JustifiedText::getGlyphsBounds (Range<int64> glyphRange) const
|
||||
{
|
||||
RectangleList<float> bounds;
|
||||
|
||||
if (linesMetrics.isEmpty())
|
||||
return bounds;
|
||||
|
||||
const auto getBounds = [&] (const LineMetrics& line, int64 lineStart, int64 boundsStart, int64 boundsEnd) -> Rectangle<float>
|
||||
{
|
||||
const auto glyphsBefore = shapedText.getGlyphs ({ lineStart, boundsStart });
|
||||
|
||||
const auto xStart = std::accumulate (glyphsBefore.begin(),
|
||||
glyphsBefore.end(),
|
||||
line.anchor.getX(),
|
||||
[] (auto sum, auto glyph)
|
||||
{
|
||||
return sum + glyph.advance.getX();
|
||||
});
|
||||
|
||||
const auto glyphs = shapedText.getGlyphs ({ boundsStart, boundsEnd });
|
||||
|
||||
const auto xEnd = std::accumulate (glyphs.begin(),
|
||||
glyphs.end(),
|
||||
xStart,
|
||||
[&] (auto sum, auto glyph)
|
||||
{
|
||||
return sum + glyph.advance.getX();
|
||||
});
|
||||
|
||||
return { { xStart, line.top }, { xEnd, line.nextLineTop } };
|
||||
};
|
||||
|
||||
for (auto consumeFrom = glyphRange.getStart(); consumeFrom < glyphRange.getEnd();)
|
||||
{
|
||||
const auto lineItem = linesMetrics.getItemWithEnclosingRange (consumeFrom);
|
||||
|
||||
if (! lineItem.has_value())
|
||||
break;
|
||||
|
||||
const auto consumeTo = std::min (glyphRange.getEnd(), lineItem->range.getEnd());
|
||||
bounds.add (getBounds (lineItem->value, lineItem->range.getStart(), consumeFrom, consumeTo));
|
||||
|
||||
consumeFrom = consumeTo;
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
float JustifiedText::getHeight() const
|
||||
{
|
||||
if (linesMetrics.isEmpty())
|
||||
|
|
|
|||
|
|
@ -55,12 +55,29 @@ struct LineMetrics
|
|||
float maxAscent;
|
||||
float maxDescent;
|
||||
|
||||
/* Effective suggests the length of trailing whitespaces will be included or not depending on
|
||||
ShapedTextOptions::getTrailingWhitespacesShouldFit().
|
||||
*/
|
||||
float effectiveLineLength;
|
||||
|
||||
/* These values seem redundant given the relation between the baseline, ascent and top, but
|
||||
we want to ensure exactlyEquals (top, nextLineTop) for subsequent lines.
|
||||
*/
|
||||
float top;
|
||||
|
||||
/* This will be below the current line's visual bottom if non-default leading or additive
|
||||
line spacing is used.
|
||||
*/
|
||||
float nextLineTop;
|
||||
};
|
||||
|
||||
struct GlyphAnchorResult
|
||||
{
|
||||
Point<float> anchor;
|
||||
float maxAscent{};
|
||||
float maxDescent{};
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class JustifiedText
|
||||
{
|
||||
|
|
@ -129,7 +146,7 @@ public:
|
|||
|
||||
anchor += glyph.advance;
|
||||
|
||||
if (glyph.whitespace)
|
||||
if (glyph.isWhitespace())
|
||||
anchor.addXY (s, 0.0f);
|
||||
|
||||
return result;
|
||||
|
|
@ -159,7 +176,9 @@ public:
|
|||
then the returned anchor specifies the location where the next glyph would have to be
|
||||
placed i.e. lastGlyphAnchor + lastGlyphAdvance.
|
||||
*/
|
||||
Point<float> getGlyphAnchor (int64 index) const;
|
||||
GlyphAnchorResult getGlyphAnchor (int64 glyphIndex) const;
|
||||
|
||||
RectangleList<float> getGlyphsBounds (Range<int64> glyphRange) const;
|
||||
|
||||
/* Returns the vertical distance from the baseline of the first line to the bottom of the last
|
||||
plus any additional line spacing that follows from the leading and additiveLineSpacing
|
||||
|
|
@ -171,6 +190,8 @@ public:
|
|||
*/
|
||||
float getHeight() const;
|
||||
|
||||
const auto& getLinesMetrics() const { return linesMetrics; }
|
||||
|
||||
private:
|
||||
const SimpleShapedText& shapedText;
|
||||
detail::RangedValues<LineMetrics> linesMetrics;
|
||||
|
|
|
|||
|
|
@ -100,14 +100,16 @@ public:
|
|||
|
||||
void runTest() override
|
||||
{
|
||||
Ranges::Operations ops;
|
||||
|
||||
beginTest ("Ranges::set() - basics");
|
||||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.set ({ -3, 14 });
|
||||
ranges.set ({ -3, 14 }, ops);
|
||||
expectRange (ranges.get (0), { -3, 14 });
|
||||
|
||||
ranges.set ({ 7, 20 });
|
||||
ranges.set ({ 7, 20 }, ops);
|
||||
expectRange (ranges.get (0), { -3, 7 });
|
||||
expectRange (ranges.get (1), { 7, 20 });
|
||||
}
|
||||
|
|
@ -115,9 +117,9 @@ public:
|
|||
beginTest ("Ranges::set() - neighbouring ranges extents are modified");
|
||||
{
|
||||
Ranges ranges;
|
||||
ranges.set ({ -3, 14 });
|
||||
ranges.set ({ 19, 30 });
|
||||
ranges.set ({ 10, 25 });
|
||||
ranges.set ({ -3, 14 }, ops);
|
||||
ranges.set ({ 19, 30 }, ops);
|
||||
ranges.set ({ 10, 25 }, ops);
|
||||
|
||||
// size_t doesn't always map to an existing overload for String::operator<< on all platforms
|
||||
expectEquals ((int64) ranges.size(), (int64) 3);
|
||||
|
|
@ -129,12 +131,12 @@ public:
|
|||
beginTest ("Ranges::set() - setting a range inside another one splits that range");
|
||||
{
|
||||
Ranges ranges;
|
||||
ranges.set ({ -3, 14 });
|
||||
ranges.set ({ -3, 14 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 1);
|
||||
|
||||
//==============================================================================
|
||||
ranges.set ({ 3, 7 });
|
||||
ranges.set ({ 3, 7 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 3);
|
||||
expectRange (ranges.get (0), { -3, 3 });
|
||||
|
|
@ -145,16 +147,16 @@ public:
|
|||
beginTest ("Ranges::set() - old ranges falling within the bounds of a newly set are erased");
|
||||
{
|
||||
Ranges ranges;
|
||||
ranges.set ({ 0, 5 });
|
||||
ranges.set ({ 5, 10 });
|
||||
ranges.set ({ 15, 20 });
|
||||
ranges.set ({ 25, 30 });
|
||||
ranges.set ({ 35, 50 });
|
||||
ranges.set ({ 0, 5 }, ops);
|
||||
ranges.set ({ 5, 10 }, ops);
|
||||
ranges.set ({ 15, 20 }, ops);
|
||||
ranges.set ({ 25, 30 }, ops);
|
||||
ranges.set ({ 35, 50 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 5);
|
||||
|
||||
//==============================================================================
|
||||
ranges.set ({ 4, 36 });
|
||||
ranges.set ({ 4, 36 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 3);
|
||||
expectRange (ranges.get (0), { 0, 4 });
|
||||
|
|
@ -166,7 +168,8 @@ public:
|
|||
{
|
||||
Ranges ranges;
|
||||
|
||||
auto ops = ranges.set ({ 0, 0 });
|
||||
ops.clear();
|
||||
ranges.set ({ 0, 0 }, ops);
|
||||
expect (ranges.isEmpty());
|
||||
expect (ops.empty());
|
||||
}
|
||||
|
|
@ -175,9 +178,9 @@ public:
|
|||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.set ({ 0, 48 });
|
||||
ranges.set ({ 48, 127 });
|
||||
ranges.set ({ 49, 94 });
|
||||
ranges.set ({ 0, 48 }, ops);
|
||||
ranges.set ({ 48, 127 }, ops);
|
||||
ranges.set ({ 49, 94 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 4, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
|
|
@ -190,10 +193,11 @@ public:
|
|||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.set ({ 0, 48 });
|
||||
ranges.set ({ 48, 127 });
|
||||
ranges.set ({ 0, 48 }, ops);
|
||||
ranges.set ({ 48, 127 }, ops);
|
||||
|
||||
auto ops = ranges.split (47);
|
||||
ops.clear();
|
||||
ranges.split (47, ops);
|
||||
|
||||
expectEquals ((int64) ops.size(), (int64) 1, "");
|
||||
const auto op0 = std::get_if<Ranges::Ops::Split> (&ops[0]);
|
||||
|
|
@ -212,10 +216,11 @@ public:
|
|||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.set ({ 0, 48 });
|
||||
ranges.set ({ 48, 127 });
|
||||
ranges.set ({ 0, 48 }, ops);
|
||||
ranges.set ({ 48, 127 }, ops);
|
||||
|
||||
auto ops = ranges.split (48);
|
||||
ops.clear();
|
||||
ranges.split (48, ops);
|
||||
expectEquals ((int64) ops.size(), (int64) 0, "");
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 2, "");
|
||||
|
|
@ -227,10 +232,10 @@ public:
|
|||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.insert ({ -3, 14 });
|
||||
ranges.insert ({ -3, 14 }, ops);
|
||||
expectRange (ranges.get (0), { -3, 14 });
|
||||
|
||||
ranges.insert ({ 7, 20 });
|
||||
ranges.insert ({ 7, 20 }, ops);
|
||||
expectRange (ranges.get (0), { -3, 7 });
|
||||
expectRange (ranges.get (1), { 7, 20 });
|
||||
expectRange (ranges.get (2), { 20, 27 });
|
||||
|
|
@ -240,8 +245,8 @@ public:
|
|||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.insert ({ 10, 11 });
|
||||
ranges.insert ({ 0, 1 });
|
||||
ranges.insert ({ 10, 11 }, ops);
|
||||
ranges.insert ({ 0, 1 }, ops);
|
||||
|
||||
expectRange (ranges.get (0), { 0, 1 });
|
||||
expectRange (ranges.get (1), { 11, 12 });
|
||||
|
|
@ -251,19 +256,20 @@ public:
|
|||
{
|
||||
Ranges ranges;
|
||||
|
||||
auto ops = ranges.insert ({ 0, 0 });
|
||||
ops.clear();
|
||||
ranges.insert ({ 0, 0 }, ops);
|
||||
expect (ranges.isEmpty());
|
||||
expect (ops.empty());
|
||||
}
|
||||
|
||||
const auto getTestRanges = []
|
||||
const auto getTestRanges = [&ops]
|
||||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.set ({ 0, 48 });
|
||||
ranges.set ({ 48, 49 });
|
||||
ranges.set ({ 55, 94 });
|
||||
ranges.set ({ 94, 127 });
|
||||
ranges.set ({ 0, 48 }, ops);
|
||||
ranges.set ({ 48, 49 }, ops);
|
||||
ranges.set ({ 55, 94 }, ops);
|
||||
ranges.set ({ 94, 127 }, ops);
|
||||
|
||||
return ranges;
|
||||
};
|
||||
|
|
@ -271,7 +277,7 @@ public:
|
|||
beginTest ("Ranges::eraseFrom() - erasing beyond all ranges has no effect");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
ranges.eraseFrom (ranges.get (ranges.size() - 1).getEnd() + 5);
|
||||
ranges.eraseFrom (ranges.get (ranges.size() - 1).getEnd() + 5, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 4, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
|
|
@ -283,7 +289,7 @@ public:
|
|||
beginTest ("Ranges::eraseFrom() - erasing modifies the range that encloses the starting index");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
ranges.eraseFrom (122);
|
||||
ranges.eraseFrom (122, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 4, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
|
|
@ -295,7 +301,7 @@ public:
|
|||
beginTest ("Ranges::eraseFrom() - ranges starting after the erased index are deleted entirely");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
ranges.eraseFrom (60);
|
||||
ranges.eraseFrom (60, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 3, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
|
|
@ -306,7 +312,7 @@ public:
|
|||
beginTest ("Ranges::eraseFrom() - erasing from a location outside any ranges will still drop subsequent ranges");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
ranges.eraseFrom (51);
|
||||
ranges.eraseFrom (51, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 2, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
|
|
@ -317,7 +323,7 @@ public:
|
|||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.erase ({ 30, 30 });
|
||||
ranges.erase ({ 30, 30 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 4, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
|
|
@ -330,7 +336,7 @@ public:
|
|||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.erase ({ 30, 31 });
|
||||
ranges.erase ({ 30, 31 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 5, "");
|
||||
expectRange (ranges.get (0), { 0, 30 });
|
||||
|
|
@ -344,7 +350,7 @@ public:
|
|||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.erase ({ 30, 70 });
|
||||
ranges.erase ({ 30, 70 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 3, "");
|
||||
expectRange (ranges.get (0), { 0, 30 });
|
||||
|
|
@ -356,7 +362,7 @@ public:
|
|||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.erase ({ 51, 53 });
|
||||
ranges.erase ({ 51, 53 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 4, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
|
|
@ -369,7 +375,7 @@ public:
|
|||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.erase ({ -1000, 1000 });
|
||||
ranges.erase ({ -1000, 1000 }, ops);
|
||||
|
||||
expect (ranges.isEmpty());
|
||||
}
|
||||
|
|
@ -378,7 +384,7 @@ public:
|
|||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.drop ({ 48, 49 });
|
||||
ranges.drop ({ 48, 49 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 3, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
|
|
@ -390,7 +396,7 @@ public:
|
|||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.drop ({ 51, 53 });
|
||||
ranges.drop ({ 51, 53 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 4, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
|
|
@ -403,7 +409,7 @@ public:
|
|||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.drop ({ -1000, 1000 });
|
||||
ranges.drop ({ -1000, 1000 }, ops);
|
||||
expect (ranges.isEmpty());
|
||||
}
|
||||
|
||||
|
|
@ -411,11 +417,11 @@ public:
|
|||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.set ({ 0, 48 });
|
||||
ranges.set ({ 48, 49 });
|
||||
ranges.set ({ 55, 94 });
|
||||
ranges.set ({ 94, 127 });
|
||||
ranges.set ({ 127, 150 });
|
||||
ranges.set ({ 0, 48 }, ops);
|
||||
ranges.set ({ 48, 49 }, ops);
|
||||
ranges.set ({ 55, 94 }, ops);
|
||||
ranges.set ({ 94, 127 }, ops);
|
||||
ranges.set ({ 127, 150 }, ops);
|
||||
|
||||
expect (ranges.covers ({ 0, 48 }));
|
||||
expect (ranges.covers ({ 0, 20 }));
|
||||
|
|
@ -460,14 +466,15 @@ public:
|
|||
void runTest() override
|
||||
{
|
||||
auto random = getRandom();
|
||||
Ranges::Operations ops;
|
||||
|
||||
const auto createRangedValuesObject = [&]
|
||||
{
|
||||
RangedValues<char> rangedValues;
|
||||
|
||||
rangedValues.set ({ 0, 10 }, 'a');
|
||||
rangedValues.set ({ 11, 20 }, 'b');
|
||||
rangedValues.set ({ 23, 30 }, 'c');
|
||||
rangedValues.set ({ 0, 10 }, 'a', ops);
|
||||
rangedValues.set ({ 11, 20 }, 'b', ops);
|
||||
rangedValues.set ({ 23, 30 }, 'c', ops);
|
||||
|
||||
return rangedValues;
|
||||
};
|
||||
|
|
@ -476,7 +483,7 @@ public:
|
|||
{
|
||||
auto rangedValues = createRangedValuesObject();
|
||||
|
||||
rangedValues.set ({ 5, 15 }, 'd');
|
||||
rangedValues.set ({ 5, 15 }, 'd', ops);
|
||||
|
||||
expect (! rangedValues.isEmpty());
|
||||
|
||||
|
|
@ -485,7 +492,7 @@ public:
|
|||
expectRangedValuesItem (rangedValues.getItem (2), { 15, 20 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c');
|
||||
|
||||
rangedValues.set ({ 19, 24 }, 'e');
|
||||
rangedValues.set ({ 19, 24 }, 'e', ops);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 15, 19 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 19, 24 }, 'e');
|
||||
|
|
@ -496,7 +503,7 @@ public:
|
|||
{
|
||||
auto rangedValues = createRangedValuesObject();
|
||||
|
||||
rangedValues.set ({ -1, 0 }, 'd');
|
||||
rangedValues.set ({ -1, 0 }, 'd', ops);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { -1, 0 }, 'd');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 0, 10 }, 'a');
|
||||
|
|
@ -506,7 +513,7 @@ public:
|
|||
{
|
||||
auto rangedValues = createRangedValuesObject();
|
||||
|
||||
rangedValues.set<MergeEqualItems::no> ({ 5, 15 }, 'b');
|
||||
rangedValues.set ({ 5, 15 }, 'b', ops, MergeEqualItemsNo{});
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 5, 15 }, 'b');
|
||||
|
|
@ -518,7 +525,7 @@ public:
|
|||
{
|
||||
auto rangedValues = createRangedValuesObject();
|
||||
|
||||
rangedValues.set<MergeEqualItems::yes> ({ 5, 15 }, 'b');
|
||||
rangedValues.set ({ 5, 15 }, 'b', ops, MergeEqualItemsYes{});
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 5, 20 }, 'b');
|
||||
|
|
@ -529,7 +536,8 @@ public:
|
|||
{
|
||||
RangedValues<char> rangedValues;
|
||||
|
||||
auto ops = rangedValues.set ({ 0, 0 }, 'a');
|
||||
ops.clear();
|
||||
rangedValues.set ({ 0, 0 }, 'a', ops);
|
||||
expect (rangedValues.isEmpty());
|
||||
expect (ops.empty());
|
||||
}
|
||||
|
|
@ -538,9 +546,9 @@ public:
|
|||
{
|
||||
RangedValues<char> rangedValues;
|
||||
|
||||
rangedValues.set ({ 0, 48 }, 'a');
|
||||
rangedValues.set ({ 48, 127 }, 'b');
|
||||
rangedValues.set ({ 49, 94 }, 'c');
|
||||
rangedValues.set ({ 0, 48 }, 'a', ops);
|
||||
rangedValues.set ({ 48, 127 }, 'b', ops);
|
||||
rangedValues.set ({ 49, 94 }, 'c', ops);
|
||||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 4, "");
|
||||
|
||||
|
|
@ -581,13 +589,17 @@ public:
|
|||
const auto beginInsertionAt = (int64) random.nextInt (100) - 50;
|
||||
const auto numElemsToInsert = (int64) random.nextInt (1000);
|
||||
|
||||
rangedValuesNotMerged.insert<MergeEqualItems::no> ({ Range<int64>::withStartAndLength (beginInsertionAt, numElemsToInsert) },
|
||||
'a' + (char) random.nextInt (25));
|
||||
rangedValuesNotMerged.insert ({ Range<int64>::withStartAndLength (beginInsertionAt, numElemsToInsert) },
|
||||
'a' + (char) random.nextInt (25),
|
||||
ops,
|
||||
MergeEqualItemsNo{});
|
||||
|
||||
expectEquals (getCumulativeRangeLengths (rangedValuesNotMerged) - totalLengthBeforeInsert, numElemsToInsert);
|
||||
|
||||
rangedValuesMerged.insert<MergeEqualItems::yes> ({ Range<int64>::withStartAndLength (beginInsertionAt, numElemsToInsert) },
|
||||
'a' + (char) random.nextInt (25));
|
||||
rangedValuesMerged.insert ({ Range<int64>::withStartAndLength (beginInsertionAt, numElemsToInsert) },
|
||||
'a' + (char) random.nextInt (25),
|
||||
ops,
|
||||
MergeEqualItemsYes{});
|
||||
|
||||
expectEquals (getCumulativeRangeLengths (rangedValuesMerged) - totalLengthBeforeInsert, numElemsToInsert);
|
||||
}
|
||||
|
|
@ -599,7 +611,7 @@ public:
|
|||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 3);
|
||||
|
||||
rangedValues.insert ({ 2, 4 }, 'd');
|
||||
rangedValues.insert ({ 2, 4 }, 'd', ops);
|
||||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 5);
|
||||
|
||||
|
|
@ -617,7 +629,7 @@ public:
|
|||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 3);
|
||||
|
||||
rangedValues.insert<MergeEqualItems::yes> ({ 2, 4 }, 'a');
|
||||
rangedValues.insert ({ 2, 4 }, 'a', ops, MergeEqualItemsYes{});
|
||||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 3);
|
||||
|
||||
|
|
@ -630,7 +642,7 @@ public:
|
|||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 3);
|
||||
|
||||
rangedValues.insert<MergeEqualItems::no> ({ 2, 4 }, 'a');
|
||||
rangedValues.insert ({ 2, 4 }, 'a', ops, MergeEqualItemsNo{});
|
||||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 5);
|
||||
|
||||
|
|
@ -647,16 +659,18 @@ public:
|
|||
{
|
||||
RangedValues<char> emptyRangedValues;
|
||||
|
||||
auto ops = emptyRangedValues.insert ({ 0, 0 }, 'a');
|
||||
ops.clear();
|
||||
emptyRangedValues.insert ({ 0, 0 }, 'a', ops);
|
||||
expect (emptyRangedValues.isEmpty());
|
||||
expect (ops.empty());
|
||||
}
|
||||
|
||||
{
|
||||
RangedValues<char> rangedValues;
|
||||
rangedValues.set ({ 0, 10 }, 'a');
|
||||
rangedValues.set ({ 0, 10 }, 'a', ops);
|
||||
|
||||
auto ops = rangedValues.insert ({ 0, 0 }, 'a');
|
||||
ops.clear();
|
||||
rangedValues.insert ({ 0, 0 }, 'a', ops);
|
||||
expect (ops.empty());
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 1);
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
|
||||
|
|
@ -667,11 +681,11 @@ public:
|
|||
{
|
||||
RangedValues<char> rangedValues;
|
||||
|
||||
rangedValues.set ({ 0, 10 }, 'a');
|
||||
rangedValues.set ({ 11, 20 }, 'b');
|
||||
rangedValues.set ({ 23, 30 }, 'c');
|
||||
rangedValues.set ({ 35, 45 }, 'c');
|
||||
rangedValues.set ({ 45, 60 }, 'd');
|
||||
rangedValues.set ({ 0, 10 }, 'a', ops);
|
||||
rangedValues.set ({ 11, 20 }, 'b', ops);
|
||||
rangedValues.set ({ 23, 30 }, 'c', ops);
|
||||
rangedValues.set ({ 35, 45 }, 'c', ops);
|
||||
rangedValues.set ({ 45, 60 }, 'd', ops);
|
||||
|
||||
return rangedValues;
|
||||
};
|
||||
|
|
@ -680,7 +694,7 @@ public:
|
|||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
|
||||
rangedValues.erase ({ 15, 16 });
|
||||
rangedValues.erase ({ 15, 16 }, ops);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 11, 15 }, 'b');
|
||||
|
|
@ -694,7 +708,7 @@ public:
|
|||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
|
||||
rangedValues.eraseUpTo (rangedValues.getRanges().get (0).getStart());
|
||||
rangedValues.eraseUpTo (rangedValues.getRanges().get (0).getStart(), ops);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 11, 20 }, 'b');
|
||||
|
|
@ -707,7 +721,7 @@ public:
|
|||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
|
||||
rangedValues.eraseUpTo (15);
|
||||
rangedValues.eraseUpTo (15, ops);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 15, 20 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 23, 30 }, 'c');
|
||||
|
|
@ -720,7 +734,7 @@ public:
|
|||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
const auto ranges = rangedValues.getRanges();
|
||||
|
||||
rangedValues.eraseUpTo (ranges.get (ranges.size() - 1).getEnd());
|
||||
rangedValues.eraseUpTo (ranges.get (ranges.size() - 1).getEnd(), ops);
|
||||
|
||||
expect (rangedValues.isEmpty());
|
||||
}
|
||||
|
|
@ -729,7 +743,7 @@ public:
|
|||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
|
||||
rangedValues.drop<MergeEqualItems::no> ({ 15, 16 });
|
||||
rangedValues.drop ({ 15, 16 }, ops, MergeEqualItemsNo{});
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 11, 15 }, 'b');
|
||||
|
|
@ -743,7 +757,7 @@ public:
|
|||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
|
||||
rangedValues.drop<MergeEqualItems::yes> ({ 15, 16 });
|
||||
rangedValues.drop ({ 15, 16 }, ops, MergeEqualItemsYes{});
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 11, 19 }, 'b');
|
||||
|
|
@ -755,9 +769,9 @@ public:
|
|||
beginTest ("RangedValues::drop() - the merge happens at the seam caused by the drop and does not extend beyond");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
rangedValues.set<MergeEqualItems::no> ({ 20, 30 }, 'b');
|
||||
rangedValues.set ({ 20, 30 }, 'b', ops, MergeEqualItemsNo{});
|
||||
|
||||
rangedValues.drop<MergeEqualItems::yes> ({ 15, 16 });
|
||||
rangedValues.drop ({ 15, 16 }, ops, MergeEqualItemsYes{});
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
|
||||
|
||||
|
|
@ -780,23 +794,25 @@ public:
|
|||
|
||||
void runTest() override
|
||||
{
|
||||
Ranges::Operations ops;
|
||||
|
||||
beginTest ("IntersectingRangedValuesTests - iterating over multiple RangedValues");
|
||||
{
|
||||
RangedValues<int> rv1;
|
||||
rv1.set ({ 3, 8}, 1);
|
||||
rv1.set ({ 9, 16}, 2);
|
||||
rv1.set ({ 30, 40}, 3);
|
||||
rv1.set ({ 3, 8}, 1, ops);
|
||||
rv1.set ({ 9, 16}, 2, ops);
|
||||
rv1.set ({ 30, 40}, 3, ops);
|
||||
|
||||
RangedValues<int> rv2;
|
||||
rv2.set ({ 0, 4}, 7);
|
||||
rv2.set ({ 4, 6}, 11);
|
||||
rv2.set ({ 6, 25}, 13);
|
||||
rv2.set ({ 27, 55}, 17);
|
||||
rv2.set ({ 0, 4}, 7, ops);
|
||||
rv2.set ({ 4, 6}, 11, ops);
|
||||
rv2.set ({ 6, 25}, 13, ops);
|
||||
rv2.set ({ 27, 55}, 17, ops);
|
||||
|
||||
RangedValues<int> rv3;
|
||||
rv3.set ({ -2, 10}, -1);
|
||||
rv3.set ({ 15, 19}, -2);
|
||||
rv3.set ({ 22, 36}, -3);
|
||||
rv3.set ({ -2, 10}, -1, ops);
|
||||
rv3.set ({ 15, 19}, -2, ops);
|
||||
rv3.set ({ 22, 36}, -3, ops);
|
||||
|
||||
int iteration = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -106,20 +106,6 @@ struct Ranges final
|
|||
|
||||
using Operations = std::vector<Op>;
|
||||
|
||||
static auto withOperationsFrom (const Operations& ops, const Operations& newOps)
|
||||
{
|
||||
auto result = ops;
|
||||
result.insert (result.end(), newOps.begin(), newOps.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
static auto withOperationsFrom (const Operations& ops, Op newOp)
|
||||
{
|
||||
auto result = ops;
|
||||
result.insert (result.end(), newOp);
|
||||
return result;
|
||||
}
|
||||
|
||||
Ranges() = default;
|
||||
|
||||
explicit Ranges (std::vector<Range<int64>> rangesIn)
|
||||
|
|
@ -141,42 +127,34 @@ struct Ranges final
|
|||
|
||||
//==============================================================================
|
||||
// Basic operations
|
||||
Operations split (int64 i)
|
||||
void split (int64 i, Operations& ops)
|
||||
{
|
||||
Operations ops;
|
||||
|
||||
const auto elemIndex = getIndexForEnclosingRange (i);
|
||||
|
||||
if (! elemIndex.has_value())
|
||||
return {};
|
||||
return;
|
||||
|
||||
auto& elem = ranges[*elemIndex];
|
||||
|
||||
if (elem.getStart() == i)
|
||||
return {};
|
||||
return;
|
||||
|
||||
ops = withOperationsFrom (ops, Ops::Split { *elemIndex,
|
||||
elem.withEnd (i),
|
||||
elem.withStart (i) });
|
||||
ops.push_back (Ops::Split { *elemIndex, elem.withEnd (i), elem.withStart (i) });
|
||||
|
||||
const auto oldLength = elem.getLength();
|
||||
elem.setEnd (i);
|
||||
|
||||
ranges.insert (iteratorWithAdvance (ranges.begin(), *elemIndex + 1),
|
||||
{ i, i + oldLength - elem.getLength() });
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
Operations erase (Range<int64> r)
|
||||
void erase (Range<int64> r, Operations& ops)
|
||||
{
|
||||
if (r.isEmpty())
|
||||
return {};
|
||||
|
||||
Operations ops;
|
||||
return;
|
||||
|
||||
for (auto i : { r.getStart(), r.getEnd() })
|
||||
ops = withOperationsFrom (ops, split (i));
|
||||
split (i, ops);
|
||||
|
||||
const auto firstToDelete = std::lower_bound (ranges.begin(),
|
||||
ranges.end(),
|
||||
|
|
@ -191,26 +169,23 @@ struct Ranges final
|
|||
{ return elem.getStart() < value; });
|
||||
|
||||
if (firstToDelete != ranges.end())
|
||||
ops = withOperationsFrom (ops, Ops::Erase { { getIndex (firstToDelete), getIndex (beyondLastToDelete) } });
|
||||
ops.push_back (Ops::Erase { { getIndex (firstToDelete), getIndex (beyondLastToDelete) } });
|
||||
|
||||
ranges.erase (firstToDelete, beyondLastToDelete);
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
Operations drop (Range<int64> r)
|
||||
void drop (Range<int64> r, Operations& ops)
|
||||
{
|
||||
auto ops = erase (r);
|
||||
ops = withOperationsFrom (ops, shift (r.getEnd(), -r.getLength()));
|
||||
return ops;
|
||||
erase (r, ops);
|
||||
shift (r.getEnd(), -r.getLength(), ops);
|
||||
}
|
||||
|
||||
/* Shift all ranges starting at or beyond the specified from parameter, by the specified amount.
|
||||
*/
|
||||
Operations shift (int64 from, int64 amount)
|
||||
void shift (int64 from, int64 amount, Operations& ops)
|
||||
{
|
||||
if (amount == 0)
|
||||
return {};
|
||||
return;
|
||||
|
||||
const auto shiftStartingFrom = std::lower_bound (ranges.begin(),
|
||||
ranges.end(),
|
||||
|
|
@ -218,26 +193,20 @@ struct Ranges final
|
|||
[] (auto& elem, auto& value)
|
||||
{ return elem.getStart() < value; });
|
||||
|
||||
Operations ops;
|
||||
|
||||
for (auto it = shiftStartingFrom; it < ranges.end(); ++it)
|
||||
{
|
||||
const auto oldRange = *it;
|
||||
*it += amount;
|
||||
ops = withOperationsFrom (ops, Ops::Change { getIndex (it), oldRange, *it });
|
||||
ops.push_back (Ops::Change { getIndex (it), oldRange, *it });
|
||||
}
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
Operations set (Range<int64> newRange)
|
||||
void set (Range<int64> newRange, Operations& ops)
|
||||
{
|
||||
if (newRange.isEmpty())
|
||||
return {};
|
||||
return;
|
||||
|
||||
Operations ops;
|
||||
|
||||
ops = withOperationsFrom (ops, erase (newRange));
|
||||
erase (newRange, ops);
|
||||
|
||||
const auto insertBefore = std::lower_bound (ranges.begin(),
|
||||
ranges.end(),
|
||||
|
|
@ -245,21 +214,17 @@ struct Ranges final
|
|||
[] (auto& elem, auto& value)
|
||||
{ return elem.getStart() < value; });
|
||||
|
||||
ops = withOperationsFrom (ops, Ops::New { getIndex (insertBefore) });
|
||||
ops.push_back (Ops::New { getIndex (insertBefore) });
|
||||
ranges.insert (insertBefore, newRange);
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
Operations insert (Range<int64> newRange)
|
||||
void insert (Range<int64> newRange, Operations& ops)
|
||||
{
|
||||
if (newRange.isEmpty())
|
||||
return {};
|
||||
return;
|
||||
|
||||
Operations ops;
|
||||
|
||||
ops = withOperationsFrom (ops, split (newRange.getStart()));
|
||||
ops = withOperationsFrom (ops, shift (newRange.getStart(), newRange.getLength()));
|
||||
split (newRange.getStart(), ops);
|
||||
shift (newRange.getStart(), newRange.getLength(), ops);
|
||||
|
||||
const auto insertBefore = std::lower_bound (ranges.begin(),
|
||||
ranges.end(),
|
||||
|
|
@ -270,9 +235,7 @@ struct Ranges final
|
|||
const auto insertBeforeIndex = getIndex (insertBefore);
|
||||
|
||||
ranges.insert (insertBefore, newRange);
|
||||
ops = withOperationsFrom (ops, Ops::New { insertBeforeIndex });
|
||||
|
||||
return ops;
|
||||
ops.push_back (Ops::New { insertBeforeIndex });
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -282,41 +245,36 @@ struct Ranges final
|
|||
ranges.clear();
|
||||
}
|
||||
|
||||
Operations eraseFrom (int64 i)
|
||||
void eraseFrom (int64 i, Operations& ops)
|
||||
{
|
||||
if (ranges.empty())
|
||||
return {};
|
||||
return;
|
||||
|
||||
return erase ({ i, ranges.back().getEnd() });
|
||||
erase ({ i, ranges.back().getEnd() }, ops);
|
||||
}
|
||||
|
||||
/* Merges neighbouring ranges backwards if they form a contiguous range.
|
||||
*/
|
||||
Operations mergeBack (size_t i)
|
||||
void mergeBack (size_t i, Operations& ops)
|
||||
{
|
||||
jassert (isPositiveAndBelow (i, ranges.size()));
|
||||
|
||||
if (i == 0 || i >= ranges.size())
|
||||
return {};
|
||||
return;
|
||||
|
||||
const auto start = i - 1;
|
||||
const auto end = i;
|
||||
|
||||
if (ranges[start].getEnd() != ranges[end].getStart())
|
||||
return {};
|
||||
|
||||
Operations ops;
|
||||
return;
|
||||
|
||||
const auto oldRange = ranges[start];
|
||||
ranges[start].setEnd (ranges[end].getEnd());
|
||||
ops = withOperationsFrom (ops, Ops::Change { start, oldRange, ranges[start] });
|
||||
|
||||
ops = withOperationsFrom (ops, Ops::Erase { { end, end + 1 } });
|
||||
ops.push_back (Ops::Change { start, oldRange, ranges[start] });
|
||||
ops.push_back (Ops::Erase { { end, end + 1 } });
|
||||
|
||||
ranges.erase (iteratorWithAdvance (ranges.begin(), end),
|
||||
iteratorWithAdvance (ranges.begin(), end + 1));
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
/* Returns the ranges that have an intersection with the provided range. */
|
||||
|
|
@ -441,12 +399,6 @@ private:
|
|||
};
|
||||
|
||||
//==============================================================================
|
||||
enum class MergeEqualItems
|
||||
{
|
||||
no,
|
||||
yes
|
||||
};
|
||||
|
||||
template <typename T, typename = void>
|
||||
constexpr auto hasEqualityOperator = false;
|
||||
|
||||
|
|
@ -469,13 +421,13 @@ private:
|
|||
using InternalIterator = const Range<int64>*;
|
||||
|
||||
public:
|
||||
using value_type = std::pair<Range<int64>, T>;
|
||||
using value_type = RangedValuesIteratorItem<T>;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using reference = RangedValuesIteratorItem<T>;
|
||||
using reference = value_type;
|
||||
|
||||
struct PointerProxy
|
||||
{
|
||||
PointerProxy (reference r) : ref { r } {}
|
||||
explicit PointerProxy (reference r) : ref { r } {}
|
||||
|
||||
auto operator->() const { return &ref; }
|
||||
|
||||
|
|
@ -543,6 +495,9 @@ private:
|
|||
InternalIterator iteratorBase, iterator;
|
||||
};
|
||||
|
||||
struct MergeEqualItemsYes{};
|
||||
struct MergeEqualItemsNo{};
|
||||
|
||||
/* Data structure for storing values associated with non-overlapping ranges.
|
||||
|
||||
Has set() and insert() operations with the optional merging of ranges that contain equal values.
|
||||
|
|
@ -567,7 +522,7 @@ class RangedValues
|
|||
return j ? std::make_optional (self.getItem (*j)) : std::nullopt;
|
||||
}
|
||||
|
||||
auto tie() const { return std::make_tuple (ranges, values); }
|
||||
auto tie() const { return std::tie (ranges, values); }
|
||||
|
||||
public:
|
||||
static constexpr bool canMergeEqualItems = hasEqualityOperator<T>;
|
||||
|
|
@ -625,70 +580,84 @@ public:
|
|||
const T& value;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Basic operations
|
||||
template <MergeEqualItems mergeEquals = MergeEqualItems::yes>
|
||||
auto set (Range<int64> r, T v)
|
||||
template <typename U>
|
||||
static auto createSubSpan (U& s, size_t offset)
|
||||
{
|
||||
static_assert (mergeEquals == MergeEqualItems::no || canMergeEqualItems,
|
||||
"You can't use MergeEqualItems::yes if your type doesn't have operator==.");
|
||||
Span span { s };
|
||||
|
||||
Ranges::Operations ops;
|
||||
if (span.empty())
|
||||
return span;
|
||||
|
||||
ops = Ranges::withOperationsFrom (ops, ranges.set (r));
|
||||
applyOperations (ops, std::move (v));
|
||||
const auto newSize = s.size() - std::min (s.size(), offset);
|
||||
|
||||
if constexpr (mergeEquals == MergeEqualItems::yes)
|
||||
{
|
||||
ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getStart()));
|
||||
ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getEnd()));
|
||||
}
|
||||
if (newSize == 0)
|
||||
return decltype (span){};
|
||||
|
||||
return ops;
|
||||
auto start = s.begin();
|
||||
std::advance (start, static_cast<typename U::difference_type> (offset));
|
||||
|
||||
return Span { start, newSize };
|
||||
}
|
||||
|
||||
template <MergeEqualItems mergeEquals = MergeEqualItems::yes>
|
||||
auto insert (Range<int64> r, T v)
|
||||
//==============================================================================
|
||||
// Basic operations
|
||||
template <typename MergeEquals = MergeEqualItemsYes>
|
||||
void set (Range<int64> r, T v, Ranges::Operations& ops, MergeEquals = {})
|
||||
{
|
||||
static_assert (mergeEquals == MergeEqualItems::no || canMergeEqualItems,
|
||||
static_assert (std::is_same_v<MergeEqualItemsNo, MergeEquals> || canMergeEqualItems,
|
||||
"You can't use MergeEqualItems::yes if your type doesn't have operator==.");
|
||||
|
||||
auto ops = ranges.insert (r);
|
||||
applyOperations (ops, std::move (v));
|
||||
const auto opsStart = ops.size();
|
||||
ranges.set (r, ops);
|
||||
applyOperations (createSubSpan (ops, opsStart), std::move (v));
|
||||
|
||||
if constexpr (mergeEquals == MergeEqualItems::yes)
|
||||
if constexpr (std::is_same_v<MergeEquals, MergeEqualItemsYes>)
|
||||
{
|
||||
ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getStart()));
|
||||
ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getEnd()));
|
||||
mergeEqualItems (r.getStart(), ops);
|
||||
mergeEqualItems (r.getEnd(), ops);
|
||||
}
|
||||
}
|
||||
|
||||
return ops;
|
||||
template <typename MergeEquals = MergeEqualItemsYes>
|
||||
void insert (Range<int64> r, T v, Ranges::Operations& ops, MergeEquals = {})
|
||||
{
|
||||
static_assert (std::is_same_v<MergeEquals, MergeEqualItemsNo> || canMergeEqualItems,
|
||||
"You can't use MergeEqualItems::yes if your type doesn't have operator==.");
|
||||
|
||||
const auto opsStart = ops.size();
|
||||
ranges.insert (r, ops);
|
||||
applyOperations (createSubSpan (ops, opsStart), std::move (v));
|
||||
|
||||
if constexpr (std::is_same_v<MergeEquals, MergeEqualItemsYes>)
|
||||
{
|
||||
mergeEqualItems (r.getStart(), ops);
|
||||
mergeEqualItems (r.getEnd(), ops);
|
||||
}
|
||||
}
|
||||
|
||||
// erase will always cause a discontinuity and thus, there is no opportunity to merge
|
||||
auto erase (Range<int64> r)
|
||||
void erase (Range<int64> r, Ranges::Operations& ops)
|
||||
{
|
||||
auto ops = ranges.erase (r);
|
||||
applyOperations (ops);
|
||||
return ops;
|
||||
const auto opsStart = ops.size();
|
||||
ranges.erase (r, ops);
|
||||
applyOperations (createSubSpan (ops, opsStart));
|
||||
}
|
||||
|
||||
// drop moves subsequent ranges downward, and can end up in these ranges forming a contiguous
|
||||
// range with the ones on the left side of the drop. Hence, it makes sense to ask if we want
|
||||
// merging behaviour.
|
||||
template <MergeEqualItems mergeEquals = MergeEqualItems::yes>
|
||||
auto drop (Range<int64> r)
|
||||
template <typename MergeEquals = MergeEqualItemsYes>
|
||||
void drop (Range<int64> r, Ranges::Operations& ops, MergeEquals = {})
|
||||
{
|
||||
static_assert (mergeEquals == MergeEqualItems::no || canMergeEqualItems,
|
||||
static_assert (std::is_same_v<MergeEquals, MergeEqualItemsNo> || canMergeEqualItems,
|
||||
"You can't use MergeEqualItems::yes if your type doesn't have operator==.");
|
||||
|
||||
auto ops = ranges.drop (r);
|
||||
applyOperations (ops);
|
||||
const auto opsStart = ops.size();
|
||||
ranges.drop (r, ops);
|
||||
applyOperations (createSubSpan (ops, opsStart));
|
||||
|
||||
if constexpr (mergeEquals == MergeEqualItems::yes)
|
||||
ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getStart()));
|
||||
|
||||
return ops;
|
||||
if constexpr (std::is_same_v<MergeEquals, MergeEqualItemsYes>)
|
||||
mergeEqualItems (r.getStart(), ops);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -698,40 +667,25 @@ public:
|
|||
values.clear();
|
||||
}
|
||||
|
||||
Ranges::Operations shift (int64 from, int64 amount)
|
||||
void shift (int64 from, int64 amount, Ranges::Operations& ops)
|
||||
{
|
||||
return ranges.shift (from, amount);
|
||||
ranges.shift (from, amount, ops);
|
||||
}
|
||||
|
||||
Ranges::Operations eraseFrom (int64 i)
|
||||
void eraseFrom (int64 i, Ranges::Operations& ops)
|
||||
{
|
||||
if (ranges.isEmpty())
|
||||
return {};
|
||||
return;
|
||||
|
||||
return erase ({ i, ranges.get (ranges.size() - 1).getEnd() });
|
||||
erase ({ i, ranges.get (ranges.size() - 1).getEnd() }, ops);
|
||||
}
|
||||
|
||||
Ranges::Operations eraseUpTo (int64 i)
|
||||
void eraseUpTo (int64 i, Ranges::Operations& ops)
|
||||
{
|
||||
if (ranges.isEmpty())
|
||||
return {};
|
||||
return;
|
||||
|
||||
return erase ({ ranges.get (0).getStart(), i });
|
||||
}
|
||||
|
||||
/** Create a RangedValues object from non-overlapping ranges. */
|
||||
template<MergeEqualItems mergeEquals, typename Iterable>
|
||||
auto setForEach (Iterable begin, Iterable end)
|
||||
{
|
||||
Ranges::Operations ops;
|
||||
|
||||
for (auto it = begin; it != end; ++it)
|
||||
{
|
||||
const auto& [range, value] = *it;
|
||||
ops = Ranges::withOperationsFrom (ops, set<mergeEquals> (range, value));
|
||||
}
|
||||
|
||||
return ops;
|
||||
erase ({ ranges.get (0).getStart(), i }, ops);
|
||||
}
|
||||
|
||||
auto getItemWithEnclosingRange (int64 i)
|
||||
|
|
@ -802,11 +756,14 @@ public:
|
|||
|
||||
RangedValues<T> result;
|
||||
|
||||
detail::Ranges::Operations ops;
|
||||
|
||||
for (const auto& is : intersections)
|
||||
{
|
||||
auto valueIndex = ranges.getIndexForEnclosingRange (is.getStart());
|
||||
jassert (valueIndex.has_value());
|
||||
result.template set<MergeEqualItems::no> (is, values[*valueIndex]);
|
||||
result.set (is, values[*valueIndex], ops, MergeEqualItemsNo{});
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -814,8 +771,9 @@ public:
|
|||
|
||||
RangedValues<T> getIntersectionsStartingAtZeroWith (Range<int64> r) const
|
||||
{
|
||||
detail::Ranges::Operations ops;
|
||||
auto result = getIntersectionsWith (r);
|
||||
result.drop ({ (int64) 0, r.getStart() });
|
||||
result.drop ({ (int64) 0, r.getStart() }, ops);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -832,23 +790,22 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
Ranges::Operations mergeEqualItems (int64 i)
|
||||
void mergeEqualItems (int64 i, Ranges::Operations& ops)
|
||||
{
|
||||
const auto endOpt = ranges.getIndexForEnclosingRange (i);
|
||||
|
||||
if (! endOpt.has_value() || *endOpt == 0)
|
||||
return {};
|
||||
return;
|
||||
|
||||
const auto end = *endOpt;
|
||||
const auto start = end - 1;
|
||||
|
||||
if (! exactlyEqual (values[start], values[end]))
|
||||
return {};
|
||||
return;
|
||||
|
||||
const auto ops = ranges.mergeBack (end);
|
||||
applyOperations (ops);
|
||||
|
||||
return ops;
|
||||
const auto opsStart = ops.size();
|
||||
ranges.mergeBack (end, ops);
|
||||
applyOperations (createSubSpan (ops, opsStart));
|
||||
}
|
||||
|
||||
void applyOperation (const Ranges::Op& op)
|
||||
|
|
@ -884,13 +841,13 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void applyOperations (const Ranges::Operations& ops)
|
||||
void applyOperations (Span<const Ranges::Op> ops)
|
||||
{
|
||||
for (const auto& op : ops)
|
||||
applyOperation (op);
|
||||
}
|
||||
|
||||
void applyOperations (const Ranges::Operations& ops, T v)
|
||||
void applyOperations (Span<const Ranges::Op> ops, T v)
|
||||
{
|
||||
for (const auto& op : ops)
|
||||
applyOperation (op, v);
|
||||
|
|
@ -918,9 +875,6 @@ template <typename T>
|
|||
class RangedIteratorWrapper final : public RangedIterator
|
||||
{
|
||||
public:
|
||||
/* We pass a pointer rather than a reference here to make it clearer that the pointed-to object
|
||||
must outlive the RangedIteratorWrapper, otherwise the wrapped iterators will dangle.
|
||||
*/
|
||||
RangedIteratorWrapper (RangedValuesIterator<T> iteratorIn, RangedValuesIterator<T> endIn)
|
||||
: iterator { std::move (iteratorIn) },
|
||||
end { std::move (endIn) }
|
||||
|
|
@ -938,6 +892,12 @@ private:
|
|||
RangedValuesIterator<T> iterator, end;
|
||||
};
|
||||
|
||||
template <typename Iterable>
|
||||
[[nodiscard]] auto makeRangedIteratorWrapper (Iterable* iterable)
|
||||
{
|
||||
return RangedIteratorWrapper { iterable->begin(), iterable->end() };
|
||||
}
|
||||
|
||||
/* A wrapper type encapsulating multiple RangedValues objects and providing iterator support.
|
||||
|
||||
The iterator will advance through Ranges that are intersections with homogeneous values in each
|
||||
|
|
@ -967,15 +927,14 @@ template <typename... Values>
|
|||
class IntersectingRangedValues
|
||||
{
|
||||
public:
|
||||
static_assert (sizeof...(Values) > 0, "IntersectingRangedValues() must wrap at least one RangedValues object");
|
||||
static_assert (sizeof... (Values) > 0, "IntersectingRangedValues() must wrap at least one RangedValues object");
|
||||
|
||||
/* This constructor takes a pointer rather than a reference to make it clearer that the pointed-to
|
||||
objects must outlive the IntersectingRangedValues instance. Passing a pointer also makes
|
||||
it harder to accidentally reference a temporary when constructing IntersectingRangedValues.
|
||||
*/
|
||||
template <typename... Iterables>
|
||||
explicit IntersectingRangedValues (Iterables*... iterable)
|
||||
: iteratorWrappers { std::make_tuple (RangedIteratorWrapper<Values> { iterable->begin(), iterable->end() }...) }
|
||||
explicit IntersectingRangedValues (RangedIteratorWrapper<Values>... wrappers)
|
||||
: iteratorWrappers { wrappers... }
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -1094,7 +1053,7 @@ template <typename... Iterables>
|
|||
{
|
||||
static_assert (sizeof...(Iterables) > 0, "makeIntersectingRangedValues() requires at least one parameter");
|
||||
|
||||
return IntersectingRangedValues<std::remove_reference_t<decltype (std::declval<Iterables>().begin()->value)>...> { iterables... };
|
||||
return IntersectingRangedValues (makeRangedIteratorWrapper (iterables)...);
|
||||
}
|
||||
|
||||
} // namespace juce::detail
|
||||
|
|
|
|||
|
|
@ -59,6 +59,11 @@ public:
|
|||
return simpleShapedText.getNumGlyphs();
|
||||
}
|
||||
|
||||
const detail::RangedValues<LineMetrics>& getLinesMetrics() const
|
||||
{
|
||||
return justifiedText.getLinesMetrics();
|
||||
}
|
||||
|
||||
auto& getText() const
|
||||
{
|
||||
return text;
|
||||
|
|
@ -74,9 +79,14 @@ public:
|
|||
return justifiedText.getGlyphIndexAt (p);
|
||||
}
|
||||
|
||||
auto getGlyphRanges (Range<int64> textRange) const
|
||||
void getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const
|
||||
{
|
||||
return simpleShapedText.getGlyphRanges (textRange);
|
||||
simpleShapedText.getGlyphRanges (textRange, outRanges);
|
||||
}
|
||||
|
||||
RectangleList<float> getGlyphsBounds (Range<int64> glyphRange) const
|
||||
{
|
||||
return justifiedText.getGlyphsBounds (glyphRange);
|
||||
}
|
||||
|
||||
auto getGlyphAnchor (int64 index) const
|
||||
|
|
@ -129,6 +139,11 @@ int64 ShapedText::getNumGlyphs() const
|
|||
return impl->getNumGlyphs();
|
||||
}
|
||||
|
||||
const detail::RangedValues<LineMetrics>& ShapedText::getLinesMetrics() const
|
||||
{
|
||||
return impl->getLinesMetrics();
|
||||
}
|
||||
|
||||
const String& ShapedText::getText() const
|
||||
{
|
||||
return impl->getText();
|
||||
|
|
@ -144,12 +159,17 @@ int64 ShapedText::getGlyphIndexAt (Point<float> p) const
|
|||
return impl->getGlyphIndexAt (p);
|
||||
}
|
||||
|
||||
std::vector<Range<int64>> ShapedText::getGlyphRanges (Range<int64> textRange) const
|
||||
void ShapedText::getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const
|
||||
{
|
||||
return impl->getGlyphRanges (textRange);
|
||||
return impl->getGlyphRanges (textRange, outRanges);
|
||||
}
|
||||
|
||||
Point<float> ShapedText::getGlyphAnchor (int64 index) const
|
||||
RectangleList<float> ShapedText::getGlyphsBounds (Range<int64> glyphRange) const
|
||||
{
|
||||
return impl->getGlyphsBounds (glyphRange);
|
||||
}
|
||||
|
||||
GlyphAnchorResult ShapedText::getGlyphAnchor (int64 index) const
|
||||
{
|
||||
return impl->getGlyphAnchor (index);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,10 +61,12 @@ public:
|
|||
|
||||
int64 getGlyphIndexAt (Point<float> p) const;
|
||||
|
||||
std::vector<Range<int64>> getGlyphRanges (Range<int64> textRange) const;
|
||||
void getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const;
|
||||
|
||||
RectangleList<float> getGlyphsBounds (Range<int64> glyphRange) const;
|
||||
|
||||
/* @see JustifiedText::getGlyphAnchor() */
|
||||
Point<float> getGlyphAnchor (int64 index) const;
|
||||
GlyphAnchorResult getGlyphAnchor (int64 index) const;
|
||||
|
||||
/* Returns the widths for each line, that the glyphs would require to be rendered without being
|
||||
truncated. This will or will not include the space required by trailing whitespaces in the
|
||||
|
|
@ -92,6 +94,8 @@ public:
|
|||
|
||||
int64 getNumGlyphs() const;
|
||||
|
||||
const detail::RangedValues<LineMetrics>& getLinesMetrics() const;
|
||||
|
||||
/* @internal */
|
||||
const JustifiedText& getJustifiedText() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -510,14 +510,19 @@ static detail::RangedValues<Font> findSuitableFontsForText (const Font& font,
|
|||
const String& language = {})
|
||||
{
|
||||
detail::RangedValues<std::optional<Font>> fonts;
|
||||
fonts.set ({ 0, (int64) text.length() }, font);
|
||||
detail::Ranges::Operations ops;
|
||||
fonts.set ({ 0, (int64) text.length() }, font, ops);
|
||||
ops.clear();
|
||||
|
||||
const auto getResult = [&]
|
||||
{
|
||||
detail::RangedValues<Font> result;
|
||||
|
||||
for (const auto [r, v] : fonts)
|
||||
result.set (r, v.value_or (font));
|
||||
{
|
||||
result.set (r, v.value_or (font), ops);
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
|
@ -542,7 +547,10 @@ static detail::RangedValues<Font> findSuitableFontsForText (const Font& font,
|
|||
}
|
||||
|
||||
for (const auto i : fontNotFound)
|
||||
fonts.set ({ i, i + 1 }, std::nullopt);
|
||||
{
|
||||
fonts.set ({ i, i + 1 }, std::nullopt, ops);
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
return fontNotFound.size();
|
||||
};
|
||||
|
|
@ -564,7 +572,10 @@ static detail::RangedValues<Font> findSuitableFontsForText (const Font& font,
|
|||
}
|
||||
|
||||
for (const auto& c : changes)
|
||||
fonts.set (c.first, c.second);
|
||||
{
|
||||
fonts.set (c.first, c.second, ops);
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
if (const auto newNumMissingGlyphs = markMissingGlyphs();
|
||||
std::exchange (numMissingGlyphs, newNumMissingGlyphs) == newNumMissingGlyphs)
|
||||
|
|
@ -580,6 +591,7 @@ static detail::RangedValues<Font> findSuitableFontsForText (const Font& font,
|
|||
static RangedValues<Font> resolveFontsWithFallback (const String& string, const RangedValues<Font>& fonts)
|
||||
{
|
||||
RangedValues<Font> resolved;
|
||||
detail::Ranges::Operations ops;
|
||||
|
||||
for (const auto [r, f] : fonts)
|
||||
{
|
||||
|
|
@ -587,7 +599,10 @@ static RangedValues<Font> resolveFontsWithFallback (const String& string, const
|
|||
(int) std::min (r.getEnd(), (int64) string.length())));
|
||||
|
||||
for (const auto [subRange, font] : rf)
|
||||
resolved.set<MergeEqualItems::no> (subRange + r.getStart(), font);
|
||||
{
|
||||
resolved.set (subRange + r.getStart(), font, ops, MergeEqualItemsNo{});
|
||||
ops.clear();
|
||||
}
|
||||
}
|
||||
|
||||
return resolved;
|
||||
|
|
@ -801,8 +816,10 @@ private:
|
|||
template <typename T>
|
||||
static auto rangedValuesWithOffset (detail::RangedValues<T> rv, int64 offset = 0)
|
||||
{
|
||||
rv.shift (std::numeric_limits<int64>::min(), -offset);
|
||||
rv.eraseUpTo (0);
|
||||
detail::Ranges::Operations ops;
|
||||
rv.shift (std::numeric_limits<int64>::min(), -offset, ops);
|
||||
ops.clear();
|
||||
rv.eraseUpTo (0, ops);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
|
@ -831,6 +848,8 @@ struct Shaper
|
|||
rangedValuesWithOffset (options.getFontsForRange(),
|
||||
shapingRange.getStart()));
|
||||
|
||||
detail::Ranges::Operations ops;
|
||||
|
||||
for (Unicode::LineBreakIterator lineIter { makeSpan (analysis) }; auto lineRun = lineIter.next();)
|
||||
{
|
||||
for (Unicode::ScriptRunIterator scriptIter { *lineRun }; auto scriptRun = scriptIter.next();)
|
||||
|
|
@ -850,11 +869,14 @@ struct Shaper
|
|||
|
||||
for (const auto [range, font] : fonts.getIntersectionsWith (bidiRange))
|
||||
{
|
||||
shaperRuns.set<MergeEqualItems::no> (range,
|
||||
{ scriptRun->front().script,
|
||||
options.getLanguage(),
|
||||
*it,
|
||||
font });
|
||||
shaperRuns.set (range,
|
||||
{ scriptRun->front().script,
|
||||
options.getLanguage(),
|
||||
*it,
|
||||
font },
|
||||
ops,
|
||||
MergeEqualItemsNo{});
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
it = next;
|
||||
|
|
@ -889,6 +911,8 @@ struct Shaper
|
|||
return *it;
|
||||
});
|
||||
|
||||
detail::Ranges::Operations ops;
|
||||
|
||||
if (! shapedGlyphs.getRanges().covers ({ startFrom, nextSoftBreakBefore }))
|
||||
{
|
||||
for (auto it = shaperRuns.find (startFrom);
|
||||
|
|
@ -905,12 +929,15 @@ struct Shaper
|
|||
it->value.language,
|
||||
it->value.embeddingLevel);
|
||||
|
||||
shapedGlyphs.set<MergeEqualItems::no> (shapingRange,
|
||||
{
|
||||
std::make_shared<std::vector<ShapedGlyph>> (std::move (g)),
|
||||
it->value.embeddingLevel % 2 == 0,
|
||||
it->value.resolvedFont
|
||||
});
|
||||
shapedGlyphs.set (shapingRange,
|
||||
{
|
||||
std::make_shared<std::vector<ShapedGlyph>> (std::move (g)),
|
||||
it->value.embeddingLevel % 2 == 0,
|
||||
it->value.resolvedFont
|
||||
},
|
||||
ops,
|
||||
MergeEqualItemsNo{});
|
||||
ops.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1030,7 +1057,7 @@ public:
|
|||
return nextState.isEmpty()
|
||||
|| glyph.advance.getX() <= remainingWidth
|
||||
|| (nextState.trailingWhitespaceCanExtendBeyondMargin
|
||||
&& glyph.whitespace
|
||||
&& glyph.isWhitespace()
|
||||
&& nextState.isInTrailingPosition (glyph));
|
||||
});
|
||||
|
||||
|
|
@ -1190,6 +1217,8 @@ static void foldLinesBeyondLineLimit (std::vector<std::vector<WrappedGlyphsCurso
|
|||
void SimpleShapedText::shape (const String& data,
|
||||
const ShapedTextOptions& options)
|
||||
{
|
||||
detail::Ranges::Operations ops;
|
||||
|
||||
for (const auto& lineRange : getLineRanges (data))
|
||||
{
|
||||
Shaper shaper { data, lineRange, options };
|
||||
|
|
@ -1234,12 +1263,15 @@ void SimpleShapedText::shape (const String& data,
|
|||
for (auto i = start; i < end; ++i)
|
||||
glyphsInVisualOrder[(size_t) i].cluster += lineRange.getStart();
|
||||
|
||||
glyphLookup.set<MergeEqualItems::no> (s.textRange + lineRange.getStart(), { { start, end }, ltr });
|
||||
resolvedFonts.set ({ start, end }, s.font);
|
||||
glyphLookup.set (s.textRange + lineRange.getStart(), { { start, end }, ltr }, ops, MergeEqualItemsNo{});
|
||||
ops.clear();
|
||||
resolvedFonts.set ({ start, end }, s.font, ops);
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
const auto lineEnd = (int64) glyphsInVisualOrder.size();
|
||||
lineNumbers.set ({ lineStart, lineEnd}, (int64) lineNumbers.size());
|
||||
lineNumbers.set ({ lineStart, lineEnd}, (int64) lineNumbers.size(), ops);
|
||||
ops.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1307,8 +1339,12 @@ Range<int64> SimpleShapedText::getTextRange (int64 glyphIndex) const
|
|||
return Range<int64>::withStartAndLength (cluster, std::max ((int64) 1, nextAdjacentCluster - cluster));
|
||||
}
|
||||
|
||||
/* Returns the last element that is smaller than value or equal to it. Returns begin of no such
|
||||
element is found.
|
||||
/* Returns the first element that equals value, if such an element exists.
|
||||
|
||||
Otherwise, returns the last element that is smaller than value, if such an element exists.
|
||||
|
||||
Returns end otherwise.
|
||||
|
||||
|
||||
NB: lower_bound: equal or greater
|
||||
upper_bound: greater
|
||||
|
|
@ -1333,9 +1369,11 @@ auto lessThanOrEqual (It begin, It end, Value v, Callback extractValue)
|
|||
return it;
|
||||
}
|
||||
|
||||
std::vector<Range<int64>> SimpleShapedText::getGlyphRanges (Range<int64> textRange) const
|
||||
void SimpleShapedText::getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const
|
||||
{
|
||||
Ranges glyphRanges;
|
||||
outRanges.clear();
|
||||
Ranges glyphRanges { std::move (outRanges) };
|
||||
detail::Ranges::Operations ops;
|
||||
|
||||
for (const auto is : glyphLookup.getIntersectionsWith (textRange))
|
||||
{
|
||||
|
|
@ -1361,7 +1399,7 @@ std::vector<Range<int64>> SimpleShapedText::getGlyphRanges (Range<int64> textRan
|
|||
|
||||
if (subRangeLookup.ltr)
|
||||
{
|
||||
glyphRanges.set (getGlyphSubRange (glyphs.begin(), glyphs.end()) + glyphsSubRange.getStart());
|
||||
glyphRanges.set (getGlyphSubRange (glyphs.begin(), glyphs.end()) + glyphsSubRange.getStart(), ops);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1369,11 +1407,14 @@ std::vector<Range<int64>> SimpleShapedText::getGlyphRanges (Range<int64> textRan
|
|||
std::reverse_iterator { glyphs.begin() });
|
||||
|
||||
glyphRanges.set ({ glyphsSubRange.getEnd() - reverseRange.getEnd(),
|
||||
glyphsSubRange.getEnd() - reverseRange.getStart() });
|
||||
glyphsSubRange.getEnd() - reverseRange.getStart() },
|
||||
ops);
|
||||
}
|
||||
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
return glyphRanges.getRanges();
|
||||
outRanges = std::move (glyphRanges.getRanges());
|
||||
}
|
||||
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
|
|
|||
|
|
@ -86,7 +86,8 @@ public:
|
|||
[[nodiscard]] ShapedTextOptions withFont (Font x) const
|
||||
{
|
||||
RangedValues<Font> fonts;
|
||||
fonts.set ({ 0, std::numeric_limits<int64>::max() }, x);
|
||||
detail::Ranges::Operations ops;
|
||||
fonts.set ({ 0, std::numeric_limits<int64>::max() }, x, ops);
|
||||
|
||||
return withMember (*this, &ShapedTextOptions::fontsForRange, std::move (fonts));
|
||||
}
|
||||
|
|
@ -177,7 +178,8 @@ private:
|
|||
detail::RangedValues<Font> fontsForRange = std::invoke ([&]
|
||||
{
|
||||
detail::RangedValues<Font> result;
|
||||
result.set ({ 0, std::numeric_limits<int64>::max() }, FontOptions { 15.0f });
|
||||
detail::Ranges::Operations ops;
|
||||
result.set ({ 0, std::numeric_limits<int64>::max() }, FontOptions { 15.0f }, ops);
|
||||
return result;
|
||||
});
|
||||
|
||||
|
|
@ -194,13 +196,35 @@ private:
|
|||
|
||||
struct ShapedGlyph
|
||||
{
|
||||
uint32_t glyphId;
|
||||
int64 cluster;
|
||||
bool unsafeToBreak;
|
||||
bool whitespace;
|
||||
bool newline;
|
||||
ShapedGlyph (uint32_t glyphIdIn,
|
||||
int64 clusterIn,
|
||||
bool unsafeToBreakIn,
|
||||
bool whitespaceIn,
|
||||
bool newlineIn,
|
||||
Point<float> advanceIn,
|
||||
Point<float> offsetIn)
|
||||
: advance (advanceIn),
|
||||
offset (offsetIn),
|
||||
cluster (clusterIn),
|
||||
glyphId (glyphIdIn),
|
||||
unsafeToBreak (unsafeToBreakIn),
|
||||
whitespace (whitespaceIn),
|
||||
newline (newlineIn) {}
|
||||
|
||||
bool isUnsafeToBreak() const { return unsafeToBreak; }
|
||||
bool isWhitespace() const { return whitespace; }
|
||||
bool isNewline() const { return newline; }
|
||||
|
||||
Point<float> advance;
|
||||
Point<float> offset;
|
||||
int64 cluster;
|
||||
uint32_t glyphId;
|
||||
|
||||
private:
|
||||
// These are effectively bools, pack into a single int once we have more than four flags.
|
||||
int8_t unsafeToBreak;
|
||||
int8_t whitespace;
|
||||
int8_t newline;
|
||||
};
|
||||
|
||||
struct GlyphLookupEntry
|
||||
|
|
@ -227,7 +251,7 @@ public:
|
|||
|
||||
Range<int64> getTextRange (int64 glyphIndex) const;
|
||||
|
||||
std::vector<Range<int64>> getGlyphRanges (Range<int64> textRange) const;
|
||||
void getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const;
|
||||
|
||||
int64 getNumLines() const { return (int64) lineNumbers.getRanges().size(); }
|
||||
int64 getNumGlyphs() const { return (int64) glyphsInVisualOrder.size(); }
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ static void addGlyphsFromShapedText (GlyphArrangement& ga, const detail::ShapedT
|
|||
position.getX() + x,
|
||||
position.getY() + y,
|
||||
glyph.advance.getX(),
|
||||
glyph.whitespace };
|
||||
glyph.isWhitespace() };
|
||||
|
||||
ga.addGlyph (std::move (pg));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -397,6 +397,8 @@ void TextLayout::createStandardLayout (const AttributedString& text)
|
|||
{
|
||||
using namespace detail;
|
||||
|
||||
detail::Ranges::Operations ops;
|
||||
|
||||
RangedValues<Font> fonts;
|
||||
RangedValues<Colour> colours;
|
||||
|
||||
|
|
@ -404,8 +406,9 @@ void TextLayout::createStandardLayout (const AttributedString& text)
|
|||
{
|
||||
const auto& attribute = text.getAttribute (i);
|
||||
const auto range = castTo<int64> (attribute.range);
|
||||
fonts.set (range, attribute.font);
|
||||
colours.set (range, attribute.colour);
|
||||
fonts.set (range, attribute.font, ops);
|
||||
colours.set (range, attribute.colour, ops);
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
auto shapedTextOptions = ShapedTextOptions{}.withFonts (fonts)
|
||||
|
|
@ -457,7 +460,7 @@ void TextLayout::createStandardLayout (const AttributedString& text)
|
|||
|
||||
for (auto it = std::reverse_iterator { glyphs.end() },
|
||||
end = std::reverse_iterator { glyphs.begin() };
|
||||
it != end && it->whitespace;
|
||||
it != end && it->isWhitespace();
|
||||
++it)
|
||||
{
|
||||
--i;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue