@@ -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
12393private:
@@ -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\xa1 X \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
295273static BidiTests bidiTests;
0 commit comments