Skip to content

Commit a0e429f

Browse files
committed
Fix bidi ordering
1 parent 69b2b3f commit a0e429f

File tree

1 file changed

+75
-97
lines changed

1 file changed

+75
-97
lines changed

modules/juce_graphics/unicode/juce_UnicodeBidi.cpp

Lines changed: 75 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -57,67 +57,37 @@ class BidiLine
5757
if (runs.empty())
5858
return;
5959

60-
return computeResultVector (SBLineGetOffset (line.get()),
61-
SBLineGetLength (line.get()),
62-
SBParagraphGetBaseLevel (paragraph.get()),
63-
runs,
64-
result);
65-
}
66-
67-
static void computeResultVector (SBUInteger offset,
68-
SBUInteger length,
69-
SBLevel baseLevel,
70-
Span<const SBRun> runs,
71-
std::vector<size_t>& result)
72-
{
73-
const auto level = [] (const SBRun& x)
74-
{
75-
return x.level;
76-
};
60+
thread_local std::vector<size_t> codepointIndicesInVisualOrder;
61+
codepointIndicesInVisualOrder.clear();
62+
codepointIndicesInVisualOrder.reserve ((size_t) SBLineGetLength (line.get()));
7763

78-
const auto high = level (*std::max_element (runs.begin(), runs.end(), [&] (const auto& a, const auto& b)
79-
{
80-
return level (a) < level (b);
81-
}));
64+
jassert (SBLineGetOffset (line.get()) == 0);
8265

83-
const auto pseudoLevel = [] (const SBRun& x)
66+
for (const auto& run : runs)
8467
{
85-
const auto l = x.level;
86-
return (l % 2) == 1 ? l : 0xff;
87-
};
68+
const auto ltr = run.level % 2 == 0;
69+
const auto increment = ltr ? 1 : -1;
70+
auto start = (int) (ltr ? run.offset : run.offset + run.length - 1);
8871

89-
const auto low = pseudoLevel (*std::min_element (runs.begin(), runs.end(), [&] (const auto& a, const auto& b)
90-
{
91-
return pseudoLevel (a) < pseudoLevel (b);
92-
}));
72+
for (SBUInteger i = 0; i < run.length; ++i)
73+
{
74+
codepointIndicesInVisualOrder.push_back ((size_t) start);
75+
start += increment;
76+
}
77+
}
9378

94-
result.resize (length);
95-
std::iota (result.begin(), result.end(), offset);
79+
result.assign (codepointIndicesInVisualOrder.size(), 0);
9680

97-
for (auto currentLevel = high; currentLevel >= low; --currentLevel)
81+
if (std::any_of (codepointIndicesInVisualOrder.begin(),
82+
codepointIndicesInVisualOrder.end(),
83+
[s = result.size()] (auto i) { return i >= s; }))
9884
{
99-
const auto doFlip = [&] (auto beginRuns, auto endRuns)
100-
{
101-
const auto getStartOfRunInResult = [&] (const auto runIterator)
102-
{
103-
return runIterator == endRuns ? result.end()
104-
: result.begin() + (ptrdiff_t) (runIterator->offset - offset);
105-
};
106-
107-
for (auto it = beginRuns; it != endRuns;)
108-
{
109-
const auto begin = std::find_if (it, endRuns, [&] (const SBRun& x) { return currentLevel <= x.level; });
110-
it = std::find_if (begin, endRuns, [&] (const SBRun& x) { return x.level < currentLevel; });
111-
112-
std::reverse (getStartOfRunInResult (begin), getStartOfRunInResult (it));
113-
}
114-
};
115-
116-
if (baseLevel % 2 == 0)
117-
doFlip (runs.begin(), runs.end());
118-
else
119-
doFlip (std::make_reverse_iterator (runs.end()), std::make_reverse_iterator (runs.begin()));
85+
jassertfalse;
86+
return;
12087
}
88+
89+
for (const auto [i, index] : enumerate (codepointIndicesInVisualOrder, size_t{}))
90+
result[index] = i;
12191
}
12292

12393
private:
@@ -211,7 +181,8 @@ class BidiTests : public UnitTest
211181
{
212182
const CharPointer_UTF8 text ("\xd9\x85\xd9\x85\xd9\x85 colour "
213183
"\xd9\x85\xd9\x85\xd9\x85\xd9\x85\xd9\x85\xd9\x85\xd9\x85\xd9\x85\n");
214-
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 };
184+
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 };
185+
215186
expect (computeVisualOrder (text) == result);
216187
}
217188

@@ -222,26 +193,63 @@ class BidiTests : public UnitTest
222193
expect (computeVisualOrder (text) == result);
223194
}
224195

