mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Fix bidi ordering
This commit is contained in:
parent
69b2b3ff5d
commit
a0e429fdd0
1 changed files with 78 additions and 100 deletions
|
|
@ -57,67 +57,37 @@ public:
|
||||||
if (runs.empty())
|
if (runs.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
return computeResultVector (SBLineGetOffset (line.get()),
|
thread_local std::vector<size_t> codepointIndicesInVisualOrder;
|
||||||
SBLineGetLength (line.get()),
|
codepointIndicesInVisualOrder.clear();
|
||||||
SBParagraphGetBaseLevel (paragraph.get()),
|
codepointIndicesInVisualOrder.reserve ((size_t) SBLineGetLength (line.get()));
|
||||||
runs,
|
|
||||||
result);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void computeResultVector (SBUInteger offset,
|
jassert (SBLineGetOffset (line.get()) == 0);
|
||||||
SBUInteger length,
|
|
||||||
SBLevel baseLevel,
|
for (const auto& run : runs)
|
||||||
Span<const SBRun> runs,
|
|
||||||
std::vector<size_t>& result)
|
|
||||||
{
|
|
||||||
const auto level = [] (const SBRun& x)
|
|
||||||
{
|
{
|
||||||
return x.level;
|
const auto ltr = run.level % 2 == 0;
|
||||||
};
|
const auto increment = ltr ? 1 : -1;
|
||||||
|
auto start = (int) (ltr ? run.offset : run.offset + run.length - 1);
|
||||||
|
|
||||||
const auto high = level (*std::max_element (runs.begin(), runs.end(), [&] (const auto& a, const auto& b)
|
for (SBUInteger i = 0; i < run.length; ++i)
|
||||||
{
|
|
||||||
return level (a) < level (b);
|
|
||||||
}));
|
|
||||||
|
|
||||||
const auto pseudoLevel = [] (const SBRun& x)
|
|
||||||
{
|
|
||||||
const auto l = x.level;
|
|
||||||
return (l % 2) == 1 ? l : 0xff;
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto low = pseudoLevel (*std::min_element (runs.begin(), runs.end(), [&] (const auto& a, const auto& b)
|
|
||||||
{
|
|
||||||
return pseudoLevel (a) < pseudoLevel (b);
|
|
||||||
}));
|
|
||||||
|
|
||||||
result.resize (length);
|
|
||||||
std::iota (result.begin(), result.end(), offset);
|
|
||||||
|
|
||||||
for (auto currentLevel = high; currentLevel >= low; --currentLevel)
|
|
||||||
{
|
|
||||||
const auto doFlip = [&] (auto beginRuns, auto endRuns)
|
|
||||||
{
|
{
|
||||||
const auto getStartOfRunInResult = [&] (const auto runIterator)
|
codepointIndicesInVisualOrder.push_back ((size_t) start);
|
||||||
{
|
start += increment;
|
||||||
return runIterator == endRuns ? result.end()
|
}
|
||||||
: result.begin() + (ptrdiff_t) (runIterator->offset - offset);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (auto it = beginRuns; it != endRuns;)
|
|
||||||
{
|
|
||||||
const auto begin = std::find_if (it, endRuns, [&] (const SBRun& x) { return currentLevel <= x.level; });
|
|
||||||
it = std::find_if (begin, endRuns, [&] (const SBRun& x) { return x.level < currentLevel; });
|
|
||||||
|
|
||||||
std::reverse (getStartOfRunInResult (begin), getStartOfRunInResult (it));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (baseLevel % 2 == 0)
|
|
||||||
doFlip (runs.begin(), runs.end());
|
|
||||||
else
|
|
||||||
doFlip (std::make_reverse_iterator (runs.end()), std::make_reverse_iterator (runs.begin()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.assign (codepointIndicesInVisualOrder.size(), 0);
|
||||||
|
|
||||||
|
if (std::any_of (codepointIndicesInVisualOrder.begin(),
|
||||||
|
codepointIndicesInVisualOrder.end(),
|
||||||
|
[s = result.size()] (auto i) { return i >= s; }))
|
||||||
|
{
|
||||||
|
jassertfalse;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto [i, index] : enumerate (codepointIndicesInVisualOrder, size_t{}))
|
||||||
|
result[index] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -211,7 +181,8 @@ public:
|
||||||
{
|
{
|
||||||
const CharPointer_UTF8 text ("\xd9\x85\xd9\x85\xd9\x85 colour "
|
const CharPointer_UTF8 text ("\xd9\x85\xd9\x85\xd9\x85 colour "
|
||||||
"\xd9\x85\xd9\x85\xd9\x85\xd9\x85\xd9\x85\xd9\x85\xd9\x85\xd9\x85\n");
|
"\xd9\x85\xd9\x85\xd9\x85\xd9\x85\xd9\x85\xd9\x85\xd9\x85\xd9\x85\n");
|
||||||
const std::vector<size_t> result { 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 4, 5, 6, 7, 8, 9, 3, 2, 1, 0 };
|
const std::vector<size_t> result { 19, 18, 17, 16, 10, 11, 12, 13, 14, 15, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
|
||||||
|
|
||||||
expect (computeVisualOrder (text) == result);
|
expect (computeVisualOrder (text) == result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,26 +193,63 @@ public:
|
||||||
expect (computeVisualOrder (text) == result);
|
expect (computeVisualOrder (text) == result);
|
||||||
}
|
}
|
||||||
|
|
||||||
beginTest ("visual order core algorithm");
|
beginTest ("multi-level bidi text");
|
||||||
{
|
{
|
||||||
const char testInput[] { "DID YOU SAY 'he said \"car MEANS CAR\"'?" };
|
const CharPointer_UTF8 text ("LOOPS 4 \xd7\xa1X \xd7\xa1""4");
|
||||||
const int testLevels[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 1 };
|
const std::vector<size_t> result { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 11 };
|
||||||
const char expectedOutput[] { "?'he said \"RAC SNAEM car\"' YAS UOY DID" };
|
expect (computeVisualOrder (text) == result);
|
||||||
|
}
|
||||||
|
|
||||||
static_assert (std::size (testInput) == std::size (expectedOutput));
|
beginTest ("bidi text with 5 embedding levels");
|
||||||
static_assert (std::size (testInput) - 1 == std::size (testLevels)); // ignore null terminator
|
{
|
||||||
|
// pop directional formatting
|
||||||
|
String PDF { CharPointer_UTF8 { "\xe2\x80\xac" } };
|
||||||
|
|
||||||
const auto [baseLevel, runs] = createRunsFromLevels (testLevels);
|
// left to right override
|
||||||
|
String LRO { CharPointer_UTF8 { "\xe2\x80\xad" } };
|
||||||
|
|
||||||
std::vector<size_t> result;
|
// right to left override
|
||||||
BidiLine::computeResultVector (0, std::size (testLevels), baseLevel, runs, result);
|
String RLO { CharPointer_UTF8 { "\xe2\x80\xae" } };
|
||||||
|
|
||||||
std::vector<char> output;
|
const auto replacements = std::array {
|
||||||
|
std::make_pair (String { "[PDF]" }, PDF),
|
||||||
|
std::make_pair (String { "[LRO]" }, LRO),
|
||||||
|
std::make_pair (String { "[RLO]" }, RLO),
|
||||||
|
};
|
||||||
|
|
||||||
for (auto i : result)
|
auto getText = [&] (StringRef templateText)
|
||||||
output.push_back (testInput[i]);
|
{
|
||||||
|
String text = templateText;
|
||||||
|
|
||||||
expect (std::equal (output.begin(), output.end(), expectedOutput));
|
for (const auto& [placeholder, replacement] : replacements)
|
||||||
|
text = text.replace (placeholder, replacement);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CharPointer_UTF8 templ { "[RLO]DID YOU SAY '[LRO]he said \"[RLO][LRO]car[PDF] MEANS CAR[PDF]\"[PDF]'?[PDF]" };
|
||||||
|
const auto text = getText (templ);
|
||||||
|
const auto lookup = computeVisualOrder (text);
|
||||||
|
|
||||||
|
std::vector<juce_wchar> reorderedText ((size_t) text.length());
|
||||||
|
|
||||||
|
for (const auto [i, c] : enumerate (text, size_t{}))
|
||||||
|
reorderedText[lookup[i]] = c;
|
||||||
|
|
||||||
|
// The visual order of the control characters is not defined, so we can only compare the
|
||||||
|
// ascii part.
|
||||||
|
std::vector<juce_wchar> asciiPartOfReorderedText;
|
||||||
|
|
||||||
|
for (const auto c : reorderedText)
|
||||||
|
{
|
||||||
|
if (c >= 0x20 && c <= 0x7e)
|
||||||
|
asciiPartOfReorderedText.push_back (c);
|
||||||
|
}
|
||||||
|
|
||||||
|
const String expected { "?'he said \"RAC SNAEM car\"' YAS UOY DID" };
|
||||||
|
const String result { CharPointer_UTF32 { asciiPartOfReorderedText.data() }, asciiPartOfReorderedText.size() };
|
||||||
|
|
||||||
|
expect (result == expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,36 +268,6 @@ public:
|
||||||
line.computeVisualOrder (order);
|
line.computeVisualOrder (order);
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::pair<SBLevel, std::vector<SBRun>> createRunsFromLevels (Span<const int> levels)
|
|
||||||
{
|
|
||||||
std::vector<SBRun> runs;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < levels.size();)
|
|
||||||
{
|
|
||||||
const auto level = levels[i];
|
|
||||||
|
|
||||||
for (size_t j = i + 1; j < levels.size(); ++j)
|
|
||||||
{
|
|
||||||
const auto lastElement = j == levels.size() - 1;
|
|
||||||
const auto endIndex = lastElement ? j + 1 : j;
|
|
||||||
|
|
||||||
if (levels[j] != level || lastElement)
|
|
||||||
{
|
|
||||||
runs.push_back ({ (SBUInteger) i, (SBUInteger) (endIndex - i), (SBLevel) level });
|
|
||||||
i = endIndex;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto baseLevel = std::size (levels) == 0 ? 0 : *std::min_element (std::begin (levels), std::end (levels));
|
|
||||||
|
|
||||||
if (baseLevel % 2 != 0)
|
|
||||||
std::reverse (runs.begin(), runs.end());
|
|
||||||
|
|
||||||
return { (SBLevel) baseLevel, runs };
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static BidiTests bidiTests;
|
static BidiTests bidiTests;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue