1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Add new Range related types that help working with attributed text

This commit is contained in:
attila 2024-03-07 15:01:35 +01:00 committed by reuk
parent 1560f87111
commit a2c7f1ea37
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
40 changed files with 1957 additions and 21 deletions

View file

@ -0,0 +1,566 @@
/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
namespace juce::detail
{
#if JUCE_UNIT_TESTS
template <typename T, typename = void>
constexpr auto hasGetStartFunction = false;
template <typename T>
constexpr auto hasGetStartFunction<T, std::void_t<decltype (std::declval<T>().getStart())>> = true;
template <typename RangeType, typename std::enable_if<hasGetStartFunction<RangeType>, int>::type = 0>
std::ostream& operator<< (std::ostream& os, const RangeType& range)
{
os << "[" << range.getStart() << ", " << range.getEnd() << ")";
return os;
}
static String& operator<< (String& s, Range<int64> r)
{
return s += "[" + String { r.getStart() } + ", " + String { r.getEnd() } + ")";
}
template <typename ValueType>
static auto getCumulativeRangeLengths (const RangedValues<ValueType>& rv)
{
int64 totalLength{};
for (size_t i = 0; i < rv.size(); ++i)
totalLength += rv.getItem (i).range.getLength();
return totalLength;
}
template <typename ValueType>
static auto toString (const RangedValues<ValueType>& rv)
{
String s {};
for (size_t i = 0; i < rv.size(); ++i)
{
auto item = rv.getItem (i);
s << item.range << ": " << item.value << "\n";
}
return s;
}
class RangesTestsBase : public UnitTest
{
public:
using UnitTest::UnitTest;
void expectRange (Range<int64> actual, Range<int64> expected)
{
String failureMessage { "range " };
failureMessage << actual << " did not equal expected range " << expected;
expect (actual == expected, failureMessage);
}
};
class RangesTests : public RangesTestsBase
{
public:
RangesTests() : RangesTestsBase ("Ranges", UnitTestCategories::containers) {}
void runTest() override
{
beginTest ("Ranges::set() - basics");
{
Ranges ranges;
ranges.set ({ -3, 14 });
expectRange (ranges.get (0), { -3, 14 });
ranges.set ({ 7, 20 });
expectRange (ranges.get (0), { -3, 7 });
expectRange (ranges.get (1), { 7, 20 });
}
beginTest ("Ranges::set() - neighbouring ranges extents are modified");
{
Ranges ranges;
ranges.set ({ -3, 14 });
ranges.set ({ 19, 30 });
ranges.set ({ 10, 25 });
// size_t doesn't always map to an existing overload for String::operator<< on all platforms
expectEquals ((int64) ranges.size(), (int64) 3);
expectRange (ranges.get (0), { -3, 10 });
expectRange (ranges.get (1), { 10, 25 });
expectRange (ranges.get (2), { 25, 30 });
}
beginTest ("Ranges::set() - setting a range inside another one splits that range");
{
Ranges ranges;
ranges.set ({ -3, 14 });
expectEquals ((int64) ranges.size(), (int64) 1);
//==============================================================================
ranges.set ({ 3, 7 });
expectEquals ((int64) ranges.size(), (int64) 3);
expectRange (ranges.get (0), { -3, 3 });
expectRange (ranges.get (1), { 3, 7 });
expectRange (ranges.get (2), { 7, 14 });
}
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 });
expectEquals ((int64) ranges.size(), (int64) 5);
//==============================================================================
ranges.set ({ 4, 36 });
expectEquals ((int64) ranges.size(), (int64) 3);
expectRange (ranges.get (0), { 0, 4 });
expectRange (ranges.get (1), { 4, 36 });
expectRange (ranges.get (2), { 36, 50 });
}
beginTest ("Ranges::set() - setting an empty range should be a no-op");
{
Ranges ranges;
auto ops = ranges.set ({ 0, 0 });
expect (ranges.isEmpty());
expect (ops.empty());
}
beginTest ("Ranges::set() - setting a range inside another range");
{
Ranges ranges;
ranges.set ({ 0, 48 });
ranges.set ({ 48, 127 });
ranges.set ({ 49, 94 });
expectEquals ((int64) ranges.size(), (int64) 4, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 49, 94 });
expectRange (ranges.get (3), { 94, 127 });
}
beginTest ("Ranges::split()");
{
Ranges ranges;
ranges.set ({ 0, 48 });
ranges.set ({ 48, 127 });
auto ops = ranges.split (47);
expectEquals ((int64) ops.size(), (int64) 2, "");
expect (std::get_if<Ranges::Ops::Changed> (&ops[0]) != nullptr);
expect (std::get_if<Ranges::Ops::Inserted> (&ops[1]) != nullptr);
expectEquals ((int64) ranges.size(), (int64) 3, "");
expectRange (ranges.get (0), { 0, 47 });
expectRange (ranges.get (1), { 47, 48 });
expectRange (ranges.get (2), { 48, 127 });
}
beginTest ("Ranges::split() - splitting has no effect when no range begins before and ends after the location");
{
Ranges ranges;
ranges.set ({ 0, 48 });
ranges.set ({ 48, 127 });
auto ops = ranges.split (48);
expectEquals ((int64) ops.size(), (int64) 0, "");
expectEquals ((int64) ranges.size(), (int64) 2, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 127 });
}
beginTest ("Ranges::insert() - basics");
{
Ranges ranges;
ranges.insert ({ -3, 14 });
expectRange (ranges.get (0), { -3, 14 });
ranges.insert ({ 7, 20 });
expectRange (ranges.get (0), { -3, 7 });
expectRange (ranges.get (1), { 7, 20 });
expectRange (ranges.get (2), { 20, 27 });
}
beginTest ("Ranges::insert() - inserting shifts all following ranges");
{
Ranges ranges;
ranges.insert ({ 10, 11 });
ranges.insert ({ 0, 1 });
expectRange (ranges.get (0), { 0, 1 });
expectRange (ranges.get (1), { 11, 12 });
}
beginTest ("Ranges::insert() - inserting an empty range should be a no-op");
{
Ranges ranges;
auto ops = ranges.insert ({ 0, 0 });
expect (ranges.isEmpty());
expect (ops.empty());
}
}
};
class RangedValuesTests : public UnitTest
{
public:
RangedValuesTests() : UnitTest ("RangedValues", UnitTestCategories::containers) {}
template <typename ItemType>
void expectRangedValuesItem (ItemType item, Range<int64> range, char value)
{
{
String failureMessage { "range " };
failureMessage << item.range << " did not equal expected range " << range;
expect (item.range == range, failureMessage);
}
{
String failureMessage { "value '" };
failureMessage << item.value << "' in range " << range << " did not equal expected value '" << value << "'";
expect (item.value == value, failureMessage);
}
}
void runTest() override
{
auto random = getRandom();
const auto createRangedValuesObject = [&]
{
RangedValues<char> rangedValues;
rangedValues.set ({ 0, 10 }, 'a');
rangedValues.set ({ 11, 20 }, 'b');
rangedValues.set ({ 23, 30 }, 'c');
return rangedValues;
};
beginTest ("RangedValues::set() with distinct value overlapping other ranges");
{
auto rangedValues = createRangedValuesObject();
rangedValues.set ({ 5, 15 }, 'd');
expect (! rangedValues.isEmpty());
expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 5, 15 }, 'd');
expectRangedValuesItem (rangedValues.getItem (2), { 15, 20 }, 'b');
expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c');
rangedValues.set ({ 19, 24 }, 'e');
expectRangedValuesItem (rangedValues.getItem (2), { 15, 19 }, 'b');
expectRangedValuesItem (rangedValues.getItem (3), { 19, 24 }, 'e');
expectRangedValuesItem (rangedValues.getItem (4), { 24, 30 }, 'c');
}
beginTest ("RangedValues::set() with distinct value in corner cases");
{
auto rangedValues = createRangedValuesObject();
rangedValues.set ({ -1, 0 }, 'd');
expectRangedValuesItem (rangedValues.getItem (0), { -1, 0 }, 'd');
expectRangedValuesItem (rangedValues.getItem (1), { 0, 10 }, 'a');
}
beginTest ("RangedValues::set() with same value with merging disallowed");
{
auto rangedValues = createRangedValuesObject();
rangedValues.set<MergeEqualItems::no> ({ 5, 15 }, 'b');
expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 5, 15 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 15, 20 }, 'b');
expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c');
}
beginTest ("RangedValues::set() with same value with merging allowed");
{
auto rangedValues = createRangedValuesObject();
rangedValues.set<MergeEqualItems::yes> ({ 5, 15 }, 'b');
expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 5, 20 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 23, 30 }, 'c');
}
beginTest ("RangedValues::set() - setting an empty Range should be a no-op");
{
RangedValues<char> rangedValues;
auto ops = rangedValues.set ({ 0, 0 }, 'a');
expect (rangedValues.isEmpty());
expect (ops.empty());
}
beginTest ("RangedValues::set() - setting a range inside another range");
{
RangedValues<char> rangedValues;
rangedValues.set ({ 0, 48 }, 'a');
rangedValues.set ({ 48, 127 }, 'b');
rangedValues.set ({ 49, 94 }, 'c');
expectEquals ((int64) rangedValues.size(), (int64) 4, "");
expectRangedValuesItem (rangedValues.getItem (0), { 0, 48 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 48, 49 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 49, 94 }, 'c');
expectRangedValuesItem (rangedValues.getItem (3), { 94, 127 }, 'b');
}
beginTest ("RangedValues::getIntersectionsWith()");
{
auto rangedValues = createRangedValuesObject();
{
const auto intersections = rangedValues.getIntersectionsWith ({ 5, 43 });
expectRangedValuesItem (intersections[0], { 5, 10 }, 'a');
expectRangedValuesItem (intersections[1], { 11, 20 }, 'b');
expectRangedValuesItem (intersections[2], { 23, 30 }, 'c');
}
{
const auto intersections = rangedValues.getIntersectionsWith ({ -10, 3 });
expectRangedValuesItem (intersections[0], { 0, 3 }, 'a');
}
}
beginTest ("RangedValues::insert() fuzzing - insert always increases the total covered range");
{
for (auto i = 0; i != 100; ++i)
{
auto rangedValuesNotMerged = createRangedValuesObject();
auto rangedValuesMerged = createRangedValuesObject();
const auto totalLengthBeforeInsert = getCumulativeRangeLengths (rangedValuesNotMerged);
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));
expectEquals (getCumulativeRangeLengths (rangedValuesNotMerged) - totalLengthBeforeInsert, numElemsToInsert);
rangedValuesMerged.insert<MergeEqualItems::yes> ({ Range<int64>::withStartAndLength (beginInsertionAt, numElemsToInsert) },
'a' + (char) random.nextInt (25));
expectEquals (getCumulativeRangeLengths (rangedValuesMerged) - totalLengthBeforeInsert, numElemsToInsert);
}
}
beginTest ("RangedValues::insert() with distinct value inside another range");
{
auto rangedValues = createRangedValuesObject();
expectEquals ((int64) rangedValues.size(), (int64) 3);
rangedValues.insert ({ 2, 4 }, 'd');
expectEquals ((int64) rangedValues.size(), (int64) 5);
expectRangedValuesItem (rangedValues.getItem (0), { 0, 2 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 2, 4 }, 'd');
expectRangedValuesItem (rangedValues.getItem (2), { 4, 12 }, 'a');
expectRangedValuesItem (rangedValues.getItem (3), { 13, 22 }, 'b');
expectRangedValuesItem (rangedValues.getItem (4), { 25, 32 }, 'c');
}
beginTest ("RangedValues::insert() with same value inside another range");
{
{
auto rangedValues = createRangedValuesObject();
expectEquals ((int64) rangedValues.size(), (int64) 3);
rangedValues.insert<MergeEqualItems::yes> ({ 2, 4 }, 'a');
expectEquals ((int64) rangedValues.size(), (int64) 3);
expectRangedValuesItem (rangedValues.getItem (0), { 0, 12 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 13, 22 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 25, 32 }, 'c');
}
{
auto rangedValues = createRangedValuesObject();
expectEquals ((int64) rangedValues.size(), (int64) 3);
rangedValues.insert<MergeEqualItems::no> ({ 2, 4 }, 'a');
expectEquals ((int64) rangedValues.size(), (int64) 5);
expectRangedValuesItem (rangedValues.getItem (0), { 0, 2 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 2, 4 }, 'a');
expectRangedValuesItem (rangedValues.getItem (2), { 4, 12 }, 'a');
expectRangedValuesItem (rangedValues.getItem (3), { 13, 22 }, 'b');
expectRangedValuesItem (rangedValues.getItem (4), { 25, 32 }, 'c');
}
}
beginTest ("RangedValues::insert() - inserting an empty Range should be a no-op");
{
{
RangedValues<char> emptyRangedValues;
auto ops = emptyRangedValues.insert ({ 0, 0 }, 'a');
expect (emptyRangedValues.isEmpty());
expect (ops.empty());
}
{
RangedValues<char> rangedValues;
rangedValues.set ({ 0, 10 }, 'a');
auto ops = rangedValues.insert ({ 0, 0 }, 'a');
expect (ops.empty());
expectEquals ((int64) rangedValues.size(), (int64) 1);
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
}
}
}
};
class IntersectingRangedValuesTests : public RangesTestsBase
{
public:
IntersectingRangedValuesTests() : RangesTestsBase ("IntersectingRangedValues", UnitTestCategories::containers) {}
void runTest() override
{
beginTest ("IntersectingRangedValuesTests - iterating over multiple RangedValues");
{
RangedValues<int> rv1;
rv1.set ({ 3, 8}, 1);
rv1.set ({ 9, 16}, 2);
rv1.set ({ 30, 40}, 3);
RangedValues<int> rv2;
rv2.set ({ 0, 4}, 7);
rv2.set ({ 4, 6}, 11);
rv2.set ({ 6, 25}, 13);
rv2.set ({ 27, 55}, 17);
RangedValues<int> rv3;
rv3.set ({ -2, 10}, -1);
rv3.set ({ 15, 19}, -2);
rv3.set ({ 22, 36}, -3);
int iteration = 0;
for (const auto [range, v1, v2, v3] : makeIntersectingRangedValues (&rv1, &rv2, &rv3))
{
if (iteration == 0)
{
expectRange (range, Range<int64> { 3, 4 });
expect (v1 == 1 && v2 == 7 && v3 == -1);
}
if (iteration == 1)
{
expectRange (range, Range<int64> { 4, 6 });
expect (v1 == 1 && v2 == 11 && v3 == -1);
}
if (iteration == 2)
{
expectRange (range, Range<int64> { 6, 8 });
expect (v1 == 1 && v2 == 13 && v3 == -1);
}
if (iteration == 3)
{
expectRange (range, Range<int64> { 9, 10 });
expect (v1 == 2 && v2 == 13 && v3 == -1);
}
if (iteration == 4)
{
expectRange (range, Range<int64> { 15, 16 });
expect (v1 == 2 && v2 == 13 && v3 == -2);
}
if (iteration == 5)
{
expectRange (range, Range<int64> { 30, 36 });
expect (v1 == 3 && v2 == 17 && v3 == -3);
}
++iteration;
}
expectEquals (iteration, 6);
}
}
};
static RangesTests rangesTests;
static RangedValuesTests rangedValuesTests;
static IntersectingRangedValuesTests intersectingRangedValuesTests;
#endif
} // namespace juce::detail

File diff suppressed because it is too large Load diff