1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsBidiPresUtils.h"
8 
9 #include "mozilla/IntegerRange.h"
10 #include "mozilla/Maybe.h"
11 #include "mozilla/PresShell.h"
12 #include "mozilla/dom/Text.h"
13 
14 #include "gfxContext.h"
15 #include "nsFontMetrics.h"
16 #include "nsGkAtoms.h"
17 #include "nsPresContext.h"
18 #include "nsBidiUtils.h"
19 #include "nsCSSFrameConstructor.h"
20 #include "nsContainerFrame.h"
21 #include "nsInlineFrame.h"
22 #include "nsPlaceholderFrame.h"
23 #include "nsPointerHashKeys.h"
24 #include "nsFirstLetterFrame.h"
25 #include "nsUnicodeProperties.h"
26 #include "nsTextFrame.h"
27 #include "nsBlockFrame.h"
28 #include "nsIFrameInlines.h"
29 #include "nsStyleStructInlines.h"
30 #include "RubyUtils.h"
31 #include "nsRubyFrame.h"
32 #include "nsRubyBaseFrame.h"
33 #include "nsRubyTextFrame.h"
34 #include "nsRubyBaseContainerFrame.h"
35 #include "nsRubyTextContainerFrame.h"
36 #include <algorithm>
37 
38 #undef NOISY_BIDI
39 #undef REALLY_NOISY_BIDI
40 
41 using namespace mozilla;
42 
43 static const char16_t kSpace = 0x0020;
44 static const char16_t kZWSP = 0x200B;
45 static const char16_t kLineSeparator = 0x2028;
46 static const char16_t kObjectSubstitute = 0xFFFC;
47 static const char16_t kLRE = 0x202A;
48 static const char16_t kRLE = 0x202B;
49 static const char16_t kLRO = 0x202D;
50 static const char16_t kRLO = 0x202E;
51 static const char16_t kPDF = 0x202C;
52 static const char16_t kLRI = 0x2066;
53 static const char16_t kRLI = 0x2067;
54 static const char16_t kFSI = 0x2068;
55 static const char16_t kPDI = 0x2069;
56 // All characters with Bidi type Segment Separator or Block Separator
57 static const char16_t kSeparators[] = {
58     char16_t('\t'), char16_t('\r'),   char16_t('\n'), char16_t(0xb),
59     char16_t(0x1c), char16_t(0x1d),   char16_t(0x1e), char16_t(0x1f),
60     char16_t(0x85), char16_t(0x2029), char16_t(0)};
61 
62 #define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1)
63 
64 // This exists just to be a type; the value doesn't matter.
65 enum class BidiControlFrameType { Value };
66 
IsIsolateControl(char16_t aChar)67 static bool IsIsolateControl(char16_t aChar) {
68   return aChar == kLRI || aChar == kRLI || aChar == kFSI;
69 }
70 
71 // Given a ComputedStyle, return any bidi control character necessary to
72 // implement style properties that override directionality (i.e. if it has
73 // unicode-bidi:bidi-override, or text-orientation:upright in vertical
74 // writing mode) when applying the bidi algorithm.
75 //
76 // Returns 0 if no override control character is implied by this style.
GetBidiOverride(ComputedStyle * aComputedStyle)77 static char16_t GetBidiOverride(ComputedStyle* aComputedStyle) {
78   const nsStyleVisibility* vis = aComputedStyle->StyleVisibility();
79   if ((vis->mWritingMode == NS_STYLE_WRITING_MODE_VERTICAL_RL ||
80        vis->mWritingMode == NS_STYLE_WRITING_MODE_VERTICAL_LR) &&
81       vis->mTextOrientation == StyleTextOrientation::Upright) {
82     return kLRO;
83   }
84   const nsStyleTextReset* text = aComputedStyle->StyleTextReset();
85   if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_BIDI_OVERRIDE) {
86     return StyleDirection::Rtl == vis->mDirection ? kRLO : kLRO;
87   }
88   return 0;
89 }
90 
91 // Given a ComputedStyle, return any bidi control character necessary to
92 // implement style properties that affect bidi resolution (i.e. if it
93 // has unicode-bidiembed, isolate, or plaintext) when applying the bidi
94 // algorithm.
95 //
96 // Returns 0 if no control character is implied by the style.
97 //
98 // Note that GetBidiOverride and GetBidiControl need to be separate
99 // because in the case of unicode-bidi:isolate-override we need both
100 // FSI and LRO/RLO.
GetBidiControl(ComputedStyle * aComputedStyle)101 static char16_t GetBidiControl(ComputedStyle* aComputedStyle) {
102   const nsStyleVisibility* vis = aComputedStyle->StyleVisibility();
103   const nsStyleTextReset* text = aComputedStyle->StyleTextReset();
104   if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_EMBED) {
105     return StyleDirection::Rtl == vis->mDirection ? kRLE : kLRE;
106   }
107   if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_ISOLATE) {
108     if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_BIDI_OVERRIDE) {
109       // isolate-override
110       return kFSI;
111     }
112     // <bdi> element already has its directionality set from content so
113     // we never need to return kFSI.
114     return StyleDirection::Rtl == vis->mDirection ? kRLI : kLRI;
115   }
116   if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
117     return kFSI;
118   }
119   return 0;
120 }
121 
122 #ifdef DEBUG
AreContinuationsInOrder(nsIFrame * aFrame1,nsIFrame * aFrame2)123 static inline bool AreContinuationsInOrder(nsIFrame* aFrame1,
124                                            nsIFrame* aFrame2) {
125   nsIFrame* f = aFrame1;
126   do {
127     f = f->GetNextContinuation();
128   } while (f && f != aFrame2);
129   return !!f;
130 }
131 #endif
132 
133 struct MOZ_STACK_CLASS BidiParagraphData {
134   struct FrameInfo {
FrameInfoBidiParagraphData::FrameInfo135     FrameInfo(nsIFrame* aFrame, nsBlockInFlowLineIterator& aLineIter)
136         : mFrame(aFrame),
137           mBlockContainer(aLineIter.GetContainer()),
138           mInOverflow(aLineIter.GetInOverflow()) {}
139 
FrameInfoBidiParagraphData::FrameInfo140     explicit FrameInfo(BidiControlFrameType aValue)
141         : mFrame(NS_BIDI_CONTROL_FRAME),
142           mBlockContainer(nullptr),
143           mInOverflow(false) {}
144 
FrameInfoBidiParagraphData::FrameInfo145     FrameInfo()
146         : mFrame(nullptr), mBlockContainer(nullptr), mInOverflow(false) {}
147 
148     nsIFrame* mFrame;
149 
150     // The block containing mFrame (i.e., which continuation).
151     nsBlockFrame* mBlockContainer;
152 
153     // true if mFrame is in mBlockContainer's overflow lines, false if
154     // in primary lines
155     bool mInOverflow;
156   };
157 
158   nsAutoString mBuffer;
159   AutoTArray<char16_t, 16> mEmbeddingStack;
160   AutoTArray<FrameInfo, 16> mLogicalFrames;
161   nsDataHashtable<nsPtrHashKey<const nsIContent>, int32_t> mContentToFrameIndex;
162   // Cached presentation context for the frames we're processing.
163   nsPresContext* mPresContext;
164   bool mIsVisual;
165   bool mRequiresBidi;
166   nsBidiLevel mParaLevel;
167   nsIContent* mPrevContent;
168 
169   /**
170    * This class is designed to manage the process of mapping a frame to
171    * the line that it's in, when we know that (a) the frames we ask it
172    * about are always in the block's lines and (b) each successive frame
173    * we ask it about is the same as or after (in depth-first search
174    * order) the previous.
175    *
176    * Since we move through the lines at a different pace in Traverse and
177    * ResolveParagraph, we use one of these for each.
178    *
179    * The state of the mapping is also different between TraverseFrames
180    * and ResolveParagraph since since resolving can call functions
181    * (EnsureBidiContinuation or SplitInlineAncestors) that can create
182    * new frames and thus break lines.
183    *
184    * The TraverseFrames iterator is only used in some edge cases.
185    */
186   struct FastLineIterator {
FastLineIteratorBidiParagraphData::FastLineIterator187     FastLineIterator() : mPrevFrame(nullptr), mNextLineStart(nullptr) {}
188 
189     // These iterators *and* mPrevFrame track the line list that we're
190     // iterating over.
191     //
192     // mPrevFrame, if non-null, should be either the frame we're currently
193     // handling (in ResolveParagraph or TraverseFrames, depending on the
194     // iterator) or a frame before it, and is also guaranteed to either be in
195     // mCurrentLine or have been in mCurrentLine until recently.
196     //
197     // In case the splitting causes block frames to break lines, however, we
198     // also track the first frame of the next line.  If that changes, it means
199     // we've broken lines and we have to invalidate mPrevFrame.
200     nsBlockInFlowLineIterator mLineIterator;
201     nsIFrame* mPrevFrame;
202     nsIFrame* mNextLineStart;
203 
GetLineBidiParagraphData::FastLineIterator204     nsLineList::iterator GetLine() { return mLineIterator.GetLine(); }
205 
IsFrameInCurrentLineBidiParagraphData::FastLineIterator206     static bool IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter,
207                                      nsIFrame* aPrevFrame, nsIFrame* aFrame) {
208       MOZ_ASSERT(!aPrevFrame || aLineIter->GetLine()->Contains(aPrevFrame),
209                  "aPrevFrame must be in aLineIter's current line");
210       nsIFrame* endFrame = aLineIter->IsLastLineInList()
211                                ? nullptr
212                                : aLineIter->GetLine().next()->mFirstChild;
213       nsIFrame* startFrame =
214           aPrevFrame ? aPrevFrame : aLineIter->GetLine()->mFirstChild;
215       for (nsIFrame* frame = startFrame; frame && frame != endFrame;
216            frame = frame->GetNextSibling()) {
217         if (frame == aFrame) return true;
218       }
219       return false;
220     }
221 
FirstChildOfNextLineBidiParagraphData::FastLineIterator222     static nsIFrame* FirstChildOfNextLine(
223         nsBlockInFlowLineIterator& aIterator) {
224       const nsLineList::iterator line = aIterator.GetLine();
225       const nsLineList::iterator lineEnd = aIterator.End();
226       MOZ_ASSERT(line != lineEnd, "iterator should start off valid");
227       const nsLineList::iterator nextLine = line.next();
228 
229       return nextLine != lineEnd ? nextLine->mFirstChild : nullptr;
230     }
231 
232     // Advance line iterator to the line containing aFrame, assuming
233     // that aFrame is already in the line list our iterator is iterating
234     // over.
AdvanceToFrameBidiParagraphData::FastLineIterator235     void AdvanceToFrame(nsIFrame* aFrame) {
236       if (mPrevFrame && FirstChildOfNextLine(mLineIterator) != mNextLineStart) {
237         // Something has caused a line to split.  We need to invalidate
238         // mPrevFrame since it may now be in a *later* line, though it may
239         // still be in this line, so we need to start searching for it from
240         // the start of this line.
241         mPrevFrame = nullptr;
242       }
243       nsIFrame* child = aFrame;
244       nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
245       while (parent && !parent->IsBlockFrameOrSubclass()) {
246         child = parent;
247         parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
248       }
249       MOZ_ASSERT(parent, "aFrame is not a descendent of a block frame");
250       while (!IsFrameInCurrentLine(&mLineIterator, mPrevFrame, child)) {
251 #ifdef DEBUG
252         bool hasNext =
253 #endif
254             mLineIterator.Next();
255         MOZ_ASSERT(hasNext, "Can't find frame in lines!");
256         mPrevFrame = nullptr;
257       }
258       mPrevFrame = child;
259       mNextLineStart = FirstChildOfNextLine(mLineIterator);
260     }
261 
262     // Advance line iterator to the line containing aFrame, which may
263     // require moving forward into overflow lines or into a later
264     // continuation (or both).
AdvanceToLinesAndFrameBidiParagraphData::FastLineIterator265     void AdvanceToLinesAndFrame(const FrameInfo& aFrameInfo) {
266       if (mLineIterator.GetContainer() != aFrameInfo.mBlockContainer ||
267           mLineIterator.GetInOverflow() != aFrameInfo.mInOverflow) {
268         MOZ_ASSERT(
269             mLineIterator.GetContainer() == aFrameInfo.mBlockContainer
270                 ? (!mLineIterator.GetInOverflow() && aFrameInfo.mInOverflow)
271                 : (!mLineIterator.GetContainer() ||
272                    AreContinuationsInOrder(mLineIterator.GetContainer(),
273                                            aFrameInfo.mBlockContainer)),
274             "must move forwards");
275         nsBlockFrame* block = aFrameInfo.mBlockContainer;
276         nsLineList::iterator lines =
277             aFrameInfo.mInOverflow ? block->GetOverflowLines()->mLines.begin()
278                                    : block->LinesBegin();
279         mLineIterator =
280             nsBlockInFlowLineIterator(block, lines, aFrameInfo.mInOverflow);
281         mPrevFrame = nullptr;
282       }
283       AdvanceToFrame(aFrameInfo.mFrame);
284     }
285   };
286 
287   FastLineIterator mCurrentTraverseLine, mCurrentResolveLine;
288 
289 #ifdef DEBUG
290   // Only used for NOISY debug output.
291   // Matches the current TraverseFrames state, not the ResolveParagraph
292   // state.
293   nsBlockFrame* mCurrentBlock;
294 #endif
295 
BidiParagraphDataBidiParagraphData296   explicit BidiParagraphData(nsBlockFrame* aBlockFrame)
297       : mPresContext(aBlockFrame->PresContext()),
298         mIsVisual(mPresContext->IsVisualMode()),
299         mRequiresBidi(false),
300         mParaLevel(nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame->Style())),
301         mPrevContent(nullptr)
302 #ifdef DEBUG
303         ,
304         mCurrentBlock(aBlockFrame)
305 #endif
306   {
307     if (mParaLevel > 0) {
308       mRequiresBidi = true;
309     }
310 
311     if (mIsVisual) {
312       /**
313        * Drill up in content to detect whether this is an element that needs to
314        * be rendered with logical order even on visual pages.
315        *
316        * We always use logical order on form controls, firstly so that text
317        * entry will be in logical order, but also because visual pages were
318        * written with the assumption that even if the browser had no support
319        * for right-to-left text rendering, it would use native widgets with
320        * bidi support to display form controls.
321        *
322        * We also use logical order in XUL elements, since we expect that if a
323        * XUL element appears in a visual page, it will be generated by an XBL
324        * binding and contain localized text which will be in logical order.
325        */
326       for (nsIContent* content = aBlockFrame->GetContent(); content;
327            content = content->GetParent()) {
328         if (content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) ||
329             content->IsXULElement()) {
330           mIsVisual = false;
331           break;
332         }
333       }
334     }
335   }
336 
SetParaBidiParagraphData337   nsresult SetPara() {
338     return mPresContext->GetBidiEngine().SetPara(mBuffer.get(), BufferLength(),
339                                                  mParaLevel);
340   }
341 
342   /**
343    * mParaLevel can be NSBIDI_DEFAULT_LTR as well as NSBIDI_LTR or NSBIDI_RTL.
344    * GetParaLevel() returns the actual (resolved) paragraph level which is
345    * always either NSBIDI_LTR or NSBIDI_RTL
346    */
GetParaLevelBidiParagraphData347   nsBidiLevel GetParaLevel() {
348     nsBidiLevel paraLevel = mParaLevel;
349     if (paraLevel == NSBIDI_DEFAULT_LTR || paraLevel == NSBIDI_DEFAULT_RTL) {
350       paraLevel = mPresContext->GetBidiEngine().GetParaLevel();
351     }
352     return paraLevel;
353   }
354 
GetDirectionBidiParagraphData355   nsBidiDirection GetDirection() {
356     return mPresContext->GetBidiEngine().GetDirection();
357   }
358 
CountRunsBidiParagraphData359   nsresult CountRuns(int32_t* runCount) {
360     return mPresContext->GetBidiEngine().CountRuns(runCount);
361   }
362 
GetLogicalRunBidiParagraphData363   void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit,
364                      nsBidiLevel* aLevel) {
365     mPresContext->GetBidiEngine().GetLogicalRun(aLogicalStart, aLogicalLimit,
366                                                 aLevel);
367     if (mIsVisual) {
368       *aLevel = GetParaLevel();
369     }
370   }
371 
ResetDataBidiParagraphData372   void ResetData() {
373     mLogicalFrames.Clear();
374     mContentToFrameIndex.Clear();
375     mBuffer.SetLength(0);
376     mPrevContent = nullptr;
377     for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) {
378       mBuffer.Append(mEmbeddingStack[i]);
379       mLogicalFrames.AppendElement(FrameInfo(BidiControlFrameType::Value));
380     }
381   }
382 
AppendFrameBidiParagraphData383   void AppendFrame(nsIFrame* aFrame, FastLineIterator& aLineIter,
384                    nsIContent* aContent = nullptr) {
385     if (aContent) {
386       mContentToFrameIndex.Put(aContent, FrameCount());
387     }
388 
389     // We don't actually need to advance aLineIter to aFrame, since all we use
390     // from it is the block and is-overflow state, which are correct already.
391     mLogicalFrames.AppendElement(FrameInfo(aFrame, aLineIter.mLineIterator));
392   }
393 
AdvanceAndAppendFrameBidiParagraphData394   void AdvanceAndAppendFrame(nsIFrame** aFrame, FastLineIterator& aLineIter,
395                              nsIFrame** aNextSibling) {
396     nsIFrame* frame = *aFrame;
397     nsIFrame* nextSibling = *aNextSibling;
398 
399     frame = frame->GetNextContinuation();
400     if (frame) {
401       AppendFrame(frame, aLineIter, nullptr);
402 
403       /*
404        * If we have already overshot the saved next-sibling while
405        * scanning the frame's continuations, advance it.
406        */
407       if (frame == nextSibling) {
408         nextSibling = frame->GetNextSibling();
409       }
410     }
411 
412     *aFrame = frame;
413     *aNextSibling = nextSibling;
414   }
415 
GetLastFrameForContentBidiParagraphData416   int32_t GetLastFrameForContent(nsIContent* aContent) {
417     int32_t index = 0;
418     mContentToFrameIndex.Get(aContent, &index);
419     return index;
420   }
421 
FrameCountBidiParagraphData422   int32_t FrameCount() { return mLogicalFrames.Length(); }
423 
BufferLengthBidiParagraphData424   int32_t BufferLength() { return mBuffer.Length(); }
425 
FrameAtBidiParagraphData426   nsIFrame* FrameAt(int32_t aIndex) { return mLogicalFrames[aIndex].mFrame; }
427 
FrameInfoAtBidiParagraphData428   const FrameInfo& FrameInfoAt(int32_t aIndex) {
429     return mLogicalFrames[aIndex];
430   }
431 
AppendUnicharBidiParagraphData432   void AppendUnichar(char16_t aCh) { mBuffer.Append(aCh); }
433 
AppendStringBidiParagraphData434   void AppendString(const nsDependentSubstring& aString) {
435     mBuffer.Append(aString);
436   }
437 
AppendControlCharBidiParagraphData438   void AppendControlChar(char16_t aCh) {
439     mLogicalFrames.AppendElement(FrameInfo(BidiControlFrameType::Value));
440     AppendUnichar(aCh);
441   }
442 
PushBidiControlBidiParagraphData443   void PushBidiControl(char16_t aCh) {
444     AppendControlChar(aCh);
445     mEmbeddingStack.AppendElement(aCh);
446   }
447 
AppendPopCharBidiParagraphData448   void AppendPopChar(char16_t aCh) {
449     AppendControlChar(IsIsolateControl(aCh) ? kPDI : kPDF);
450   }
451 
PopBidiControlBidiParagraphData452   void PopBidiControl(char16_t aCh) {
453     MOZ_ASSERT(mEmbeddingStack.Length(), "embedding/override underflow");
454     MOZ_ASSERT(aCh == mEmbeddingStack.LastElement());
455     AppendPopChar(aCh);
456     mEmbeddingStack.TruncateLength(mEmbeddingStack.Length() - 1);
457   }
458 
ClearBidiControlsBidiParagraphData459   void ClearBidiControls() {
460     for (char16_t c : Reversed(mEmbeddingStack)) {
461       AppendPopChar(c);
462     }
463   }
464 };
465 
466 struct MOZ_STACK_CLASS BidiLineData {
467   AutoTArray<nsIFrame*, 16> mLogicalFrames;
468   AutoTArray<nsIFrame*, 16> mVisualFrames;
469   AutoTArray<int32_t, 16> mIndexMap;
470   AutoTArray<uint8_t, 16> mLevels;
471   bool mIsReordered;
472 
BidiLineDataBidiLineData473   BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) {
474     /**
475      * Initialize the logically-ordered array of frames using the top-level
476      * frames of a single line
477      */
478     bool isReordered = false;
479     bool hasRTLFrames = false;
480     bool hasVirtualControls = false;
481 
482     auto appendFrame = [&](nsIFrame* frame, nsBidiLevel level) {
483       mLogicalFrames.AppendElement(frame);
484       mLevels.AppendElement(level);
485       mIndexMap.AppendElement(0);
486       if (IS_LEVEL_RTL(level)) {
487         hasRTLFrames = true;
488       }
489     };
490 
491     bool firstFrame = true;
492     for (nsIFrame* frame = aFirstFrameOnLine; frame && aNumFramesOnLine--;
493          frame = frame->GetNextSibling()) {
494       FrameBidiData bidiData = nsBidiPresUtils::GetFrameBidiData(frame);
495       // Ignore virtual control before the first frame. Doing so should
496       // not affect the visual result, but could avoid running into the
497       // stripping code below for many cases.
498       if (!firstFrame && bidiData.precedingControl != kBidiLevelNone) {
499         appendFrame(NS_BIDI_CONTROL_FRAME, bidiData.precedingControl);
500         hasVirtualControls = true;
501       }
502       appendFrame(frame, bidiData.embeddingLevel);
503       firstFrame = false;
504     }
505 
506     // Reorder the line
507     nsBidi::ReorderVisual(mLevels.Elements(), FrameCount(),
508                           mIndexMap.Elements());
509 
510     // Strip virtual frames
511     if (hasVirtualControls) {
512       auto originalCount = mLogicalFrames.Length();
513       AutoTArray<int32_t, 16> realFrameMap;
514       realFrameMap.SetCapacity(originalCount);
515       size_t count = 0;
516       for (auto i : IntegerRange(originalCount)) {
517         if (mLogicalFrames[i] == NS_BIDI_CONTROL_FRAME) {
518           realFrameMap.AppendElement(-1);
519         } else {
520           mLogicalFrames[count] = mLogicalFrames[i];
521           mLevels[count] = mLevels[i];
522           realFrameMap.AppendElement(count);
523           count++;
524         }
525       }
526       // Only keep index map for real frames.
527       for (size_t i = 0, j = 0; i < originalCount; ++i) {
528         auto newIndex = realFrameMap[mIndexMap[i]];
529         if (newIndex != -1) {
530           mIndexMap[j] = newIndex;
531           j++;
532         }
533       }
534       mLogicalFrames.TruncateLength(count);
535       mLevels.TruncateLength(count);
536       mIndexMap.TruncateLength(count);
537     }
538 
539     for (int32_t i = 0; i < FrameCount(); i++) {
540       mVisualFrames.AppendElement(LogicalFrameAt(mIndexMap[i]));
541       if (i != mIndexMap[i]) {
542         isReordered = true;
543       }
544     }
545 
546     // If there's an RTL frame, assume the line is reordered
547     mIsReordered = isReordered || hasRTLFrames;
548   }
549 
FrameCountBidiLineData550   int32_t FrameCount() const { return mLogicalFrames.Length(); }
551 
LogicalFrameAtBidiLineData552   nsIFrame* LogicalFrameAt(int32_t aIndex) const {
553     return mLogicalFrames[aIndex];
554   }
555 
VisualFrameAtBidiLineData556   nsIFrame* VisualFrameAt(int32_t aIndex) const {
557     return mVisualFrames[aIndex];
558   }
559 };
560 
561 #ifdef DEBUG
562 extern "C" {
DumpFrameArray(const nsTArray<nsIFrame * > & aFrames)563 void MOZ_EXPORT DumpFrameArray(const nsTArray<nsIFrame*>& aFrames) {
564   for (nsIFrame* frame : aFrames) {
565     if (frame == NS_BIDI_CONTROL_FRAME) {
566       fprintf_stderr(stderr, "(Bidi control frame)\n");
567     } else {
568       frame->List();
569     }
570   }
571 }
572 
DumpBidiLine(BidiLineData * aData,bool aVisualOrder)573 void MOZ_EXPORT DumpBidiLine(BidiLineData* aData, bool aVisualOrder) {
574   DumpFrameArray(aVisualOrder ? aData->mVisualFrames : aData->mLogicalFrames);
575 }
576 }
577 #endif
578 
579 /* Some helper methods for Resolve() */
580 
581 // Should this frame be split between text runs?
IsBidiSplittable(nsIFrame * aFrame)582 static bool IsBidiSplittable(nsIFrame* aFrame) {
583   MOZ_ASSERT(aFrame);
584   // Bidi inline containers should be split, unless they're line frames.
585   LayoutFrameType frameType = aFrame->Type();
586   return (aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer) &&
587           frameType != LayoutFrameType::Line) ||
588          frameType == LayoutFrameType::Text;
589 }
590 
591 // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
IsBidiLeaf(const nsIFrame * aFrame)592 static bool IsBidiLeaf(const nsIFrame* aFrame) {
593   nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
594   if (kid) {
595     if (aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer) ||
596         RubyUtils::IsRubyBox(aFrame->Type())) {
597       return false;
598     }
599   }
600   return true;
601 }
602 
603 /**
604  * Create non-fluid continuations for the ancestors of a given frame all the way
605  * up the frame tree until we hit a non-splittable frame (a line or a block).
606  *
607  * @param aParent the first parent frame to be split
608  * @param aFrame the child frames after this frame are reparented to the
609  *        newly-created continuation of aParent.
610  *        If aFrame is null, all the children of aParent are reparented.
611  */
SplitInlineAncestors(nsContainerFrame * aParent,nsLineList::iterator aLine,nsIFrame * aFrame)612 static void SplitInlineAncestors(nsContainerFrame* aParent,
613                                  nsLineList::iterator aLine, nsIFrame* aFrame) {
614   PresShell* presShell = aParent->PresShell();
615   nsIFrame* frame = aFrame;
616   nsContainerFrame* parent = aParent;
617   nsContainerFrame* newParent;
618 
619   while (IsBidiSplittable(parent)) {
620     nsContainerFrame* grandparent = parent->GetParent();
621     NS_ASSERTION(grandparent,
622                  "Couldn't get parent's parent in "
623                  "nsBidiPresUtils::SplitInlineAncestors");
624 
625     // Split the child list after |frame|, unless it is the last child.
626     if (!frame || frame->GetNextSibling()) {
627       newParent = static_cast<nsContainerFrame*>(
628           presShell->FrameConstructor()->CreateContinuingFrame(
629               parent, grandparent, false));
630 
631       nsFrameList tail = parent->StealFramesAfter(frame);
632 
633       // Reparent views as necessary
634       nsContainerFrame::ReparentFrameViewList(tail, parent, newParent);
635 
636       // The parent's continuation adopts the siblings after the split.
637       MOZ_ASSERT(!newParent->IsBlockFrameOrSubclass(),
638                  "blocks should not be IsBidiSplittable");
639       newParent->InsertFrames(nsIFrame::kNoReflowPrincipalList, nullptr,
640                               nullptr, tail);
641 
642       // While passing &aLine to InsertFrames for a non-block isn't harmful
643       // because it's a no-op, it doesn't really make sense.  However, the
644       // MOZ_ASSERT() we need to guarantee that it's safe only works if the
645       // parent is actually the block.
646       const nsLineList::iterator* parentLine;
647       if (grandparent->IsBlockFrameOrSubclass()) {
648         MOZ_ASSERT(aLine->Contains(parent));
649         parentLine = &aLine;
650       } else {
651         parentLine = nullptr;
652       }
653 
654       // The list name kNoReflowPrincipalList would indicate we don't want
655       // reflow
656       nsFrameList temp(newParent, newParent);
657       grandparent->InsertFrames(nsIFrame::kNoReflowPrincipalList, parent,
658                                 parentLine, temp);
659     }
660 
661     frame = parent;
662     parent = grandparent;
663   }
664 }
665 
MakeContinuationFluid(nsIFrame * aFrame,nsIFrame * aNext)666 static void MakeContinuationFluid(nsIFrame* aFrame, nsIFrame* aNext) {
667   NS_ASSERTION(!aFrame->GetNextInFlow() || aFrame->GetNextInFlow() == aNext,
668                "next-in-flow is not next continuation!");
669   aFrame->SetNextInFlow(aNext);
670 
671   NS_ASSERTION(!aNext->GetPrevInFlow() || aNext->GetPrevInFlow() == aFrame,
672                "prev-in-flow is not prev continuation!");
673   aNext->SetPrevInFlow(aFrame);
674 }
675 
MakeContinuationsNonFluidUpParentChain(nsIFrame * aFrame,nsIFrame * aNext)676 static void MakeContinuationsNonFluidUpParentChain(nsIFrame* aFrame,
677                                                    nsIFrame* aNext) {
678   nsIFrame* frame;
679   nsIFrame* next;
680 
681   for (frame = aFrame, next = aNext;
682        frame && next && next != frame && next == frame->GetNextInFlow() &&
683        IsBidiSplittable(frame);
684        frame = frame->GetParent(), next = next->GetParent()) {
685     frame->SetNextContinuation(next);
686     next->SetPrevContinuation(frame);
687   }
688 }
689 
690 // If aFrame is the last child of its parent, convert bidi continuations to
691 // fluid continuations for all of its inline ancestors.
692 // If it isn't the last child, make sure that its continuation is fluid.
JoinInlineAncestors(nsIFrame * aFrame)693 static void JoinInlineAncestors(nsIFrame* aFrame) {
694   nsIFrame* frame = aFrame;
695   while (frame && IsBidiSplittable(frame)) {
696     nsIFrame* next = frame->GetNextContinuation();
697     if (next) {
698       MakeContinuationFluid(frame, next);
699     }
700     // Join the parent only as long as we're its last child.
701     if (frame->GetNextSibling()) break;
702     frame = frame->GetParent();
703   }
704 }
705 
CreateContinuation(nsIFrame * aFrame,const nsLineList::iterator aLine,nsIFrame ** aNewFrame,bool aIsFluid)706 static void CreateContinuation(nsIFrame* aFrame,
707                                const nsLineList::iterator aLine,
708                                nsIFrame** aNewFrame, bool aIsFluid) {
709   MOZ_ASSERT(aNewFrame, "null OUT ptr");
710   MOZ_ASSERT(aFrame, "null ptr");
711 
712   *aNewFrame = nullptr;
713 
714   nsPresContext* presContext = aFrame->PresContext();
715   PresShell* presShell = presContext->PresShell();
716   NS_ASSERTION(presShell,
717                "PresShell must be set on PresContext before calling "
718                "nsBidiPresUtils::CreateContinuation");
719 
720   nsContainerFrame* parent = aFrame->GetParent();
721   NS_ASSERTION(
722       parent,
723       "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
724 
725   // While passing &aLine to InsertFrames for a non-block isn't harmful
726   // because it's a no-op, it doesn't really make sense.  However, the
727   // MOZ_ASSERT() we need to guarantee that it's safe only works if the
728   // parent is actually the block.
729   const nsLineList::iterator* parentLine;
730   if (parent->IsBlockFrameOrSubclass()) {
731     MOZ_ASSERT(aLine->Contains(aFrame));
732     parentLine = &aLine;
733   } else {
734     parentLine = nullptr;
735   }
736 
737   // Have to special case floating first letter frames because the continuation
738   // doesn't go in the first letter frame. The continuation goes with the rest
739   // of the text that the first letter frame was made out of.
740   if (parent->IsLetterFrame() && parent->IsFloating()) {
741     nsFirstLetterFrame* letterFrame = do_QueryFrame(parent);
742     letterFrame->CreateContinuationForFloatingParent(aFrame, aNewFrame,
743                                                      aIsFluid);
744     return;
745   }
746 
747   *aNewFrame = presShell->FrameConstructor()->CreateContinuingFrame(
748       aFrame, parent, aIsFluid);
749 
750   // The list name kNoReflowPrincipalList would indicate we don't want reflow
751   // XXXbz this needs higher-level framelist love
752   nsFrameList temp(*aNewFrame, *aNewFrame);
753   parent->InsertFrames(nsIFrame::kNoReflowPrincipalList, aFrame, parentLine,
754                        temp);
755 
756   if (!aIsFluid) {
757     // Split inline ancestor frames
758     SplitInlineAncestors(parent, aLine, aFrame);
759   }
760 }
761 
762 /*
763  * Overview of the implementation of Resolve():
764  *
765  *  Walk through the descendants of aBlockFrame and build:
766  *   * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
767  *   * mBuffer: an nsString containing a representation of
768  *     the content of the frames.
769  *     In the case of text frames, this is the actual text context of the
770  *     frames, but some other elements are represented in a symbolic form which
771  *     will make the Unicode Bidi Algorithm give the correct results.
772  *     Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo>
773  *     elements are represented by the corresponding Unicode control characters.
774  *     <br> elements are represented by U+2028 LINE SEPARATOR
775  *     Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
776  *     CHARACTER
777  *
778  *  Then pass mBuffer to the Bidi engine for resolving of embedding levels
779  *  by nsBidi::SetPara() and division into directional runs by
780  *  nsBidi::CountRuns().
781  *
782  *  Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
783  *  correlate them with the frames indexed in mLogicalFrames, setting the
784  *  baseLevel and embeddingLevel properties according to the results returned
785  *  by the Bidi engine.
786  *
787  *  The rendering layer requires each text frame to contain text in only one
788  *  direction, so we may need to call EnsureBidiContinuation() to split frames.
789  *  We may also need to call RemoveBidiContinuation() to convert frames created
790  *  by EnsureBidiContinuation() in previous reflows into fluid continuations.
791  */
Resolve(nsBlockFrame * aBlockFrame)792 nsresult nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame) {
793   BidiParagraphData bpd(aBlockFrame);
794 
795   // Handle bidi-override being set on the block itself before calling
796   // TraverseFrames.
797   // No need to call GetBidiControl as well, because isolate and embed
798   // values of unicode-bidi property are redundant on block elements.
799   // unicode-bidi:plaintext on a block element is handled by block frame
800   // via using nsIFrame::GetWritingMode(nsIFrame*).
801   char16_t ch = GetBidiOverride(aBlockFrame->Style());
802   if (ch != 0) {
803     bpd.PushBidiControl(ch);
804     bpd.mRequiresBidi = true;
805   } else {
806     // If there are no unicode-bidi properties and no RTL characters in the
807     // block's content, then it is pure LTR and we can skip the rest of bidi
808     // resolution.
809     nsIContent* currContent = nullptr;
810     for (nsBlockFrame* block = aBlockFrame; block;
811          block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
812       block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
813       if (!bpd.mRequiresBidi &&
814           ChildListMayRequireBidi(block->PrincipalChildList().FirstChild(),
815                                   &currContent)) {
816         bpd.mRequiresBidi = true;
817       }
818       if (!bpd.mRequiresBidi) {
819         nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
820         if (overflowLines) {
821           if (ChildListMayRequireBidi(overflowLines->mFrames.FirstChild(),
822                                       &currContent)) {
823             bpd.mRequiresBidi = true;
824           }
825         }
826       }
827     }
828     if (!bpd.mRequiresBidi) {
829       return NS_OK;
830     }
831   }
832 
833   for (nsBlockFrame* block = aBlockFrame; block;
834        block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
835 #ifdef DEBUG
836     bpd.mCurrentBlock = block;
837 #endif
838     block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
839     bpd.mCurrentTraverseLine.mLineIterator =
840         nsBlockInFlowLineIterator(block, block->LinesBegin());
841     bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
842     TraverseFrames(block->PrincipalChildList().FirstChild(), &bpd);
843     nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
844     if (overflowLines) {
845       bpd.mCurrentTraverseLine.mLineIterator =
846           nsBlockInFlowLineIterator(block, overflowLines->mLines.begin(), true);
847       bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
848       TraverseFrames(overflowLines->mFrames.FirstChild(), &bpd);
849     }
850   }
851 
852   if (ch != 0) {
853     bpd.PopBidiControl(ch);
854   }
855 
856   return ResolveParagraph(&bpd);
857 }
858 
ResolveParagraph(BidiParagraphData * aBpd)859 nsresult nsBidiPresUtils::ResolveParagraph(BidiParagraphData* aBpd) {
860   if (aBpd->BufferLength() < 1) {
861     return NS_OK;
862   }
863   aBpd->mBuffer.ReplaceChar(kSeparators, kSpace);
864 
865   int32_t runCount;
866 
867   nsresult rv = aBpd->SetPara();
868   NS_ENSURE_SUCCESS(rv, rv);
869 
870   nsBidiLevel embeddingLevel = aBpd->GetParaLevel();
871 
872   rv = aBpd->CountRuns(&runCount);
873   NS_ENSURE_SUCCESS(rv, rv);
874 
875   int32_t runLength = 0;     // the length of the current run of text
876   int32_t logicalLimit = 0;  // the end of the current run + 1
877   int32_t numRun = -1;
878   int32_t fragmentLength = 0;  // the length of the current text frame
879   int32_t frameIndex = -1;     // index to the frames in mLogicalFrames
880   int32_t frameCount = aBpd->FrameCount();
881   int32_t contentOffset = 0;  // offset of current frame in its content node
882   bool isTextFrame = false;
883   nsIFrame* frame = nullptr;
884   BidiParagraphData::FrameInfo frameInfo;
885   nsIContent* content = nullptr;
886   int32_t contentTextLength = 0;
887 
888 #ifdef DEBUG
889 #  ifdef NOISY_BIDI
890   printf(
891       "Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, "
892       "runCount=%d\n",
893       (void*)aBpd->mCurrentBlock, NS_ConvertUTF16toUTF8(aBpd->mBuffer).get(),
894       frameCount, runCount);
895 #    ifdef REALLY_NOISY_BIDI
896   printf(" block frame tree=:\n");
897   aBpd->mCurrentBlock->List(stdout);
898 #    endif
899 #  endif
900 #endif
901 
902   if (runCount == 1 && frameCount == 1 && aBpd->GetDirection() == NSBIDI_LTR &&
903       aBpd->GetParaLevel() == 0) {
904     // We have a single left-to-right frame in a left-to-right paragraph,
905     // without bidi isolation from the surrounding text.
906     // Make sure that the embedding level and base level frame properties aren't
907     // set (because if they are this frame used to have some other direction,
908     // so we can't do this optimization), and we're done.
909     nsIFrame* frame = aBpd->FrameAt(0);
910     if (frame != NS_BIDI_CONTROL_FRAME) {
911       FrameBidiData bidiData = frame->GetBidiData();
912       if (!bidiData.embeddingLevel && !bidiData.baseLevel) {
913 #ifdef DEBUG
914 #  ifdef NOISY_BIDI
915         printf("early return for single direction frame %p\n", (void*)frame);
916 #  endif
917 #endif
918         frame->AddStateBits(NS_FRAME_IS_BIDI);
919         return NS_OK;
920       }
921     }
922   }
923 
924   BidiParagraphData::FrameInfo lastRealFrame;
925   nsBidiLevel lastEmbeddingLevel = kBidiLevelNone;
926   nsBidiLevel precedingControl = kBidiLevelNone;
927 
928   auto storeBidiDataToFrame = [&]() {
929     FrameBidiData bidiData;
930     bidiData.embeddingLevel = embeddingLevel;
931     bidiData.baseLevel = aBpd->GetParaLevel();
932     // If a control character doesn't have a lower embedding level than
933     // both the preceding and the following frame, it isn't something
934     // needed for getting the correct result. This optimization should
935     // remove almost all of embeds and overrides, and some of isolates.
936     if (precedingControl >= embeddingLevel ||
937         precedingControl >= lastEmbeddingLevel) {
938       bidiData.precedingControl = kBidiLevelNone;
939     } else {
940       bidiData.precedingControl = precedingControl;
941     }
942     precedingControl = kBidiLevelNone;
943     lastEmbeddingLevel = embeddingLevel;
944     frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData);
945   };
946 
947   for (;;) {
948     if (fragmentLength <= 0) {
949       // Get the next frame from mLogicalFrames
950       if (++frameIndex >= frameCount) {
951         break;
952       }
953       frameInfo = aBpd->FrameInfoAt(frameIndex);
954       frame = frameInfo.mFrame;
955       if (frame == NS_BIDI_CONTROL_FRAME || !frame->IsTextFrame()) {
956         /*
957          * Any non-text frame corresponds to a single character in the text
958          * buffer (a bidi control character, LINE SEPARATOR, or OBJECT
959          * SUBSTITUTE)
960          */
961         isTextFrame = false;
962         fragmentLength = 1;
963       } else {
964         aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(frameInfo);
965         content = frame->GetContent();
966         if (!content) {
967           rv = NS_OK;
968           break;
969         }
970         contentTextLength = content->TextLength();
971         int32_t start, end;
972         frame->GetOffsets(start, end);
973         NS_ASSERTION(!(contentTextLength < end - start),
974                      "Frame offsets don't fit in content");
975         fragmentLength = std::min(contentTextLength, end - start);
976         contentOffset = start;
977         isTextFrame = true;
978       }
979     }  // if (fragmentLength <= 0)
980 
981     if (runLength <= 0) {
982       // Get the next run of text from the Bidi engine
983       if (++numRun >= runCount) {
984         // We've run out of runs of text; but don't forget to store bidi data
985         // to the frame before breaking out of the loop (bug 1426042).
986         storeBidiDataToFrame();
987         if (isTextFrame) {
988           frame->AdjustOffsetsForBidi(contentOffset,
989                                       contentOffset + fragmentLength);
990         }
991         break;
992       }
993       int32_t lineOffset = logicalLimit;
994       aBpd->GetLogicalRun(lineOffset, &logicalLimit, &embeddingLevel);
995       runLength = logicalLimit - lineOffset;
996     }  // if (runLength <= 0)
997 
998     if (frame == NS_BIDI_CONTROL_FRAME) {
999       // In theory, we only need to do this for isolates. However, it is
1000       // easier to do this for all here because we do not maintain the
1001       // index to get corresponding character from buffer. Since we do
1002       // have proper embedding level for all those characters, including
1003       // them wouldn't affect the final result.
1004       precedingControl = std::min(precedingControl, embeddingLevel);
1005     } else {
1006       storeBidiDataToFrame();
1007       if (isTextFrame) {
1008         if (contentTextLength == 0) {
1009           // Set the base level and embedding level of the current run even
1010           // on an empty frame. Otherwise frame reordering will not be correct.
1011           frame->AdjustOffsetsForBidi(0, 0);
1012           // Nothing more to do for an empty frame, except update
1013           // lastRealFrame like we do below.
1014           lastRealFrame = frameInfo;
1015           continue;
1016         }
1017         nsLineList::iterator currentLine = aBpd->mCurrentResolveLine.GetLine();
1018         if ((runLength > 0) && (runLength < fragmentLength)) {
1019           /*
1020            * The text in this frame continues beyond the end of this directional
1021            * run. Create a non-fluid continuation frame for the next directional
1022            * run.
1023            */
1024           currentLine->MarkDirty();
1025           nsIFrame* nextBidi;
1026           int32_t runEnd = contentOffset + runLength;
1027           EnsureBidiContinuation(frame, currentLine, &nextBidi, contentOffset,
1028                                  runEnd);
1029           nextBidi->AdjustOffsetsForBidi(runEnd,
1030                                          contentOffset + fragmentLength);
1031           frame = nextBidi;
1032           frameInfo.mFrame = frame;
1033           contentOffset = runEnd;
1034 
1035           aBpd->mCurrentResolveLine.AdvanceToFrame(frame);
1036         }  // if (runLength < fragmentLength)
1037         else {
1038           if (contentOffset + fragmentLength == contentTextLength) {
1039             /*
1040              * We have finished all the text in this content node. Convert any
1041              * further non-fluid continuations to fluid continuations and
1042              * advance frameIndex to the last frame in the content node
1043              */
1044             int32_t newIndex = aBpd->GetLastFrameForContent(content);
1045             if (newIndex > frameIndex) {
1046               currentLine->MarkDirty();
1047               RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
1048               frameIndex = newIndex;
1049               frameInfo = aBpd->FrameInfoAt(frameIndex);
1050               frame = frameInfo.mFrame;
1051             }
1052           } else if (fragmentLength > 0 && runLength > fragmentLength) {
1053             /*
1054              * There is more text that belongs to this directional run in the
1055              * next text frame: make sure it is a fluid continuation of the
1056              * current frame. Do not advance frameIndex, because the next frame
1057              * may contain multi-directional text and need to be split
1058              */
1059             int32_t newIndex = frameIndex;
1060             do {
1061             } while (++newIndex < frameCount &&
1062                      aBpd->FrameAt(newIndex) == NS_BIDI_CONTROL_FRAME);
1063             if (newIndex < frameCount) {
1064               currentLine->MarkDirty();
1065               RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
1066             }
1067           } else if (runLength == fragmentLength) {
1068             /*
1069              * If the directional run ends at the end of the frame, make sure
1070              * that any continuation is non-fluid, and do the same up the
1071              * parent chain
1072              */
1073             nsIFrame* next = frame->GetNextInFlow();
1074             if (next) {
1075               currentLine->MarkDirty();
1076               MakeContinuationsNonFluidUpParentChain(frame, next);
1077             }
1078           }
1079           frame->AdjustOffsetsForBidi(contentOffset,
1080                                       contentOffset + fragmentLength);
1081         }
1082       }  // isTextFrame
1083     }    // not bidi control frame
1084     int32_t temp = runLength;
1085     runLength -= fragmentLength;
1086     fragmentLength -= temp;
1087 
1088     // Record last real frame so that we can do splitting properly even
1089     // if a run ends after a virtual bidi control frame.
1090     if (frame != NS_BIDI_CONTROL_FRAME) {
1091       lastRealFrame = frameInfo;
1092     }
1093     if (lastRealFrame.mFrame && fragmentLength <= 0) {
1094       // If the frame is at the end of a run, and this is not the end of our
1095       // paragraph, split all ancestor inlines that need splitting.
1096       // To determine whether we're at the end of the run, we check that we've
1097       // finished processing the current run, and that the current frame
1098       // doesn't have a fluid continuation (it could have a fluid continuation
1099       // of zero length, so testing runLength alone is not sufficient).
1100       if (runLength <= 0 && !lastRealFrame.mFrame->GetNextInFlow()) {
1101         if (numRun + 1 < runCount) {
1102           nsIFrame* child = lastRealFrame.mFrame;
1103           nsContainerFrame* parent = child->GetParent();
1104           // As long as we're on the last sibling, the parent doesn't have to
1105           // be split.
1106           // However, if the parent has a fluid continuation, we do have to make
1107           // it non-fluid. This can happen e.g. when we have a first-letter
1108           // frame and the end of the first-letter coincides with the end of a
1109           // directional run.
1110           while (parent && IsBidiSplittable(parent) &&
1111                  !child->GetNextSibling()) {
1112             nsIFrame* next = parent->GetNextInFlow();
1113             if (next) {
1114               parent->SetNextContinuation(next);
1115               next->SetPrevContinuation(parent);
1116             }
1117             child = parent;
1118             parent = child->GetParent();
1119           }
1120           if (parent && IsBidiSplittable(parent)) {
1121             aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
1122             SplitInlineAncestors(parent, aBpd->mCurrentResolveLine.GetLine(),
1123                                  child);
1124 
1125             aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
1126           }
1127         }
1128       } else if (frame != NS_BIDI_CONTROL_FRAME) {
1129         // We're not at an end of a run. If |frame| is the last child of its
1130         // parent, and its ancestors happen to have bidi continuations, convert
1131         // them into fluid continuations.
1132         JoinInlineAncestors(frame);
1133       }
1134     }
1135   }  // for
1136 
1137 #ifdef DEBUG
1138 #  ifdef REALLY_NOISY_BIDI
1139   printf("---\nAfter Resolve(), frameTree =:\n");
1140   aBpd->mCurrentBlock->List(stdout);
1141   printf("===\n");
1142 #  endif
1143 #endif
1144 
1145   return rv;
1146 }
1147 
TraverseFrames(nsIFrame * aCurrentFrame,BidiParagraphData * aBpd)1148 void nsBidiPresUtils::TraverseFrames(nsIFrame* aCurrentFrame,
1149                                      BidiParagraphData* aBpd) {
1150   if (!aCurrentFrame) return;
1151 
1152 #ifdef DEBUG
1153   nsBlockFrame* initialLineContainer =
1154       aBpd->mCurrentTraverseLine.mLineIterator.GetContainer();
1155 #endif
1156 
1157   nsIFrame* childFrame = aCurrentFrame;
1158   do {
1159     /*
1160      * It's important to get the next sibling and next continuation *before*
1161      * handling the frame: If we encounter a forced paragraph break and call
1162      * ResolveParagraph within this loop, doing GetNextSibling and
1163      * GetNextContinuation after that could return a bidi continuation that had
1164      * just been split from the original childFrame and we would process it
1165      * twice.
1166      */
1167     nsIFrame* nextSibling = childFrame->GetNextSibling();
1168 
1169     // If the real frame for a placeholder is a first letter frame, we need to
1170     // drill down into it and include its contents in Bidi resolution.
1171     // If not, we just use the placeholder.
1172     nsIFrame* frame = childFrame;
1173     if (childFrame->IsPlaceholderFrame()) {
1174       nsIFrame* realFrame =
1175           nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
1176       if (realFrame->IsLetterFrame()) {
1177         frame = realFrame;
1178       }
1179     }
1180 
1181     auto DifferentBidiValues = [](ComputedStyle* aSC1, nsIFrame* aFrame2) {
1182       ComputedStyle* sc2 = aFrame2->Style();
1183       return GetBidiControl(aSC1) != GetBidiControl(sc2) ||
1184              GetBidiOverride(aSC1) != GetBidiOverride(sc2);
1185     };
1186 
1187     ComputedStyle* sc = frame->Style();
1188     nsIFrame* nextContinuation = frame->GetNextContinuation();
1189     nsIFrame* prevContinuation = frame->GetPrevContinuation();
1190     bool isLastFrame =
1191         !nextContinuation || DifferentBidiValues(sc, nextContinuation);
1192     bool isFirstFrame =
1193         !prevContinuation || DifferentBidiValues(sc, prevContinuation);
1194 
1195     char16_t controlChar = 0;
1196     char16_t overrideChar = 0;
1197     LayoutFrameType frameType = frame->Type();
1198     if (frame->IsFrameOfType(nsIFrame::eBidiInlineContainer) ||
1199         frameType == LayoutFrameType::Ruby) {
1200       if (!(frame->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
1201         nsContainerFrame* c = static_cast<nsContainerFrame*>(frame);
1202         MOZ_ASSERT(c == do_QueryFrame(frame),
1203                    "eBidiInlineContainer and ruby frame must be"
1204                    " a nsContainerFrame subclass");
1205         c->DrainSelfOverflowList();
1206       }
1207 
1208       controlChar = GetBidiControl(sc);
1209       overrideChar = GetBidiOverride(sc);
1210 
1211       // Add dummy frame pointers representing bidi control codes before
1212       // the first frames of elements specifying override, isolation, or
1213       // plaintext.
1214       if (isFirstFrame) {
1215         if (controlChar != 0) {
1216           aBpd->PushBidiControl(controlChar);
1217         }
1218         if (overrideChar != 0) {
1219           aBpd->PushBidiControl(overrideChar);
1220         }
1221       }
1222     }
1223 
1224     if (IsBidiLeaf(frame)) {
1225       /* Bidi leaf frame: add the frame to the mLogicalFrames array,
1226        * and add its index to the mContentToFrameIndex hashtable. This
1227        * will be used in RemoveBidiContinuation() to identify the last
1228        * frame in the array with a given content.
1229        */
1230       nsIContent* content = frame->GetContent();
1231       aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine, content);
1232 
1233       // Append the content of the frame to the paragraph buffer
1234       if (LayoutFrameType::Text == frameType) {
1235         if (content != aBpd->mPrevContent) {
1236           aBpd->mPrevContent = content;
1237           if (!frame->StyleText()->NewlineIsSignificant(
1238                   static_cast<nsTextFrame*>(frame))) {
1239             content->GetAsText()->AppendTextTo(aBpd->mBuffer);
1240           } else {
1241             /*
1242              * For preformatted text we have to do bidi resolution on each line
1243              * separately.
1244              */
1245             nsAutoString text;
1246             content->GetAsText()->AppendTextTo(text);
1247             nsIFrame* next;
1248             do {
1249               next = nullptr;
1250 
1251               int32_t start, end;
1252               frame->GetOffsets(start, end);
1253               int32_t endLine = text.FindChar('\n', start);
1254               if (endLine == -1) {
1255                 /*
1256                  * If there is no newline in the text content, just save the
1257                  * text from this frame and its continuations, and do bidi
1258                  * resolution later
1259                  */
1260                 aBpd->AppendString(Substring(text, start));
1261                 while (frame && nextSibling) {
1262                   aBpd->AdvanceAndAppendFrame(
1263                       &frame, aBpd->mCurrentTraverseLine, &nextSibling);
1264                 }
1265                 break;
1266               }
1267 
1268               /*
1269                * If there is a newline in the frame, break the frame after the
1270                * newline, do bidi resolution and repeat until the last sibling
1271                */
1272               ++endLine;
1273 
1274               /*
1275                * If the frame ends before the new line, save the text and move
1276                * into the next continuation
1277                */
1278               aBpd->AppendString(
1279                   Substring(text, start, std::min(end, endLine) - start));
1280               while (end < endLine && nextSibling) {
1281                 aBpd->AdvanceAndAppendFrame(&frame, aBpd->mCurrentTraverseLine,
1282                                             &nextSibling);
1283                 NS_ASSERTION(frame, "Premature end of continuation chain");
1284                 frame->GetOffsets(start, end);
1285                 aBpd->AppendString(
1286                     Substring(text, start, std::min(end, endLine) - start));
1287               }
1288 
1289               if (end < endLine) {
1290                 aBpd->mPrevContent = nullptr;
1291                 break;
1292               }
1293 
1294               bool createdContinuation = false;
1295               if (uint32_t(endLine) < text.Length()) {
1296                 /*
1297                  * Timing is everything here: if the frame already has a bidi
1298                  * continuation, we need to make the continuation fluid *before*
1299                  * resetting the length of the current frame. Otherwise
1300                  * nsTextFrame::SetLength won't set the continuation frame's
1301                  * text offsets correctly.
1302                  *
1303                  * On the other hand, if the frame doesn't have a continuation,
1304                  * we need to create one *after* resetting the length, or
1305                  * CreateContinuingFrame will complain that there is no more
1306                  * content for the continuation.
1307                  */
1308                 next = frame->GetNextInFlow();
1309                 if (!next) {
1310                   // If the frame already has a bidi continuation, make it fluid
1311                   next = frame->GetNextContinuation();
1312                   if (next) {
1313                     MakeContinuationFluid(frame, next);
1314                     JoinInlineAncestors(frame);
1315                   }
1316                 }
1317 
1318                 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
1319                 textFrame->SetLength(endLine - start, nullptr);
1320 
1321                 // If it weren't for CreateContinuation needing this to
1322                 // be current, we could restructure the marking dirty
1323                 // below to use mCurrentResolveLine and eliminate
1324                 // mCurrentTraverseLine entirely.
1325                 aBpd->mCurrentTraverseLine.AdvanceToFrame(frame);
1326 
1327                 if (!next) {
1328                   // If the frame has no next in flow, create one.
1329                   CreateContinuation(
1330                       frame, aBpd->mCurrentTraverseLine.GetLine(), &next, true);
1331                   createdContinuation = true;
1332                 }
1333                 // Mark the line before the newline as dirty.
1334                 aBpd->mCurrentTraverseLine.GetLine()->MarkDirty();
1335               }
1336               ResolveParagraphWithinBlock(aBpd);
1337 
1338               if (!nextSibling && !createdContinuation) {
1339                 break;
1340               } else if (next) {
1341                 frame = next;
1342                 aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine);
1343                 // Mark the line after the newline as dirty.
1344                 aBpd->mCurrentTraverseLine.AdvanceToFrame(frame);
1345                 aBpd->mCurrentTraverseLine.GetLine()->MarkDirty();
1346               }
1347 
1348               /*
1349                * If we have already overshot the saved next-sibling while
1350                * scanning the frame's continuations, advance it.
1351                */
1352               if (frame && frame == nextSibling) {
1353                 nextSibling = frame->GetNextSibling();
1354               }
1355 
1356             } while (next);
1357           }
1358         }
1359       } else if (LayoutFrameType::Br == frameType) {
1360         // break frame -- append line separator
1361         aBpd->AppendUnichar(kLineSeparator);
1362         ResolveParagraphWithinBlock(aBpd);
1363       } else {
1364         // other frame type -- see the Unicode Bidi Algorithm:
1365         // "...inline objects (such as graphics) are treated as if they are ...
1366         // U+FFFC"
1367         // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
1368         // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1
1369         aBpd->AppendUnichar(
1370             content->IsHTMLElement(nsGkAtoms::wbr) ? kZWSP : kObjectSubstitute);
1371         if (!frame->IsInlineOutside()) {
1372           // if it is not inline, end the paragraph
1373           ResolveParagraphWithinBlock(aBpd);
1374         }
1375       }
1376     } else {
1377       // For a non-leaf frame, recurse into TraverseFrames
1378       nsIFrame* kid = frame->PrincipalChildList().FirstChild();
1379       MOZ_ASSERT(!frame->GetChildList(nsIFrame::kOverflowList).FirstChild(),
1380                  "should have drained the overflow list above");
1381       if (kid) {
1382         TraverseFrames(kid, aBpd);
1383       }
1384     }
1385 
1386     // If the element is attributed by dir, indicate direction pop (add PDF
1387     // frame)
1388     if (isLastFrame) {
1389       // Add a dummy frame pointer representing a bidi control code after the
1390       // last frame of an element specifying embedding or override
1391       if (overrideChar != 0) {
1392         aBpd->PopBidiControl(overrideChar);
1393       }
1394       if (controlChar != 0) {
1395         aBpd->PopBidiControl(controlChar);
1396       }
1397     }
1398     childFrame = nextSibling;
1399   } while (childFrame);
1400 
1401   MOZ_ASSERT(initialLineContainer ==
1402              aBpd->mCurrentTraverseLine.mLineIterator.GetContainer());
1403 }
1404 
ChildListMayRequireBidi(nsIFrame * aFirstChild,nsIContent ** aCurrContent)1405 bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame* aFirstChild,
1406                                               nsIContent** aCurrContent) {
1407   MOZ_ASSERT(!aFirstChild || !aFirstChild->GetPrevSibling(),
1408              "Expecting to traverse from the start of a child list");
1409 
1410   for (nsIFrame* childFrame = aFirstChild; childFrame;
1411        childFrame = childFrame->GetNextSibling()) {
1412     nsIFrame* frame = childFrame;
1413 
1414     // If the real frame for a placeholder is a first-letter frame, we need to
1415     // consider its contents for potential Bidi resolution.
1416     if (childFrame->IsPlaceholderFrame()) {
1417       nsIFrame* realFrame =
1418           nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
1419       if (realFrame->IsLetterFrame()) {
1420         frame = realFrame;
1421       }
1422     }
1423 
1424     // If unicode-bidi properties are present, we should do bidi resolution.
1425     ComputedStyle* sc = frame->Style();
1426     if (GetBidiControl(sc) || GetBidiOverride(sc)) {
1427       return true;
1428     }
1429 
1430     if (IsBidiLeaf(frame)) {
1431       if (frame->IsTextFrame()) {
1432         // If the frame already has a BidiDataProperty, we know we need to
1433         // perform bidi resolution (even if no bidi content is NOW present --
1434         // we might need to remove the property set by a previous reflow, if
1435         // content has changed; see bug 1366623).
1436         if (frame->HasProperty(nsIFrame::BidiDataProperty())) {
1437           return true;
1438         }
1439 
1440         // Check whether the text frame has any RTL characters; if so, bidi
1441         // resolution will be needed.
1442         dom::Text* content = frame->GetContent()->AsText();
1443         if (content != *aCurrContent) {
1444           *aCurrContent = content;
1445           const nsTextFragment* txt = &content->TextFragment();
1446           if (txt->Is2b() &&
1447               HasRTLChars(MakeSpan(txt->Get2b(), txt->GetLength()))) {
1448             return true;
1449           }
1450         }
1451       }
1452     } else if (ChildListMayRequireBidi(frame->PrincipalChildList().FirstChild(),
1453                                        aCurrContent)) {
1454       return true;
1455     }
1456   }
1457 
1458   return false;
1459 }
1460 
ResolveParagraphWithinBlock(BidiParagraphData * aBpd)1461 void nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData* aBpd) {
1462   aBpd->ClearBidiControls();
1463   ResolveParagraph(aBpd);
1464   aBpd->ResetData();
1465 }
1466 
1467 /* static */
ReorderFrames(nsIFrame * aFirstFrameOnLine,int32_t aNumFramesOnLine,WritingMode aLineWM,const nsSize & aContainerSize,nscoord aStart)1468 nscoord nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine,
1469                                        int32_t aNumFramesOnLine,
1470                                        WritingMode aLineWM,
1471                                        const nsSize& aContainerSize,
1472                                        nscoord aStart) {
1473   nsSize containerSize(aContainerSize);
1474 
1475   // If this line consists of a line frame, reorder the line frame's children.
1476   if (aFirstFrameOnLine->IsLineFrame()) {
1477     // The line frame is positioned at the start-edge, so use its size
1478     // as the container size.
1479     containerSize = aFirstFrameOnLine->GetSize();
1480 
1481     aFirstFrameOnLine = aFirstFrameOnLine->PrincipalChildList().FirstChild();
1482     if (!aFirstFrameOnLine) {
1483       return 0;
1484     }
1485     // All children of the line frame are on the first line. Setting
1486     // aNumFramesOnLine to -1 makes InitLogicalArrayFromLine look at all of
1487     // them.
1488     aNumFramesOnLine = -1;
1489   }
1490 
1491   BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
1492   return RepositionInlineFrames(&bld, aLineWM, containerSize, aStart);
1493 }
1494 
GetFirstLeaf(nsIFrame * aFrame)1495 nsIFrame* nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame) {
1496   nsIFrame* firstLeaf = aFrame;
1497   while (!IsBidiLeaf(firstLeaf)) {
1498     nsIFrame* firstChild = firstLeaf->PrincipalChildList().FirstChild();
1499     nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(firstChild);
1500     firstLeaf = (realFrame->IsLetterFrame()) ? realFrame : firstChild;
1501   }
1502   return firstLeaf;
1503 }
1504 
GetFrameBidiData(nsIFrame * aFrame)1505 FrameBidiData nsBidiPresUtils::GetFrameBidiData(nsIFrame* aFrame) {
1506   return GetFirstLeaf(aFrame)->GetBidiData();
1507 }
1508 
GetFrameEmbeddingLevel(nsIFrame * aFrame)1509 nsBidiLevel nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) {
1510   return GetFirstLeaf(aFrame)->GetEmbeddingLevel();
1511 }
1512 
GetFrameBaseLevel(const nsIFrame * aFrame)1513 nsBidiLevel nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame* aFrame) {
1514   const nsIFrame* firstLeaf = aFrame;
1515   while (!IsBidiLeaf(firstLeaf)) {
1516     firstLeaf = firstLeaf->PrincipalChildList().FirstChild();
1517   }
1518   return firstLeaf->GetBaseLevel();
1519 }
1520 
IsFirstOrLast(nsIFrame * aFrame,nsContinuationStates * aContinuationStates,bool aSpanDirMatchesLineDir,bool & aIsFirst,bool & aIsLast)1521 void nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame,
1522                                     nsContinuationStates* aContinuationStates,
1523                                     bool aSpanDirMatchesLineDir,
1524                                     bool& aIsFirst /* out */,
1525                                     bool& aIsLast /* out */) {
1526   /*
1527    * Since we lay out frames in the line's direction, visiting a frame with
1528    * 'mFirstVisualFrame == nullptr', means it's the first appearance of one
1529    * of its continuation chain frames on the line.
1530    * To determine if it's the last visual frame of its continuation chain on
1531    * the line or not, we count the number of frames of the chain on the line,
1532    * and then reduce it when we lay out a frame of the chain. If this value
1533    * becomes 1 it means that it's the last visual frame of its continuation
1534    * chain on this line.
1535    */
1536 
1537   bool firstInLineOrder, lastInLineOrder;
1538   nsFrameContinuationState* frameState = aContinuationStates->Get(aFrame);
1539   nsFrameContinuationState* firstFrameState;
1540 
1541   if (!frameState->mFirstVisualFrame) {
1542     // aFrame is the first visual frame of its continuation chain
1543     nsFrameContinuationState* contState;
1544     nsIFrame* frame;
1545 
1546     frameState->mFrameCount = 1;
1547     frameState->mFirstVisualFrame = aFrame;
1548 
1549     /**
1550      * Traverse continuation chain of aFrame in both backward and forward
1551      * directions while the frames are on this line. Count the frames and
1552      * set their mFirstVisualFrame to aFrame.
1553      */
1554     // Traverse continuation chain backward
1555     for (frame = aFrame->GetPrevContinuation();
1556          frame && (contState = aContinuationStates->Get(frame));
1557          frame = frame->GetPrevContinuation()) {
1558       frameState->mFrameCount++;
1559       contState->mFirstVisualFrame = aFrame;
1560     }
1561     frameState->mHasContOnPrevLines = (frame != nullptr);
1562 
1563     // Traverse continuation chain forward
1564     for (frame = aFrame->GetNextContinuation();
1565          frame && (contState = aContinuationStates->Get(frame));
1566          frame = frame->GetNextContinuation()) {
1567       frameState->mFrameCount++;
1568       contState->mFirstVisualFrame = aFrame;
1569     }
1570     frameState->mHasContOnNextLines = (frame != nullptr);
1571 
1572     firstInLineOrder = true;
1573     firstFrameState = frameState;
1574   } else {
1575     // aFrame is not the first visual frame of its continuation chain
1576     firstInLineOrder = false;
1577     firstFrameState = aContinuationStates->Get(frameState->mFirstVisualFrame);
1578   }
1579 
1580   lastInLineOrder = (firstFrameState->mFrameCount == 1);
1581 
1582   if (aSpanDirMatchesLineDir) {
1583     aIsFirst = firstInLineOrder;
1584     aIsLast = lastInLineOrder;
1585   } else {
1586     aIsFirst = lastInLineOrder;
1587     aIsLast = firstInLineOrder;
1588   }
1589 
1590   if (frameState->mHasContOnPrevLines) {
1591     aIsFirst = false;
1592   }
1593   if (firstFrameState->mHasContOnNextLines) {
1594     aIsLast = false;
1595   }
1596 
1597   if ((aIsFirst || aIsLast) &&
1598       (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
1599     // For ib splits, don't treat anything except the last part as
1600     // endmost or anything except the first part as startmost.
1601     // As an optimization, only get the first continuation once.
1602     nsIFrame* firstContinuation = aFrame->FirstContinuation();
1603     if (firstContinuation->FrameIsNonLastInIBSplit()) {
1604       // We are not endmost
1605       aIsLast = false;
1606     }
1607     if (firstContinuation->FrameIsNonFirstInIBSplit()) {
1608       // We are not startmost
1609       aIsFirst = false;
1610     }
1611   }
1612 
1613   // Reduce number of remaining frames of the continuation chain on the line.
1614   firstFrameState->mFrameCount--;
1615 
1616   nsInlineFrame* testFrame = do_QueryFrame(aFrame);
1617 
1618   if (testFrame) {
1619     aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET);
1620 
1621     if (aIsFirst) {
1622       aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
1623     } else {
1624       aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
1625     }
1626 
1627     if (aIsLast) {
1628       aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
1629     } else {
1630       aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
1631     }
1632   }
1633 }
1634 
1635 /* static */
RepositionRubyContentFrame(nsIFrame * aFrame,WritingMode aFrameWM,const LogicalMargin & aBorderPadding)1636 void nsBidiPresUtils::RepositionRubyContentFrame(
1637     nsIFrame* aFrame, WritingMode aFrameWM,
1638     const LogicalMargin& aBorderPadding) {
1639   const nsFrameList& childList = aFrame->PrincipalChildList();
1640   if (childList.IsEmpty()) {
1641     return;
1642   }
1643 
1644   // Reorder the children.
1645   nscoord isize =
1646       ReorderFrames(childList.FirstChild(), childList.GetLength(), aFrameWM,
1647                     aFrame->GetSize(), aBorderPadding.IStart(aFrameWM));
1648   isize += aBorderPadding.IEnd(aFrameWM);
1649 
1650   if (aFrame->StyleText()->mRubyAlign == StyleRubyAlign::Start) {
1651     return;
1652   }
1653   nscoord residualISize = aFrame->ISize(aFrameWM) - isize;
1654   if (residualISize <= 0) {
1655     return;
1656   }
1657 
1658   // When ruby-align is not "start", if the content does not fill this
1659   // frame, we need to center the children.
1660   const nsSize dummyContainerSize;
1661   for (nsIFrame* child : childList) {
1662     LogicalRect rect = child->GetLogicalRect(aFrameWM, dummyContainerSize);
1663     rect.IStart(aFrameWM) += residualISize / 2;
1664     child->SetRect(aFrameWM, rect, dummyContainerSize);
1665   }
1666 }
1667 
1668 /* static */
RepositionRubyFrame(nsIFrame * aFrame,nsContinuationStates * aContinuationStates,const WritingMode aContainerWM,const LogicalMargin & aBorderPadding)1669 nscoord nsBidiPresUtils::RepositionRubyFrame(
1670     nsIFrame* aFrame, nsContinuationStates* aContinuationStates,
1671     const WritingMode aContainerWM, const LogicalMargin& aBorderPadding) {
1672   LayoutFrameType frameType = aFrame->Type();
1673   MOZ_ASSERT(RubyUtils::IsRubyBox(frameType));
1674 
1675   nscoord icoord = 0;
1676   WritingMode frameWM = aFrame->GetWritingMode();
1677   bool isLTR = frameWM.IsBidiLTR();
1678   nsSize frameSize = aFrame->GetSize();
1679   if (frameType == LayoutFrameType::Ruby) {
1680     icoord += aBorderPadding.IStart(frameWM);
1681     // Reposition ruby segments in a ruby container
1682     for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(aFrame)); !e.AtEnd();
1683          e.Next()) {
1684       nsRubyBaseContainerFrame* rbc = e.GetBaseContainer();
1685       AutoRubyTextContainerArray textContainers(rbc);
1686 
1687       nscoord segmentISize = RepositionFrame(
1688           rbc, isLTR, icoord, aContinuationStates, frameWM, false, frameSize);
1689       for (nsRubyTextContainerFrame* rtc : textContainers) {
1690         nscoord isize = RepositionFrame(rtc, isLTR, icoord, aContinuationStates,
1691                                         frameWM, false, frameSize);
1692         segmentISize = std::max(segmentISize, isize);
1693       }
1694       icoord += segmentISize;
1695     }
1696     icoord += aBorderPadding.IEnd(frameWM);
1697   } else if (frameType == LayoutFrameType::RubyBaseContainer) {
1698     // Reposition ruby columns in a ruby segment
1699     auto rbc = static_cast<nsRubyBaseContainerFrame*>(aFrame);
1700     AutoRubyTextContainerArray textContainers(rbc);
1701 
1702     for (RubyColumnEnumerator e(rbc, textContainers); !e.AtEnd(); e.Next()) {
1703       RubyColumn column;
1704       e.GetColumn(column);
1705 
1706       nscoord columnISize =
1707           RepositionFrame(column.mBaseFrame, isLTR, icoord, aContinuationStates,
1708                           frameWM, false, frameSize);
1709       for (nsRubyTextFrame* rt : column.mTextFrames) {
1710         nscoord isize = RepositionFrame(rt, isLTR, icoord, aContinuationStates,
1711                                         frameWM, false, frameSize);
1712         columnISize = std::max(columnISize, isize);
1713       }
1714       icoord += columnISize;
1715     }
1716   } else {
1717     if (frameType == LayoutFrameType::RubyBase ||
1718         frameType == LayoutFrameType::RubyText) {
1719       RepositionRubyContentFrame(aFrame, frameWM, aBorderPadding);
1720     }
1721     // Note that, ruby text container is not present in all conditions
1722     // above. It is intended, because the children of rtc are reordered
1723     // with the children of ruby base container simultaneously. We only
1724     // need to return its isize here, as it should not be changed.
1725     icoord += aFrame->ISize(aContainerWM);
1726   }
1727   return icoord;
1728 }
1729 
1730 /* static */
RepositionFrame(nsIFrame * aFrame,bool aIsEvenLevel,nscoord aStartOrEnd,nsContinuationStates * aContinuationStates,WritingMode aContainerWM,bool aContainerReverseDir,const nsSize & aContainerSize)1731 nscoord nsBidiPresUtils::RepositionFrame(
1732     nsIFrame* aFrame, bool aIsEvenLevel, nscoord aStartOrEnd,
1733     nsContinuationStates* aContinuationStates, WritingMode aContainerWM,
1734     bool aContainerReverseDir, const nsSize& aContainerSize) {
1735   nscoord lineSize =
1736       aContainerWM.IsVertical() ? aContainerSize.height : aContainerSize.width;
1737   NS_ASSERTION(lineSize != NS_UNCONSTRAINEDSIZE,
1738                "Unconstrained inline line size in bidi frame reordering");
1739   if (!aFrame) return 0;
1740 
1741   bool isFirst, isLast;
1742   WritingMode frameWM = aFrame->GetWritingMode();
1743   IsFirstOrLast(aFrame, aContinuationStates,
1744                 aContainerWM.IsBidiLTR() == frameWM.IsBidiLTR(),
1745                 isFirst /* out */, isLast /* out */);
1746 
1747   // We only need the margin if the frame is first or last in its own
1748   // writing mode, but we're traversing the frames in the order of the
1749   // container's writing mode. To get the right values, we set start and
1750   // end margins on a logical margin in the frame's writing mode, and
1751   // then convert the margin to the container's writing mode to set the
1752   // coordinates.
1753 
1754   // This method is called from nsBlockFrame::PlaceLine via the call to
1755   // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
1756   // have been reflowed, which is required for GetUsedMargin/Border/Padding
1757   nscoord frameISize = aFrame->ISize();
1758   LogicalMargin frameMargin = aFrame->GetLogicalUsedMargin(frameWM);
1759   LogicalMargin borderPadding = aFrame->GetLogicalUsedBorderAndPadding(frameWM);
1760   // Since the visual order of frame could be different from the continuation
1761   // order, we need to remove any inline border/padding [that is already applied
1762   // based on continuation order] and then add it back based on the visual order
1763   // (i.e. isFirst/isLast) to get the correct isize for the current frame.
1764   // We don't need to do that for 'box-decoration-break:clone' because then all
1765   // continuations have border/padding/margin applied.
1766   if (aFrame->StyleBorder()->mBoxDecorationBreak ==
1767       StyleBoxDecorationBreak::Slice) {
1768     // First remove the border/padding that was applied based on logical order.
1769     if (!aFrame->GetPrevContinuation()) {
1770       frameISize -= borderPadding.IStart(frameWM);
1771     }
1772     if (!aFrame->GetNextContinuation()) {
1773       frameISize -= borderPadding.IEnd(frameWM);
1774     }
1775     // Set margin/border/padding based on visual order.
1776     if (!isFirst) {
1777       frameMargin.IStart(frameWM) = 0;
1778       borderPadding.IStart(frameWM) = 0;
1779     }
1780     if (!isLast) {
1781       frameMargin.IEnd(frameWM) = 0;
1782       borderPadding.IEnd(frameWM) = 0;
1783     }
1784     // Add the border/padding which is now based on visual order.
1785     frameISize += borderPadding.IStartEnd(frameWM);
1786   }
1787 
1788   nscoord icoord = 0;
1789   if (IsBidiLeaf(aFrame)) {
1790     icoord +=
1791         frameWM.IsOrthogonalTo(aContainerWM) ? aFrame->BSize() : frameISize;
1792   } else if (RubyUtils::IsRubyBox(aFrame->Type())) {
1793     icoord += RepositionRubyFrame(aFrame, aContinuationStates, aContainerWM,
1794                                   borderPadding);
1795   } else {
1796     bool reverseDir = aIsEvenLevel != frameWM.IsBidiLTR();
1797     icoord += reverseDir ? borderPadding.IEnd(frameWM)
1798                          : borderPadding.IStart(frameWM);
1799     LogicalSize logicalSize(frameWM, frameISize, aFrame->BSize());
1800     nsSize frameSize = logicalSize.GetPhysicalSize(frameWM);
1801     // Reposition the child frames
1802     for (nsFrameList::Enumerator e(aFrame->PrincipalChildList()); !e.AtEnd();
1803          e.Next()) {
1804       icoord +=
1805           RepositionFrame(e.get(), aIsEvenLevel, icoord, aContinuationStates,
1806                           frameWM, reverseDir, frameSize);
1807     }
1808     icoord += reverseDir ? borderPadding.IStart(frameWM)
1809                          : borderPadding.IEnd(frameWM);
1810   }
1811 
1812   // In the following variables, if aContainerReverseDir is true, i.e.
1813   // the container is positioning its children in reverse of its logical
1814   // direction, the "StartOrEnd" refers to the distance from the frame
1815   // to the inline end edge of the container, elsewise, it refers to the
1816   // distance to the inline start edge.
1817   const LogicalMargin margin = frameMargin.ConvertTo(aContainerWM, frameWM);
1818   nscoord marginStartOrEnd = aContainerReverseDir ? margin.IEnd(aContainerWM)
1819                                                   : margin.IStart(aContainerWM);
1820   nscoord frameStartOrEnd = aStartOrEnd + marginStartOrEnd;
1821 
1822   LogicalRect rect = aFrame->GetLogicalRect(aContainerWM, aContainerSize);
1823   rect.ISize(aContainerWM) = icoord;
1824   rect.IStart(aContainerWM) = aContainerReverseDir
1825                                   ? lineSize - frameStartOrEnd - icoord
1826                                   : frameStartOrEnd;
1827   aFrame->SetRect(aContainerWM, rect, aContainerSize);
1828 
1829   return icoord + margin.IStartEnd(aContainerWM);
1830 }
1831 
InitContinuationStates(nsIFrame * aFrame,nsContinuationStates * aContinuationStates)1832 void nsBidiPresUtils::InitContinuationStates(
1833     nsIFrame* aFrame, nsContinuationStates* aContinuationStates) {
1834   aContinuationStates->Insert(aFrame);
1835   if (!IsBidiLeaf(aFrame)) {
1836     // Continue for child frames
1837     for (nsIFrame* frame : aFrame->PrincipalChildList()) {
1838       InitContinuationStates(frame, aContinuationStates);
1839     }
1840   }
1841 }
1842 
1843 /* static */
RepositionInlineFrames(BidiLineData * aBld,WritingMode aLineWM,const nsSize & aContainerSize,nscoord aStart)1844 nscoord nsBidiPresUtils::RepositionInlineFrames(BidiLineData* aBld,
1845                                                 WritingMode aLineWM,
1846                                                 const nsSize& aContainerSize,
1847                                                 nscoord aStart) {
1848   nscoord start = aStart;
1849   nsIFrame* frame;
1850   int32_t count = aBld->mVisualFrames.Length();
1851   int32_t index;
1852   nsContinuationStates continuationStates;
1853 
1854   // Initialize continuation states to (nullptr, 0) for
1855   // each frame on the line.
1856   for (index = 0; index < count; index++) {
1857     InitContinuationStates(aBld->VisualFrameAt(index), &continuationStates);
1858   }
1859 
1860   // Reposition frames in visual order
1861   int32_t step, limit;
1862   if (aLineWM.IsBidiLTR()) {
1863     index = 0;
1864     step = 1;
1865     limit = count;
1866   } else {
1867     index = count - 1;
1868     step = -1;
1869     limit = -1;
1870   }
1871   for (; index != limit; index += step) {
1872     frame = aBld->VisualFrameAt(index);
1873     start += RepositionFrame(
1874         frame, !(IS_LEVEL_RTL(aBld->mLevels[aBld->mIndexMap[index]])), start,
1875         &continuationStates, aLineWM, false, aContainerSize);
1876   }
1877   return start;
1878 }
1879 
CheckLineOrder(nsIFrame * aFirstFrameOnLine,int32_t aNumFramesOnLine,nsIFrame ** aFirstVisual,nsIFrame ** aLastVisual)1880 bool nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine,
1881                                      int32_t aNumFramesOnLine,
1882                                      nsIFrame** aFirstVisual,
1883                                      nsIFrame** aLastVisual) {
1884   BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
1885   int32_t count = bld.FrameCount();
1886 
1887   if (aFirstVisual) {
1888     *aFirstVisual = bld.VisualFrameAt(0);
1889   }
1890   if (aLastVisual) {
1891     *aLastVisual = bld.VisualFrameAt(count - 1);
1892   }
1893 
1894   return bld.mIsReordered;
1895 }
1896 
GetFrameToRightOf(const nsIFrame * aFrame,nsIFrame * aFirstFrameOnLine,int32_t aNumFramesOnLine)1897 nsIFrame* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame,
1898                                              nsIFrame* aFirstFrameOnLine,
1899                                              int32_t aNumFramesOnLine) {
1900   BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
1901 
1902   int32_t count = bld.mVisualFrames.Length();
1903 
1904   if (aFrame == nullptr && count) return bld.VisualFrameAt(0);
1905 
1906   for (int32_t i = 0; i < count - 1; i++) {
1907     if (bld.VisualFrameAt(i) == aFrame) {
1908       return bld.VisualFrameAt(i + 1);
1909     }
1910   }
1911 
1912   return nullptr;
1913 }
1914 
GetFrameToLeftOf(const nsIFrame * aFrame,nsIFrame * aFirstFrameOnLine,int32_t aNumFramesOnLine)1915 nsIFrame* nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame* aFrame,
1916                                             nsIFrame* aFirstFrameOnLine,
1917                                             int32_t aNumFramesOnLine) {
1918   BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
1919 
1920   int32_t count = bld.mVisualFrames.Length();
1921 
1922   if (aFrame == nullptr && count) return bld.VisualFrameAt(count - 1);
1923 
1924   for (int32_t i = 1; i < count; i++) {
1925     if (bld.VisualFrameAt(i) == aFrame) {
1926       return bld.VisualFrameAt(i - 1);
1927     }
1928   }
1929 
1930   return nullptr;
1931 }
1932 
EnsureBidiContinuation(nsIFrame * aFrame,const nsLineList::iterator aLine,nsIFrame ** aNewFrame,int32_t aStart,int32_t aEnd)1933 inline void nsBidiPresUtils::EnsureBidiContinuation(
1934     nsIFrame* aFrame, const nsLineList::iterator aLine, nsIFrame** aNewFrame,
1935     int32_t aStart, int32_t aEnd) {
1936   MOZ_ASSERT(aNewFrame, "null OUT ptr");
1937   MOZ_ASSERT(aFrame, "aFrame is null");
1938 
1939   aFrame->AdjustOffsetsForBidi(aStart, aEnd);
1940   CreateContinuation(aFrame, aLine, aNewFrame, false);
1941 }
1942 
RemoveBidiContinuation(BidiParagraphData * aBpd,nsIFrame * aFrame,int32_t aFirstIndex,int32_t aLastIndex)1943 void nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData* aBpd,
1944                                              nsIFrame* aFrame,
1945                                              int32_t aFirstIndex,
1946                                              int32_t aLastIndex) {
1947   FrameBidiData bidiData = aFrame->GetBidiData();
1948   bidiData.precedingControl = kBidiLevelNone;
1949   for (int32_t index = aFirstIndex + 1; index <= aLastIndex; index++) {
1950     nsIFrame* frame = aBpd->FrameAt(index);
1951     if (frame != NS_BIDI_CONTROL_FRAME) {
1952       // Make the frame and its continuation ancestors fluid,
1953       // so they can be reused or deleted by normal reflow code
1954       frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData);
1955       frame->AddStateBits(NS_FRAME_IS_BIDI);
1956       while (frame && IsBidiSplittable(frame)) {
1957         nsIFrame* prev = frame->GetPrevContinuation();
1958         if (prev) {
1959           MakeContinuationFluid(prev, frame);
1960           frame = frame->GetParent();
1961         } else {
1962           break;
1963         }
1964       }
1965     }
1966   }
1967 
1968   // Make sure that the last continuation we made fluid does not itself have a
1969   // fluid continuation (this can happen when re-resolving after dynamic changes
1970   // to content)
1971   nsIFrame* lastFrame = aBpd->FrameAt(aLastIndex);
1972   MakeContinuationsNonFluidUpParentChain(lastFrame, lastFrame->GetNextInFlow());
1973 }
1974 
FormatUnicodeText(nsPresContext * aPresContext,char16_t * aText,int32_t & aTextLength,nsCharType aCharType)1975 nsresult nsBidiPresUtils::FormatUnicodeText(nsPresContext* aPresContext,
1976                                             char16_t* aText,
1977                                             int32_t& aTextLength,
1978                                             nsCharType aCharType) {
1979   nsresult rv = NS_OK;
1980   // ahmed
1981   // adjusted for correct numeral shaping
1982   uint32_t bidiOptions = aPresContext->GetBidi();
1983   switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) {
1984     case IBMBIDI_NUMERAL_HINDI:
1985       HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
1986       break;
1987 
1988     case IBMBIDI_NUMERAL_ARABIC:
1989       HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
1990       break;
1991 
1992     case IBMBIDI_NUMERAL_PERSIAN:
1993       HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN);
1994       break;
1995 
1996     case IBMBIDI_NUMERAL_REGULAR:
1997 
1998       switch (aCharType) {
1999         case eCharType_EuropeanNumber:
2000           HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
2001           break;
2002 
2003         case eCharType_ArabicNumber:
2004           HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
2005           break;
2006 
2007         default:
2008           break;
2009       }
2010       break;
2011 
2012     case IBMBIDI_NUMERAL_HINDICONTEXT:
2013       if (((GET_BIDI_OPTION_DIRECTION(bidiOptions) ==
2014             IBMBIDI_TEXTDIRECTION_RTL) &&
2015            (IS_ARABIC_DIGIT(aText[0]))) ||
2016           (eCharType_ArabicNumber == aCharType))
2017         HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
2018       else if (eCharType_EuropeanNumber == aCharType)
2019         HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
2020       break;
2021 
2022     case IBMBIDI_NUMERAL_PERSIANCONTEXT:
2023       if (((GET_BIDI_OPTION_DIRECTION(bidiOptions) ==
2024             IBMBIDI_TEXTDIRECTION_RTL) &&
2025            (IS_ARABIC_DIGIT(aText[0]))) ||
2026           (eCharType_ArabicNumber == aCharType))
2027         HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN);
2028       else if (eCharType_EuropeanNumber == aCharType)
2029         HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
2030       break;
2031 
2032     case IBMBIDI_NUMERAL_NOMINAL:
2033     default:
2034       break;
2035   }
2036 
2037   StripBidiControlCharacters(aText, aTextLength);
2038   return rv;
2039 }
2040 
StripBidiControlCharacters(char16_t * aText,int32_t & aTextLength)2041 void nsBidiPresUtils::StripBidiControlCharacters(char16_t* aText,
2042                                                  int32_t& aTextLength) {
2043   if ((nullptr == aText) || (aTextLength < 1)) {
2044     return;
2045   }
2046 
2047   int32_t stripLen = 0;
2048 
2049   for (int32_t i = 0; i < aTextLength; i++) {
2050     // XXX: This silently ignores surrogate characters.
2051     //      As of Unicode 4.0, all Bidi control characters are within the BMP.
2052     if (IsBidiControl((uint32_t)aText[i])) {
2053       ++stripLen;
2054     } else {
2055       aText[i - stripLen] = aText[i];
2056     }
2057   }
2058   aTextLength -= stripLen;
2059 }
2060 
2061 #if 0  // XXX: for the future use ???
2062 void
2063 RemoveDiacritics(char16_t* aText,
2064                  int32_t&   aTextLength)
2065 {
2066   if (aText && (aTextLength > 0) ) {
2067     int32_t offset = 0;
2068 
2069     for (int32_t i = 0; i < aTextLength && aText[i]; i++) {
2070       if (IS_BIDI_DIACRITIC(aText[i]) ) {
2071         ++offset;
2072         continue;
2073       }
2074       aText[i - offset] = aText[i];
2075     }
2076     aTextLength = i - offset;
2077     aText[aTextLength] = 0;
2078   }
2079 }
2080 #endif
2081 
CalculateCharType(nsBidi * aBidiEngine,const char16_t * aText,int32_t & aOffset,int32_t aCharTypeLimit,int32_t & aRunLimit,int32_t & aRunLength,int32_t & aRunCount,uint8_t & aCharType,uint8_t & aPrevCharType)2082 void nsBidiPresUtils::CalculateCharType(nsBidi* aBidiEngine,
2083                                         const char16_t* aText, int32_t& aOffset,
2084                                         int32_t aCharTypeLimit,
2085                                         int32_t& aRunLimit, int32_t& aRunLength,
2086                                         int32_t& aRunCount, uint8_t& aCharType,
2087                                         uint8_t& aPrevCharType)
2088 
2089 {
2090   bool strongTypeFound = false;
2091   int32_t offset;
2092   nsCharType charType;
2093 
2094   aCharType = eCharType_OtherNeutral;
2095 
2096   int32_t charLen;
2097   for (offset = aOffset; offset < aCharTypeLimit; offset += charLen) {
2098     // Make sure we give RTL chartype to all characters that would be classified
2099     // as Right-To-Left by a bidi platform.
2100     // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
2101     charLen = 1;
2102     uint32_t ch = aText[offset];
2103     if (IS_HEBREW_CHAR(ch)) {
2104       charType = eCharType_RightToLeft;
2105     } else if (IS_ARABIC_ALPHABETIC(ch)) {
2106       charType = eCharType_RightToLeftArabic;
2107     } else {
2108       if (offset + 1 < aCharTypeLimit &&
2109           NS_IS_SURROGATE_PAIR(ch, aText[offset + 1])) {
2110         ch = SURROGATE_TO_UCS4(ch, aText[offset + 1]);
2111         charLen = 2;
2112       }
2113       charType = unicode::GetBidiCat(ch);
2114     }
2115 
2116     if (!CHARTYPE_IS_WEAK(charType)) {
2117       if (strongTypeFound && (charType != aPrevCharType) &&
2118           (CHARTYPE_IS_RTL(charType) || CHARTYPE_IS_RTL(aPrevCharType))) {
2119         // Stop at this point to ensure uni-directionality of the text
2120         // (from platform's point of view).
2121         // Also, don't mix Arabic and Hebrew content (since platform may
2122         // provide BIDI support to one of them only).
2123         aRunLength = offset - aOffset;
2124         aRunLimit = offset;
2125         ++aRunCount;
2126         break;
2127       }
2128 
2129       if ((eCharType_RightToLeftArabic == aPrevCharType ||
2130            eCharType_ArabicNumber == aPrevCharType) &&
2131           eCharType_EuropeanNumber == charType) {
2132         charType = eCharType_ArabicNumber;
2133       }
2134 
2135       // Set PrevCharType to the last strong type in this frame
2136       // (for correct numeric shaping)
2137       aPrevCharType = charType;
2138 
2139       strongTypeFound = true;
2140       aCharType = charType;
2141     }
2142   }
2143   aOffset = offset;
2144 }
2145 
ProcessText(const char16_t * aText,int32_t aLength,nsBidiLevel aBaseLevel,nsPresContext * aPresContext,BidiProcessor & aprocessor,Mode aMode,nsBidiPositionResolve * aPosResolve,int32_t aPosResolveCount,nscoord * aWidth,nsBidi * aBidiEngine)2146 nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, int32_t aLength,
2147                                       nsBidiLevel aBaseLevel,
2148                                       nsPresContext* aPresContext,
2149                                       BidiProcessor& aprocessor, Mode aMode,
2150                                       nsBidiPositionResolve* aPosResolve,
2151                                       int32_t aPosResolveCount, nscoord* aWidth,
2152                                       nsBidi* aBidiEngine) {
2153   NS_ASSERTION((aPosResolve == nullptr) != (aPosResolveCount > 0),
2154                "Incorrect aPosResolve / aPosResolveCount arguments");
2155 
2156   int32_t runCount;
2157 
2158   nsAutoString textBuffer(aText, aLength);
2159   textBuffer.ReplaceChar(kSeparators, kSpace);
2160   const char16_t* text = textBuffer.get();
2161 
2162   nsresult rv = aBidiEngine->SetPara(text, aLength, aBaseLevel);
2163   if (NS_FAILED(rv)) return rv;
2164 
2165   rv = aBidiEngine->CountRuns(&runCount);
2166   if (NS_FAILED(rv)) return rv;
2167 
2168   nscoord xOffset = 0;
2169   nscoord width, xEndRun = 0;
2170   nscoord totalWidth = 0;
2171   int32_t i, start, limit, length;
2172   uint32_t visualStart = 0;
2173   uint8_t charType;
2174   uint8_t prevType = eCharType_LeftToRight;
2175 
2176   for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
2177     aPosResolve[nPosResolve].visualIndex = kNotFound;
2178     aPosResolve[nPosResolve].visualLeftTwips = kNotFound;
2179     aPosResolve[nPosResolve].visualWidth = kNotFound;
2180   }
2181 
2182   for (i = 0; i < runCount; i++) {
2183     nsBidiDirection dir = aBidiEngine->GetVisualRun(i, &start, &length);
2184 
2185     nsBidiLevel level;
2186     aBidiEngine->GetLogicalRun(start, &limit, &level);
2187 
2188     dir = DIRECTION_FROM_LEVEL(level);
2189     int32_t subRunLength = limit - start;
2190     int32_t lineOffset = start;
2191     int32_t typeLimit = std::min(limit, aLength);
2192     int32_t subRunCount = 1;
2193     int32_t subRunLimit = typeLimit;
2194 
2195     /*
2196      * If |level| is even, i.e. the direction of the run is left-to-right, we
2197      * render the subruns from left to right and increment the x-coordinate
2198      * |xOffset| by the width of each subrun after rendering.
2199      *
2200      * If |level| is odd, i.e. the direction of the run is right-to-left, we
2201      * render the subruns from right to left. We begin by incrementing |xOffset|
2202      * by the width of the whole run, and then decrement it by the width of each
2203      * subrun before rendering. After rendering all the subruns, we restore the
2204      * x-coordinate of the end of the run for the start of the next run.
2205      */
2206 
2207     if (dir == NSBIDI_RTL) {
2208       aprocessor.SetText(text + start, subRunLength, dir);
2209       width = aprocessor.GetWidth();
2210       xOffset += width;
2211       xEndRun = xOffset;
2212     }
2213 
2214     while (subRunCount > 0) {
2215       // CalculateCharType can increment subRunCount if the run
2216       // contains mixed character types
2217       CalculateCharType(aBidiEngine, text, lineOffset, typeLimit, subRunLimit,
2218                         subRunLength, subRunCount, charType, prevType);
2219 
2220       nsAutoString runVisualText;
2221       runVisualText.Assign(text + start, subRunLength);
2222       if (int32_t(runVisualText.Length()) < subRunLength)
2223         return NS_ERROR_OUT_OF_MEMORY;
2224       FormatUnicodeText(aPresContext, runVisualText.BeginWriting(),
2225                         subRunLength, (nsCharType)charType);
2226 
2227       aprocessor.SetText(runVisualText.get(), subRunLength, dir);
2228       width = aprocessor.GetWidth();
2229       totalWidth += width;
2230       if (dir == NSBIDI_RTL) {
2231         xOffset -= width;
2232       }
2233       if (aMode == MODE_DRAW) {
2234         aprocessor.DrawText(xOffset, width);
2235       }
2236 
2237       /*
2238        * The caller may request to calculate the visual position of one
2239        * or more characters.
2240        */
2241       for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
2242         nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
2243         /*
2244          * Did we already resolve this position's visual metric? If so, skip.
2245          */
2246         if (posResolve->visualLeftTwips != kNotFound) continue;
2247 
2248         /*
2249          * First find out if the logical position is within this run.
2250          */
2251         if (start <= posResolve->logicalIndex &&
2252             start + subRunLength > posResolve->logicalIndex) {
2253           /*
2254            * If this run is only one character long, we have an easy case:
2255            * the visual position is the x-coord of the start of the run
2256            * less the x-coord of the start of the whole text.
2257            */
2258           if (subRunLength == 1) {
2259             posResolve->visualIndex = visualStart;
2260             posResolve->visualLeftTwips = xOffset;
2261             posResolve->visualWidth = width;
2262           }
2263           /*
2264            * Otherwise, we need to measure the width of the run's part
2265            * which is to the visual left of the index.
2266            * In other words, the run is broken in two, around the logical index,
2267            * and we measure the part which is visually left.
2268            * If the run is right-to-left, this part will span from after the
2269            * index up to the end of the run; if it is left-to-right, this part
2270            * will span from the start of the run up to (and inclduing) the
2271            * character before the index.
2272            */
2273           else {
2274             /*
2275              * Here is a description of how the width of the current character
2276              * (posResolve->visualWidth) is calculated:
2277              *
2278              * LTR (current char: "P"):
2279              *    S A M P L E          (logical index: 3, visual index: 3)
2280              *    ^ (visualLeftPart)
2281              *    ^ (visualRightSide)
2282              *    visualLeftLength == 3
2283              *    ^^^^^^ (subWidth)
2284              *    ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2285              *          ^^ (posResolve->visualWidth)
2286              *
2287              * RTL (current char: "M"):
2288              *    E L P M A S          (logical index: 2, visual index: 3)
2289              *        ^ (visualLeftPart)
2290              *          ^ (visualRightSide)
2291              *    visualLeftLength == 3
2292              *    ^^^^^^ (subWidth)
2293              *    ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2294              *          ^^ (posResolve->visualWidth)
2295              */
2296             nscoord subWidth;
2297             // The position in the text where this run's "left part" begins.
2298             const char16_t* visualLeftPart;
2299             const char16_t* visualRightSide;
2300             if (dir == NSBIDI_RTL) {
2301               // One day, son, this could all be replaced with
2302               // mPresContext->GetBidiEngine().GetVisualIndex() ...
2303               posResolve->visualIndex =
2304                   visualStart +
2305                   (subRunLength - (posResolve->logicalIndex + 1 - start));
2306               // Skipping to the "left part".
2307               visualLeftPart = text + posResolve->logicalIndex + 1;
2308               // Skipping to the right side of the current character
2309               visualRightSide = visualLeftPart - 1;
2310             } else {
2311               posResolve->visualIndex =
2312                   visualStart + (posResolve->logicalIndex - start);
2313               // Skipping to the "left part".
2314               visualLeftPart = text + start;
2315               // In LTR mode this is the same as visualLeftPart
2316               visualRightSide = visualLeftPart;
2317             }
2318             // The delta between the start of the run and the left part's end.
2319             int32_t visualLeftLength = posResolve->visualIndex - visualStart;
2320             aprocessor.SetText(visualLeftPart, visualLeftLength, dir);
2321             subWidth = aprocessor.GetWidth();
2322             aprocessor.SetText(visualRightSide, visualLeftLength + 1, dir);
2323             posResolve->visualLeftTwips = xOffset + subWidth;
2324             posResolve->visualWidth = aprocessor.GetWidth() - subWidth;
2325           }
2326         }
2327       }
2328 
2329       if (dir == NSBIDI_LTR) {
2330         xOffset += width;
2331       }
2332 
2333       --subRunCount;
2334       start = lineOffset;
2335       subRunLimit = typeLimit;
2336       subRunLength = typeLimit - lineOffset;
2337     }  // while
2338     if (dir == NSBIDI_RTL) {
2339       xOffset = xEndRun;
2340     }
2341 
2342     visualStart += length;
2343   }  // for
2344 
2345   if (aWidth) {
2346     *aWidth = totalWidth;
2347   }
2348   return NS_OK;
2349 }
2350 
2351 class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final
2352     : public nsBidiPresUtils::BidiProcessor {
2353  public:
2354   typedef mozilla::gfx::DrawTarget DrawTarget;
2355 
nsIRenderingContextBidiProcessor(gfxContext * aCtx,DrawTarget * aTextRunConstructionDrawTarget,nsFontMetrics * aFontMetrics,const nsPoint & aPt)2356   nsIRenderingContextBidiProcessor(gfxContext* aCtx,
2357                                    DrawTarget* aTextRunConstructionDrawTarget,
2358                                    nsFontMetrics* aFontMetrics,
2359                                    const nsPoint& aPt)
2360       : mCtx(aCtx),
2361         mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget),
2362         mFontMetrics(aFontMetrics),
2363         mPt(aPt),
2364         mText(nullptr),
2365         mLength(0) {}
2366 
~nsIRenderingContextBidiProcessor()2367   ~nsIRenderingContextBidiProcessor() { mFontMetrics->SetTextRunRTL(false); }
2368 
SetText(const char16_t * aText,int32_t aLength,nsBidiDirection aDirection)2369   virtual void SetText(const char16_t* aText, int32_t aLength,
2370                        nsBidiDirection aDirection) override {
2371     mFontMetrics->SetTextRunRTL(aDirection == NSBIDI_RTL);
2372     mText = aText;
2373     mLength = aLength;
2374   }
2375 
GetWidth()2376   virtual nscoord GetWidth() override {
2377     return nsLayoutUtils::AppUnitWidthOfString(mText, mLength, *mFontMetrics,
2378                                                mTextRunConstructionDrawTarget);
2379   }
2380 
DrawText(nscoord aIOffset,nscoord)2381   virtual void DrawText(nscoord aIOffset, nscoord) override {
2382     nsPoint pt(mPt);
2383     if (mFontMetrics->GetVertical()) {
2384       pt.y += aIOffset;
2385     } else {
2386       pt.x += aIOffset;
2387     }
2388     mFontMetrics->DrawString(mText, mLength, pt.x, pt.y, mCtx,
2389                              mTextRunConstructionDrawTarget);
2390   }
2391 
2392  private:
2393   gfxContext* mCtx;
2394   DrawTarget* mTextRunConstructionDrawTarget;
2395   nsFontMetrics* mFontMetrics;
2396   nsPoint mPt;
2397   const char16_t* mText;
2398   int32_t mLength;
2399 };
2400 
ProcessTextForRenderingContext(const char16_t * aText,int32_t aLength,nsBidiLevel aBaseLevel,nsPresContext * aPresContext,gfxContext & aRenderingContext,DrawTarget * aTextRunConstructionDrawTarget,nsFontMetrics & aFontMetrics,Mode aMode,nscoord aX,nscoord aY,nsBidiPositionResolve * aPosResolve,int32_t aPosResolveCount,nscoord * aWidth)2401 nsresult nsBidiPresUtils::ProcessTextForRenderingContext(
2402     const char16_t* aText, int32_t aLength, nsBidiLevel aBaseLevel,
2403     nsPresContext* aPresContext, gfxContext& aRenderingContext,
2404     DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics& aFontMetrics,
2405     Mode aMode, nscoord aX, nscoord aY, nsBidiPositionResolve* aPosResolve,
2406     int32_t aPosResolveCount, nscoord* aWidth) {
2407   nsIRenderingContextBidiProcessor processor(&aRenderingContext,
2408                                              aTextRunConstructionDrawTarget,
2409                                              &aFontMetrics, nsPoint(aX, aY));
2410   return ProcessText(aText, aLength, aBaseLevel, aPresContext, processor, aMode,
2411                      aPosResolve, aPosResolveCount, aWidth,
2412                      &aPresContext->GetBidiEngine());
2413 }
2414 
2415 /* static */
BidiLevelFromStyle(ComputedStyle * aComputedStyle)2416 nsBidiLevel nsBidiPresUtils::BidiLevelFromStyle(ComputedStyle* aComputedStyle) {
2417   if (aComputedStyle->StyleTextReset()->mUnicodeBidi &
2418       NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
2419     return NSBIDI_DEFAULT_LTR;
2420   }
2421 
2422   if (aComputedStyle->StyleVisibility()->mDirection == StyleDirection::Rtl) {
2423     return NSBIDI_RTL;
2424   }
2425 
2426   return NSBIDI_LTR;
2427 }
2428