225-
beginTest ("visual order core algorithm");
196+
beginTest ("multi-level bidi text");
226197
{
227-
const char testInput[] { "DID YOU SAY 'he said \"car MEANS CAR\"'?" };
228-
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 };
229-
const char expectedOutput[] { "?'he said \"RAC SNAEM car\"' YAS UOY DID" };
198+
const CharPointer_UTF8 text ("LOOPS 4 \xd7\xa1X \xd7\xa1""4");
199+
const std::vector<size_t> result { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 11 };
200+
expect (computeVisualOrder (text) == result);
201+
}
230202

231-
static_assert (std::size (testInput) == std::size (expectedOutput));
232-
static_assert (std::size (testInput) - 1 == std::size (testLevels)); // ignore null terminator
203+
beginTest ("bidi text with 5 embedding levels");
204+
{
205+
// pop directional formatting
206+
String PDF { CharPointer_UTF8 { "\xe2\x80\xac" } };
233207

234-
const auto [baseLevel, runs] = createRunsFromLevels (testLevels);
208+
// left to right override
209+
String LRO { CharPointer_UTF8 { "\xe2\x80\xad" } };
235210

236-
std::vector<size_t> result;
237-
BidiLine::computeResultVector (0, std::size (testLevels), baseLevel, runs, result);
211+
// right to left override
212+
String RLO { CharPointer_UTF8 { "\xe2\x80\xae" } };
238213

239-
std::vector<char> output;
214+
const auto replacements = std::array {
215+
std::make_pair (String { "[PDF]" }, PDF),
216+
std::make_pair (String { "[LRO]" }, LRO),
217+
std::make_pair (String { "[RLO]" }, RLO),
218+
};
240219

241-
for (auto i : result)
242-
output.push_back (testInput[i]);
220+
auto getText = [&] (StringRef templateText)
221+
{
222+
String text = templateText;
243223

244-
expect (std::equal (output.begin(), output.end(), expectedOutput));
224+
for (const auto& [placeholder, replacement] : replacements)
225+
text = text.replace (placeholder, replacement);
226+
227+
return text;
228+
};
229+
230+
const CharPointer_UTF8 templ { "[RLO]DID YOU SAY '[LRO]he said \"[RLO][LRO]car[PDF] MEANS CAR[PDF]\"[PDF]'?[PDF]" };
231+
const auto text = getText (templ);
232+
const auto lookup = computeVisualOrder (text);
233+
234+
std::vector<juce_wchar> reorderedText ((size_t) text.length());
235+
236+
for (const auto [i, c] : enumerate (text, size_t{}))
237+
reorderedText[lookup[i]] = c;
238+
239+
// The visual order of the control characters is not defined, so we can only compare the
240+
// ascii part.
241+
std::vector<juce_wchar> asciiPartOfReorderedText;
242+
243+
for (const auto c : reorderedText)
244+
{
245+
if (c >= 0x20 && c <= 0x7e)
246+
asciiPartOfReorderedText.push_back (c);
247+
}
248+
249+
const String expected { "?'he said \"RAC SNAEM car\"' YAS UOY DID" };
250+
const String result { CharPointer_UTF32 { asciiPartOfReorderedText.data() }, asciiPartOfReorderedText.size() };
251+
252+
expect (result == expected);
245253
}
246254
}
247255

@@ -260,36 +268,6 @@ class BidiTests : public UnitTest
260268
line.computeVisualOrder (order);
261269
return order;
262270
}
263-
264-
static std::pair<SBLevel, std::vector<SBRun>> createRunsFromLevels (Span<const int> levels)
265-
{
266-
std::vector<SBRun> runs;
267-
268-
for (size_t i = 0; i < levels.size();)
269-
{
270-
const auto level = levels[i];
271-
272-
for (size_t j = i + 1; j < levels.size(); ++j)
273-
{
274-
const auto lastElement = j == levels.size() - 1;
275-
const auto endIndex = lastElement ? j + 1 : j;
276-
277-
if (levels[j] != level || lastElement)
278-
{
279-
runs.push_back ({ (SBUInteger) i, (SBUInteger) (endIndex - i), (SBLevel) level });
280-
i = endIndex;
281-
break;
282-
}
283-
}
284-
}
285-
286-
const auto baseLevel = std::size (levels) == 0 ? 0 : *std::min_element (std::begin (levels), std::end (levels));
287-
288-
if (baseLevel % 2 != 0)
289-
std::reverse (runs.begin(), runs.end());
290-
291-
return { (SBLevel) baseLevel, runs };
292-
}
293271
};
294272

295273
static BidiTests bidiTests;

0 commit comments

Comments
 (0)