Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
BiDiLayoutEngine.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include <algorithm>
7
9#include "Stroika/Foundation/Containers/Support/ReserveTweaks.h"
10
11#include "BiDiLayoutEngine.h"
12
13using std::byte;
14
15using namespace Stroika::Foundation;
16
17using namespace Stroika::Frameworks;
18using namespace Stroika::Frameworks::Led;
19
20/*
21 * This hack reverses the direction results returned by this class for basic text parsing/layout. This is
22 * useful to debug/test - cuz then I can see ENGLISH text (words) displayed RTL (right to left) - which I can
23 * better identify and see if they are working properly.
24 *
25 * Note that this ONLY reverses directions WITHIN a run - not the order of the runs. Thats still good enough
26 * to debug most display issues.
27 */
28#ifndef qDebugHack_ReverseDirections_
29#define qDebugHack_ReverseDirections_ 0
30#endif
31
32/*
33 * This hack treats all english aplhabetic upper case characters as RTL characters, and lower case characters
34 * as LTR characters. This hack COULD be improved to actually PRESERVE the original characters in the virtual
35 * output, but that hasn't been done yet.
36 */
37#ifndef qDebugHack_UpperCaseCharsTratedAsRTL_
38#define qDebugHack_UpperCaseCharsTratedAsRTL_ 0
39#endif
40
41/*
42 ********************************************************************************
43 ******************************** TextLayoutBlock *******************************
44 ********************************************************************************
45 */
46/*
47@METHOD: TextLayoutBlock::GetCharacterDirection
48@DESCRIPTION:
49*/
50TextDirection TextLayoutBlock::GetCharacterDirection (size_t realCharOffset) const
51{
52 vector<ScriptRunElt> runs = GetScriptRuns ();
53 for (auto i = runs.begin (); i != runs.end (); ++i) {
54 if ((*i).fRealStart <= realCharOffset and realCharOffset < (*i).fRealEnd) {
55 return (*i).fDirection;
56 }
57 }
58 Assert (false);
59 return eLeftToRight;
60}
61
62/*
63@METHOD: TextLayoutBlock::MapRealOffsetToVirtual
64@DESCRIPTION:
65*/
66size_t TextLayoutBlock::MapRealOffsetToVirtual (size_t /*i*/) const
67{
68 Assert (false); //NYI
69 return 0;
70}
71
72/*
73@METHOD: TextLayoutBlock::MapVirtualOffsetToReal
74@DESCRIPTION:
75*/
76size_t TextLayoutBlock::MapVirtualOffsetToReal (size_t i) const
77{
78 Require (i < GetTextLength ());
79 vector<ScriptRunElt> runs = GetScriptRuns ();
80 for (auto runIt = runs.begin (); runIt != runs.end (); ++runIt) {
81 const ScriptRunElt& se = *runIt;
82 if (se.fVirtualStart <= i and i < se.fVirtualEnd) {
83 if (se.fDirection == eLeftToRight) {
84 size_t result = (i - se.fVirtualStart) + se.fRealStart;
85 Ensure (result < GetTextLength ());
86 return (result);
87 }
88 else {
89 size_t segLen = se.fRealEnd - se.fRealStart;
90 size_t result = (segLen - (i - se.fVirtualStart)) - 1 + se.fRealStart;
91 Ensure (result < GetTextLength ());
92 return (result);
93 }
94 }
95 }
96 Assert (false); // BAD INDEX
97 return 0;
98}
99
100void TextLayoutBlock::CopyOutRealText (const ScriptRunElt& scriptRunElt, Led_tChar* buf) const
101{
102 const Led_tChar* s = nullptr;
103 const Led_tChar* e = nullptr;
104 PeekAtRealText (scriptRunElt, &s, &e);
105 copy (s, e, buf);
106}
107
108void TextLayoutBlock::CopyOutVirtualText (const ScriptRunElt& scriptRunElt, Led_tChar* buf) const
109{
110 const Led_tChar* s = nullptr;
111 const Led_tChar* e = nullptr;
112 PeekAtVirtualText (scriptRunElt, &s, &e);
113 copy (s, e, buf);
114}
115
116void TextLayoutBlock::PeekAtRealText (const ScriptRunElt& scriptRunElt, const Led_tChar** startText, const Led_tChar** endText) const
117{
118 RequireNotNull (startText);
119 RequireNotNull (endText);
120 PeekAtRealText_ (startText, endText);
121 *startText += scriptRunElt.fRealStart;
122 size_t len = scriptRunElt.fRealEnd - scriptRunElt.fRealStart;
123 Assert (*endText - *startText >= static_cast<ptrdiff_t> (len)); // make sure we are SHRINKING the text and not making it point past its end
124 *endText = *startText + len;
125}
126
127Led_tString TextLayoutBlock::GetRealText () const
128{
129 const Led_tChar* s = nullptr;
130 const Led_tChar* e = nullptr;
131 PeekAtRealText_ (&s, &e);
132 return Led_tString{s, e};
133}
134
135Led_tString TextLayoutBlock::GetRealText (const ScriptRunElt& scriptRunElt) const
136{
137 const Led_tChar* s = nullptr;
138 const Led_tChar* e = nullptr;
139 PeekAtRealText (scriptRunElt, &s, &e);
140 return Led_tString{s, e};
141}
142
143void TextLayoutBlock::PeekAtVirtualText (const ScriptRunElt& scriptRunElt, const Led_tChar** startText, const Led_tChar** endText) const
144{
145 RequireNotNull (startText);
146 RequireNotNull (endText);
147 PeekAtVirtualText_ (startText, endText);
148 *startText += scriptRunElt.fVirtualStart;
149 size_t len = scriptRunElt.fVirtualEnd - scriptRunElt.fVirtualStart;
150 Assert (*endText - *startText >= static_cast<ptrdiff_t> (len)); // make sure we are SHRINKING the text and not making it point past its end
151 *endText = *startText + len;
152}
153
154Led_tString TextLayoutBlock::GetVirtualText () const
155{
156 const Led_tChar* s = nullptr;
157 const Led_tChar* e = nullptr;
158 PeekAtVirtualText_ (&s, &e);
159 return Led_tString{s, e};
160}
161
162Led_tString TextLayoutBlock::GetVirtualText (const ScriptRunElt& scriptRunElt) const
163{
164 const Led_tChar* s = nullptr;
165 const Led_tChar* e = nullptr;
166 PeekAtVirtualText (scriptRunElt, &s, &e);
167 return Led_tString{s, e};
168}
169
170#if qStroika_Foundation_Debug_AssertionsChecked
171void TextLayoutBlock::Invariant_ () const
172{
173 size_t len = GetTextLength ();
174
175 vector<ScriptRunElt> scriptRuns = GetScriptRuns ();
176
177 // Make sure each scriptrun elt is non-empty
178 {
179 for (auto j = scriptRuns.begin (); j != scriptRuns.end (); ++j) {
180 Assert ((*j).fRealStart < len);
181 Assert ((*j).fRealEnd <= len);
182 Assert ((*j).fVirtualStart < len);
183 Assert ((*j).fVirtualEnd <= len);
184 Assert ((*j).fRealStart < (*j).fRealEnd); // no empty elements
185 }
186 }
187
188 /*
189 * Validate the fScriptRuns list. Make sure it completely covers the real and virtual range. Make
190 * sure each element has exactly LENGTH spaces used up for the real text and the virtual text.
191 * That - by itself - assures also that there are no overlapping elements.
192 */
193 {
194 for (size_t i = 0; i < len; ++i) {
195 // Assure pos 'i' found in source and virtual
196 bool foundReal = false;
197 bool foundVirtual = false;
198 size_t nRealFound = 0;
199 size_t nVirtualFound = 0;
200 for (auto j = scriptRuns.begin (); j != scriptRuns.end (); ++j) {
201 if ((*j).fRealStart <= i and i < (*j).fRealEnd) {
202 Assert (not foundReal);
203 foundReal = true;
204 }
205 Assert ((*j).fVirtualStart < (*j).fVirtualEnd); // no empty elements
206 if ((*j).fVirtualStart <= i and i < (*j).fVirtualEnd) {
207 Assert (not foundVirtual);
208 foundVirtual = true;
209 }
210 nRealFound += (*j).fRealEnd - (*j).fRealStart;
211 nVirtualFound += (*j).fVirtualEnd - (*j).fVirtualStart;
212 }
213 Assert (foundReal);
214 Assert (foundVirtual);
215 Assert (nRealFound == len);
216 Assert (nVirtualFound == len);
217 }
218 }
219}
220#endif
221
222bool TextLayoutBlock::operator== (const TextLayoutBlock& rhs) const
223{
224 if (this->GetTextLength () != rhs.GetTextLength ()) {
225 return false;
226 }
227 if (this->GetRealText () != rhs.GetRealText ()) {
228 return false;
229 }
230 if (this->GetVirtualText () != rhs.GetVirtualText ()) {
231 return false;
232 }
233 vector<TextLayoutBlock::ScriptRunElt> lhsSR = this->GetScriptRuns ();
234 vector<TextLayoutBlock::ScriptRunElt> rhsSR = rhs.GetScriptRuns ();
235 if (lhsSR.size () != rhsSR.size ()) {
236 return false;
237 }
238 for (size_t i = 0; i < lhsSR.size (); ++i) {
239 if (lhsSR[i] != rhsSR[i]) {
240 return false;
241 }
242 }
243 return true;
244}
245
246/*
247 ********************************************************************************
248 ****************************** TextLayoutBlock_Basic ***************************
249 ********************************************************************************
250 */
251TextLayoutBlock_Basic::TextLayoutBlock_Basic (const Led_tChar* realText, const Led_tChar* realTextEnd)
252 : fTextLength{0}
253 , fRealText{0}
254 , fVirtualText{0}
255 , fScriptRuns{}
256{
257 Construct (realText, realTextEnd, nullptr);
258}
259
260TextLayoutBlock_Basic::TextLayoutBlock_Basic (const Led_tChar* realText, const Led_tChar* realTextEnd, TextDirection initialDirection)
261 : fTextLength{0}
262 , fRealText{0}
263 , fVirtualText{0}
264 , fScriptRuns{}
265{
266 Construct (realText, realTextEnd, &initialDirection);
267}
268
269void TextLayoutBlock_Basic::Construct (const Led_tChar* realText, const Led_tChar* realTextEnd, [[maybe_unused]] const TextDirection* initialDirection)
270{
271 size_t textLength = realTextEnd - realText;
272 fTextLength = textLength;
273 fRealText.GrowToSize (textLength);
274 fVirtualText.GrowToSize (textLength);
275 copy (realText, realText + textLength, static_cast<Led_tChar*> (fRealText));
276
277#if qDebugHack_UpperCaseCharsTratedAsRTL_
278 for (size_t i = 0; i < textLength; ++i) {
279 if ('A' <= fRealText[i] and fRealText[i] <= 'Z') {
280 fRealText[i] = 0xfe7d; // random arabic character
281 }
282 }
283#endif
284
285 // No bidirectional layout engine: each paragraph is one left-to-right run - see https://github.com/SophistSolutions/Stroika/issues/1182
286 Construct_Default ();
287
288 if constexpr (qDebugHack_ReverseDirections_) {
289 for (auto i = fScriptRuns.begin (); i != fScriptRuns.end (); ++i) {
290 ScriptRunElt& se = *i;
291 TextDirection newDir = (se.fDirection == eLeftToRight) ? eRightToLeft : eLeftToRight;
292 se.fDirection = newDir;
293 // now reverse the virtual text in the run...
294 size_t runLen = se.fVirtualEnd - se.fVirtualStart;
295 Memory::StackBuffer<Led_tChar> reverseBuf{runLen};
296 for (size_t j = se.fVirtualStart; j < se.fVirtualEnd; ++j) {
297 reverseBuf[runLen - 1 - (j - se.fVirtualStart)] = fVirtualText[j];
298 }
299 copy (static_cast<Led_tChar*> (reverseBuf), static_cast<Led_tChar*> (reverseBuf) + runLen,
300 static_cast<Led_tChar*> (fVirtualText) + se.fVirtualStart);
301 }
302 }
303
304 Invariant ();
305}
306
307void TextLayoutBlock_Basic::Construct_Default ()
308{
309 // Only do something for UNICODE/wide-chars - otherwise - just copy the chars by value...
310 copy (static_cast<Led_tChar*> (fRealText), static_cast<Led_tChar*> (fRealText) + fTextLength, static_cast<Led_tChar*> (fVirtualText));
311 if (fTextLength != 0) {
312 ScriptRunElt thisCharElt;
313 thisCharElt.fDirection = eLeftToRight;
314 thisCharElt.fRealStart = 0;
315 thisCharElt.fRealEnd = fTextLength;
316 thisCharElt.fVirtualStart = 0;
317 thisCharElt.fVirtualEnd = fTextLength;
318 fScriptRuns.push_back (thisCharElt);
319 }
320}
321
322void TextLayoutBlock_Basic::PeekAtRealText_ (const Led_tChar** startText, const Led_tChar** endText) const
323{
324 RequireNotNull (startText);
325 RequireNotNull (endText);
326 *startText = fRealText.data ();
327 *endText = fRealText.data () + fTextLength;
328}
329
330void TextLayoutBlock_Basic::PeekAtVirtualText_ (const Led_tChar** startText, const Led_tChar** endText) const
331{
332 RequireNotNull (startText);
333 RequireNotNull (endText);
334 *startText = fVirtualText.data ();
335 *endText = fVirtualText.data () + fTextLength;
336}
337
338vector<TextLayoutBlock::ScriptRunElt> TextLayoutBlock_Basic::GetScriptRuns () const
339{
340 return fScriptRuns;
341}
342
343/*
344 ********************************************************************************
345 ***************************** TextLayoutBlock_Copy *****************************
346 ********************************************************************************
347 */
348TextLayoutBlock_Copy::TextLayoutBlock_Copy (const TextLayoutBlock& from)
349{
350 /*
351 * Compute size - and then copy all the data into a continguous block of RAM.
352 */
353 size_t strLength = from.GetTextLength ();
354 vector<ScriptRunElt> scriptRuns = from.GetScriptRuns ();
355
356 // compute needed size
357 size_t neededSize = sizeof (BlockRep);
358 neededSize += sizeof (Led_tChar) * strLength; // fRealText
359 neededSize += sizeof (Led_tChar) * strLength; // fVirtualText
360 neededSize += sizeof (ScriptRunElt) * scriptRuns.size (); // fScriptRuns
361 auto deleter = [] (BlockRep* br) { delete[] (reinterpret_cast<byte*> (br)); };
362 fRep = shared_ptr<BlockRep>{reinterpret_cast<BlockRep*> (new byte[neededSize]), deleter};
363
364 fRep->fTextLength = strLength;
365
366 fRep->fRealText = reinterpret_cast<Led_tChar*> (fRep.get () + 1);
367 {
368 const Led_tChar* s = nullptr;
369 const Led_tChar* e = nullptr;
370 from.PeekAtRealText_ (&s, &e);
371 copy (s, e, const_cast<Led_tChar*> (fRep->fRealText));
372 }
373
374 fRep->fVirtualText = fRep->fRealText + strLength;
375 {
376 const Led_tChar* s = nullptr;
377 const Led_tChar* e = nullptr;
378 from.PeekAtVirtualText_ (&s, &e);
379 copy (s, e, const_cast<Led_tChar*> (fRep->fVirtualText));
380 }
381
382 fRep->fScriptRuns = reinterpret_cast<const ScriptRunElt*> (fRep->fVirtualText + strLength);
383 fRep->fScriptRunsEnd = fRep->fScriptRuns + scriptRuns.size ();
384 copy (scriptRuns.begin (), scriptRuns.end (), const_cast<ScriptRunElt*> (fRep->fScriptRuns));
385}
386
387TextLayoutBlock_Copy::TextLayoutBlock_Copy (const TextLayoutBlock_Copy& from)
388 : inherited (from)
389 , fRep (from.fRep)
390{
391}
392
393void TextLayoutBlock_Copy::PeekAtRealText_ (const Led_tChar** startText, const Led_tChar** endText) const
394{
395 RequireNotNull (startText);
396 RequireNotNull (endText);
397 *startText = fRep->fRealText;
398 *endText = fRep->fRealText + fRep->fTextLength;
399}
400
401void TextLayoutBlock_Copy::PeekAtVirtualText_ (const Led_tChar** startText, const Led_tChar** endText) const
402{
403 RequireNotNull (startText);
404 RequireNotNull (endText);
405 *startText = fRep->fVirtualText;
406 *endText = fRep->fVirtualText + fRep->fTextLength;
407}
408
409vector<TextLayoutBlock_Copy::ScriptRunElt> TextLayoutBlock_Copy::GetScriptRuns () const
410{
411 return vector<ScriptRunElt> (fRep->fScriptRuns, fRep->fScriptRunsEnd);
412}
413
414/*
415 ********************************************************************************
416 ************************ TextLayoutBlock_Copy::BlockRep ************************
417 ********************************************************************************
418 */
419void TextLayoutBlock_Copy::BlockRep::operator delete (void* p)
420{
421 // Because we allocate using new byte[] in TextLayoutBlock_Copy::CTOR (see SPR#1596)
422 delete[] (reinterpret_cast<char*> (p));
423}
424
425/*
426 ********************************************************************************
427 *********************** TextLayoutBlock_VirtualSubset **************************
428 ********************************************************************************
429 */
430TextLayoutBlock_VirtualSubset::TextLayoutBlock_VirtualSubset (const TextLayoutBlock& subsetOf, size_t start, size_t end)
431 : fSubsetOf{subsetOf}
432 , fStart{start}
433 , fEnd{end}
434 , fRealText{end - start}
435{
436 vector<ScriptRunElt> origRuns = fSubsetOf.GetScriptRuns ();
437 size_t offsetSoFar = 0;
438 const Led_tChar* fullRealText = fSubsetOf.PeekAtRealText ();
439 for (auto i = origRuns.begin (); i != origRuns.end (); ++i) {
440 size_t offsetStart = (*i).fVirtualStart;
441 size_t offsetEnd = (*i).fVirtualEnd;
442
443 size_t runRelStart = max (offsetStart, static_cast<size_t> (fStart));
444 size_t runRelEnd = min (offsetEnd, static_cast<size_t> (fEnd));
445 if (runRelStart < runRelEnd) {
446 // then this is a run with some stuff in it
447 ScriptRunElt s = *i;
448 Assert (runRelStart >= fStart);
449 Assert (runRelEnd >= fStart);
450 s.fVirtualStart = runRelStart - fStart;
451 s.fVirtualEnd = runRelEnd - fStart;
452
453 /*
454 * Since the API of TextLayoutBlock assumes a contiguous piece of RAM for the REAL and Virtual text, and since
455 * the source text is no longer continguous (if we used the full buffer then its length wouldn't match the length
456 * of our virtual buffer), we must make a copy of the real text and use different adjusted offsets. This 'real' text then
457 * isn't so real after all. It might not even be in an order that would have produced the given virtual text. BUT - if one
458 * makes calls to get back the original real text for any given range of Virual text - you WILL at least get back the
459 * right text.
460 */
461 Assert (runRelEnd >= runRelStart);
462 size_t runEltLen = runRelEnd - runRelStart;
463 copy (fullRealText + s.fRealStart, fullRealText + s.fRealStart + runEltLen, fRealText.data () + offsetSoFar);
464 s.fRealStart = offsetSoFar;
465 offsetSoFar += runEltLen;
466 s.fRealEnd = offsetSoFar;
467 Containers::Support::ReserveTweaks::Reserve4Add1 (fScriptRuns);
468 fScriptRuns.push_back (s);
469 }
470 }
471
472 Invariant ();
473}
474
475void TextLayoutBlock_VirtualSubset::PeekAtRealText_ (const Led_tChar** startText, const Led_tChar** endText) const
476{
477 RequireNotNull (startText);
478 RequireNotNull (endText);
479 *startText = fRealText.data ();
480 *endText = *startText + (fEnd - fStart);
481}
482
483void TextLayoutBlock_VirtualSubset::PeekAtVirtualText_ (const Led_tChar** startText, const Led_tChar** endText) const
484{
485 RequireNotNull (startText);
486 RequireNotNull (endText);
487 fSubsetOf.PeekAtVirtualText_ (startText, endText);
488 *startText += fStart;
489 Assert (fStart <= fEnd);
490 Assert (*endText - *startText >= static_cast<int> (fEnd - fStart)); // make sure orig string >= shortened string
491 *endText = *startText + (fEnd - fStart);
492}
493
494vector<TextLayoutBlock::ScriptRunElt> TextLayoutBlock_VirtualSubset::GetScriptRuns () const
495{
496 return fScriptRuns;
497}
#define RequireNotNull(p)
Definition Assertions.h:350
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...