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/. */
7 #include "nsBidiPresUtils.h"
9 #include "mozilla/intl/Bidi.h"
10 #include "mozilla/Casting.h"
11 #include "mozilla/IntegerRange.h"
12 #include "mozilla/Maybe.h"
13 #include "mozilla/PresShell.h"
14 #include "mozilla/dom/Text.h"
16 #include "gfxContext.h"
17 #include "nsFontMetrics.h"
18 #include "nsGkAtoms.h"
19 #include "nsPresContext.h"
20 #include "nsBidiUtils.h"
21 #include "nsCSSFrameConstructor.h"
22 #include "nsContainerFrame.h"
23 #include "nsInlineFrame.h"
24 #include "nsPlaceholderFrame.h"
25 #include "nsPointerHashKeys.h"
26 #include "nsFirstLetterFrame.h"
27 #include "nsUnicodeProperties.h"
28 #include "nsTextFrame.h"
29 #include "nsBlockFrame.h"
30 #include "nsIFrameInlines.h"
31 #include "nsStyleStructInlines.h"
32 #include "RubyUtils.h"
33 #include "nsRubyFrame.h"
34 #include "nsRubyBaseFrame.h"
35 #include "nsRubyTextFrame.h"
36 #include "nsRubyBaseContainerFrame.h"
37 #include "nsRubyTextContainerFrame.h"
41 #undef REALLY_NOISY_BIDI
43 using namespace mozilla
;
45 using BidiEngine
= intl::Bidi
;
46 using BidiClass
= intl::BidiClass
;
47 using BidiDirection
= intl::BidiDirection
;
48 using BidiEmbeddingLevel
= intl::BidiEmbeddingLevel
;
50 static const char16_t kSpace
= 0x0020;
51 static const char16_t kZWSP
= 0x200B;
52 static const char16_t kLineSeparator
= 0x2028;
53 static const char16_t kObjectSubstitute
= 0xFFFC;
54 static const char16_t kLRE
= 0x202A;
55 static const char16_t kRLE
= 0x202B;
56 static const char16_t kLRO
= 0x202D;
57 static const char16_t kRLO
= 0x202E;
58 static const char16_t kPDF
= 0x202C;
59 static const char16_t kLRI
= 0x2066;
60 static const char16_t kRLI
= 0x2067;
61 static const char16_t kFSI
= 0x2068;
62 static const char16_t kPDI
= 0x2069;
63 // All characters with Bidi type Segment Separator or Block Separator
64 static const char16_t kSeparators
[] = {
65 char16_t('\t'), char16_t('\r'), char16_t('\n'), char16_t(0xb),
66 char16_t(0x1c), char16_t(0x1d), char16_t(0x1e), char16_t(0x1f),
67 char16_t(0x85), char16_t(0x2029), char16_t(0)};
69 #define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1)
71 // This exists just to be a type; the value doesn't matter.
72 enum class BidiControlFrameType
{ Value
};
74 static bool IsIsolateControl(char16_t aChar
) {
75 return aChar
== kLRI
|| aChar
== kRLI
|| aChar
== kFSI
;
78 // Given a ComputedStyle, return any bidi control character necessary to
79 // implement style properties that override directionality (i.e. if it has
80 // unicode-bidi:bidi-override, or text-orientation:upright in vertical
81 // writing mode) when applying the bidi algorithm.
83 // Returns 0 if no override control character is implied by this style.
84 static char16_t
GetBidiOverride(ComputedStyle
* aComputedStyle
) {
85 const nsStyleVisibility
* vis
= aComputedStyle
->StyleVisibility();
86 if ((vis
->mWritingMode
== StyleWritingModeProperty::VerticalRl
||
87 vis
->mWritingMode
== StyleWritingModeProperty::VerticalLr
) &&
88 vis
->mTextOrientation
== StyleTextOrientation::Upright
) {
91 const nsStyleTextReset
* text
= aComputedStyle
->StyleTextReset();
92 if (text
->mUnicodeBidi
== StyleUnicodeBidi::BidiOverride
||
93 text
->mUnicodeBidi
== StyleUnicodeBidi::IsolateOverride
) {
94 return StyleDirection::Rtl
== vis
->mDirection
? kRLO
: kLRO
;
99 // Given a ComputedStyle, return any bidi control character necessary to
100 // implement style properties that affect bidi resolution (i.e. if it
101 // has unicode-bidiembed, isolate, or plaintext) when applying the bidi
104 // Returns 0 if no control character is implied by the style.
106 // Note that GetBidiOverride and GetBidiControl need to be separate
107 // because in the case of unicode-bidi:isolate-override we need both
109 static char16_t
GetBidiControl(ComputedStyle
* aComputedStyle
) {
110 const nsStyleVisibility
* vis
= aComputedStyle
->StyleVisibility();
111 const nsStyleTextReset
* text
= aComputedStyle
->StyleTextReset();
112 switch (text
->mUnicodeBidi
) {
113 case StyleUnicodeBidi::Embed
:
114 return StyleDirection::Rtl
== vis
->mDirection
? kRLE
: kLRE
;
115 case StyleUnicodeBidi::Isolate
:
116 // <bdi> element already has its directionality set from content so
117 // we never need to return kFSI.
118 return StyleDirection::Rtl
== vis
->mDirection
? kRLI
: kLRI
;
119 case StyleUnicodeBidi::IsolateOverride
:
120 case StyleUnicodeBidi::Plaintext
:
122 case StyleUnicodeBidi::Normal
:
123 case StyleUnicodeBidi::BidiOverride
:
131 static inline bool AreContinuationsInOrder(nsIFrame
* aFrame1
,
133 nsIFrame
* f
= aFrame1
;
135 f
= f
->GetNextContinuation();
136 } while (f
&& f
!= aFrame2
);
141 struct MOZ_STACK_CLASS BidiParagraphData
{
143 FrameInfo(nsIFrame
* aFrame
, nsBlockInFlowLineIterator
& aLineIter
)
145 mBlockContainer(aLineIter
.GetContainer()),
146 mInOverflow(aLineIter
.GetInOverflow()) {}
148 explicit FrameInfo(BidiControlFrameType aValue
)
149 : mFrame(NS_BIDI_CONTROL_FRAME
),
150 mBlockContainer(nullptr),
151 mInOverflow(false) {}
154 : mFrame(nullptr), mBlockContainer(nullptr), mInOverflow(false) {}
158 // The block containing mFrame (i.e., which continuation).
159 nsBlockFrame
* mBlockContainer
;
161 // true if mFrame is in mBlockContainer's overflow lines, false if
166 nsAutoString mBuffer
;
167 AutoTArray
<char16_t
, 16> mEmbeddingStack
;
168 AutoTArray
<FrameInfo
, 16> mLogicalFrames
;
169 nsTHashMap
<nsPtrHashKey
<const nsIContent
>, int32_t> mContentToFrameIndex
;
170 // Cached presentation context for the frames we're processing.
171 nsPresContext
* mPresContext
;
174 BidiEmbeddingLevel mParaLevel
;
175 nsIContent
* mPrevContent
;
178 * This class is designed to manage the process of mapping a frame to
179 * the line that it's in, when we know that (a) the frames we ask it
180 * about are always in the block's lines and (b) each successive frame
181 * we ask it about is the same as or after (in depth-first search
182 * order) the previous.
184 * Since we move through the lines at a different pace in Traverse and
185 * ResolveParagraph, we use one of these for each.
187 * The state of the mapping is also different between TraverseFrames
188 * and ResolveParagraph since since resolving can call functions
189 * (EnsureBidiContinuation or SplitInlineAncestors) that can create
190 * new frames and thus break lines.
192 * The TraverseFrames iterator is only used in some edge cases.
194 struct FastLineIterator
{
195 FastLineIterator() : mPrevFrame(nullptr), mNextLineStart(nullptr) {}
197 // These iterators *and* mPrevFrame track the line list that we're
200 // mPrevFrame, if non-null, should be either the frame we're currently
201 // handling (in ResolveParagraph or TraverseFrames, depending on the
202 // iterator) or a frame before it, and is also guaranteed to either be in
203 // mCurrentLine or have been in mCurrentLine until recently.
205 // In case the splitting causes block frames to break lines, however, we
206 // also track the first frame of the next line. If that changes, it means
207 // we've broken lines and we have to invalidate mPrevFrame.
208 nsBlockInFlowLineIterator mLineIterator
;
209 nsIFrame
* mPrevFrame
;
210 nsIFrame
* mNextLineStart
;
212 nsLineList::iterator
GetLine() { return mLineIterator
.GetLine(); }
214 static bool IsFrameInCurrentLine(nsBlockInFlowLineIterator
* aLineIter
,
215 nsIFrame
* aPrevFrame
, nsIFrame
* aFrame
) {
216 MOZ_ASSERT(!aPrevFrame
|| aLineIter
->GetLine()->Contains(aPrevFrame
),
217 "aPrevFrame must be in aLineIter's current line");
218 nsIFrame
* endFrame
= aLineIter
->IsLastLineInList()
220 : aLineIter
->GetLine().next()->mFirstChild
;
221 nsIFrame
* startFrame
=
222 aPrevFrame
? aPrevFrame
: aLineIter
->GetLine()->mFirstChild
;
223 for (nsIFrame
* frame
= startFrame
; frame
&& frame
!= endFrame
;
224 frame
= frame
->GetNextSibling()) {
225 if (frame
== aFrame
) return true;
230 static nsIFrame
* FirstChildOfNextLine(
231 nsBlockInFlowLineIterator
& aIterator
) {
232 const nsLineList::iterator line
= aIterator
.GetLine();
233 const nsLineList::iterator lineEnd
= aIterator
.End();
234 MOZ_ASSERT(line
!= lineEnd
, "iterator should start off valid");
235 const nsLineList::iterator nextLine
= line
.next();
237 return nextLine
!= lineEnd
? nextLine
->mFirstChild
: nullptr;
240 // Advance line iterator to the line containing aFrame, assuming
241 // that aFrame is already in the line list our iterator is iterating
243 void AdvanceToFrame(nsIFrame
* aFrame
) {
244 if (mPrevFrame
&& FirstChildOfNextLine(mLineIterator
) != mNextLineStart
) {
245 // Something has caused a line to split. We need to invalidate
246 // mPrevFrame since it may now be in a *later* line, though it may
247 // still be in this line, so we need to start searching for it from
248 // the start of this line.
249 mPrevFrame
= nullptr;
251 nsIFrame
* child
= aFrame
;
252 nsIFrame
* parent
= nsLayoutUtils::GetParentOrPlaceholderFor(child
);
253 while (parent
&& !parent
->IsBlockFrameOrSubclass()) {
255 parent
= nsLayoutUtils::GetParentOrPlaceholderFor(child
);
257 MOZ_ASSERT(parent
, "aFrame is not a descendent of a block frame");
258 while (!IsFrameInCurrentLine(&mLineIterator
, mPrevFrame
, child
)) {
262 mLineIterator
.Next();
263 MOZ_ASSERT(hasNext
, "Can't find frame in lines!");
264 mPrevFrame
= nullptr;
267 mNextLineStart
= FirstChildOfNextLine(mLineIterator
);
270 // Advance line iterator to the line containing aFrame, which may
271 // require moving forward into overflow lines or into a later
272 // continuation (or both).
273 void AdvanceToLinesAndFrame(const FrameInfo
& aFrameInfo
) {
274 if (mLineIterator
.GetContainer() != aFrameInfo
.mBlockContainer
||
275 mLineIterator
.GetInOverflow() != aFrameInfo
.mInOverflow
) {
277 mLineIterator
.GetContainer() == aFrameInfo
.mBlockContainer
278 ? (!mLineIterator
.GetInOverflow() && aFrameInfo
.mInOverflow
)
279 : (!mLineIterator
.GetContainer() ||
280 AreContinuationsInOrder(mLineIterator
.GetContainer(),
281 aFrameInfo
.mBlockContainer
)),
282 "must move forwards");
283 nsBlockFrame
* block
= aFrameInfo
.mBlockContainer
;
284 nsLineList::iterator lines
=
285 aFrameInfo
.mInOverflow
? block
->GetOverflowLines()->mLines
.begin()
286 : block
->LinesBegin();
288 nsBlockInFlowLineIterator(block
, lines
, aFrameInfo
.mInOverflow
);
289 mPrevFrame
= nullptr;
291 AdvanceToFrame(aFrameInfo
.mFrame
);
295 FastLineIterator mCurrentTraverseLine
, mCurrentResolveLine
;
298 // Only used for NOISY debug output.
299 // Matches the current TraverseFrames state, not the ResolveParagraph
301 nsBlockFrame
* mCurrentBlock
;
304 explicit BidiParagraphData(nsBlockFrame
* aBlockFrame
)
305 : mPresContext(aBlockFrame
->PresContext()),
306 mIsVisual(mPresContext
->IsVisualMode()),
307 mRequiresBidi(false),
308 mParaLevel(nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame
->Style())),
309 mPrevContent(nullptr)
312 mCurrentBlock(aBlockFrame
)
315 if (mParaLevel
> 0) {
316 mRequiresBidi
= true;
321 * Drill up in content to detect whether this is an element that needs to
322 * be rendered with logical order even on visual pages.
324 * We always use logical order on form controls, firstly so that text
325 * entry will be in logical order, but also because visual pages were
326 * written with the assumption that even if the browser had no support
327 * for right-to-left text rendering, it would use native widgets with
328 * bidi support to display form controls.
330 * We also use logical order in XUL elements, since we expect that if a
331 * XUL element appears in a visual page, it will be generated by an XBL
332 * binding and contain localized text which will be in logical order.
334 for (nsIContent
* content
= aBlockFrame
->GetContent(); content
;
335 content
= content
->GetParent()) {
336 if (content
->IsXULElement() || content
->IsHTMLFormControlElement()) {
345 if (mPresContext
->BidiEngine().SetParagraph(mBuffer
, mParaLevel
).isErr()) {
346 return NS_ERROR_FAILURE
;
352 * mParaLevel can be BidiDirection::LTR as well as
353 * BidiDirection::LTR or BidiDirection::RTL.
354 * GetParagraphEmbeddingLevel() returns the actual (resolved) paragraph level
355 * which is always either BidiDirection::LTR or
358 BidiEmbeddingLevel
GetParagraphEmbeddingLevel() {
359 BidiEmbeddingLevel paraLevel
= mParaLevel
;
360 if (paraLevel
== BidiEmbeddingLevel::DefaultLTR() ||
361 paraLevel
== BidiEmbeddingLevel::DefaultRTL()) {
362 paraLevel
= mPresContext
->BidiEngine().GetParagraphEmbeddingLevel();
367 BidiEngine::ParagraphDirection
GetParagraphDirection() {
368 return mPresContext
->BidiEngine().GetParagraphDirection();
371 nsresult
CountRuns(int32_t* runCount
) {
372 auto result
= mPresContext
->BidiEngine().CountRuns();
373 if (result
.isErr()) {
374 return NS_ERROR_FAILURE
;
376 *runCount
= result
.unwrap();
380 void GetLogicalRun(int32_t aLogicalStart
, int32_t* aLogicalLimit
,
381 BidiEmbeddingLevel
* aLevel
) {
382 mPresContext
->BidiEngine().GetLogicalRun(aLogicalStart
, aLogicalLimit
,
385 *aLevel
= GetParagraphEmbeddingLevel();
390 mLogicalFrames
.Clear();
391 mContentToFrameIndex
.Clear();
392 mBuffer
.SetLength(0);
393 mPrevContent
= nullptr;
394 for (uint32_t i
= 0; i
< mEmbeddingStack
.Length(); ++i
) {
395 mBuffer
.Append(mEmbeddingStack
[i
]);
396 mLogicalFrames
.AppendElement(FrameInfo(BidiControlFrameType::Value
));
400 void AppendFrame(nsIFrame
* aFrame
, FastLineIterator
& aLineIter
,
401 nsIContent
* aContent
= nullptr) {
403 mContentToFrameIndex
.InsertOrUpdate(aContent
, FrameCount());
406 // We don't actually need to advance aLineIter to aFrame, since all we use
407 // from it is the block and is-overflow state, which are correct already.
408 mLogicalFrames
.AppendElement(FrameInfo(aFrame
, aLineIter
.mLineIterator
));
411 void AdvanceAndAppendFrame(nsIFrame
** aFrame
, FastLineIterator
& aLineIter
,
412 nsIFrame
** aNextSibling
) {
413 nsIFrame
* frame
= *aFrame
;
414 nsIFrame
* nextSibling
= *aNextSibling
;
416 frame
= frame
->GetNextContinuation();
418 AppendFrame(frame
, aLineIter
, nullptr);
421 * If we have already overshot the saved next-sibling while
422 * scanning the frame's continuations, advance it.
424 if (frame
== nextSibling
) {
425 nextSibling
= frame
->GetNextSibling();
430 *aNextSibling
= nextSibling
;
433 int32_t GetLastFrameForContent(nsIContent
* aContent
) {
434 return mContentToFrameIndex
.Get(aContent
);
437 int32_t FrameCount() { return mLogicalFrames
.Length(); }
439 int32_t BufferLength() { return mBuffer
.Length(); }
441 nsIFrame
* FrameAt(int32_t aIndex
) { return mLogicalFrames
[aIndex
].mFrame
; }
443 const FrameInfo
& FrameInfoAt(int32_t aIndex
) {
444 return mLogicalFrames
[aIndex
];
447 void AppendUnichar(char16_t aCh
) { mBuffer
.Append(aCh
); }
449 void AppendString(const nsDependentSubstring
& aString
) {
450 mBuffer
.Append(aString
);
453 void AppendControlChar(char16_t aCh
) {
454 mLogicalFrames
.AppendElement(FrameInfo(BidiControlFrameType::Value
));
458 void PushBidiControl(char16_t aCh
) {
459 AppendControlChar(aCh
);
460 mEmbeddingStack
.AppendElement(aCh
);
463 void AppendPopChar(char16_t aCh
) {
464 AppendControlChar(IsIsolateControl(aCh
) ? kPDI
: kPDF
);
467 void PopBidiControl(char16_t aCh
) {
468 MOZ_ASSERT(mEmbeddingStack
.Length(), "embedding/override underflow");
469 MOZ_ASSERT(aCh
== mEmbeddingStack
.LastElement());
471 mEmbeddingStack
.RemoveLastElement();
474 void ClearBidiControls() {
475 for (char16_t c
: Reversed(mEmbeddingStack
)) {
481 struct MOZ_STACK_CLASS BidiLineData
{
482 AutoTArray
<nsIFrame
*, 16> mLogicalFrames
;
483 AutoTArray
<nsIFrame
*, 16> mVisualFrames
;
484 AutoTArray
<int32_t, 16> mIndexMap
;
485 AutoTArray
<BidiEmbeddingLevel
, 16> mLevels
;
488 BidiLineData(nsIFrame
* aFirstFrameOnLine
, int32_t aNumFramesOnLine
) {
490 * Initialize the logically-ordered array of frames using the top-level
491 * frames of a single line
493 bool isReordered
= false;
494 bool hasRTLFrames
= false;
495 bool hasVirtualControls
= false;
497 auto appendFrame
= [&](nsIFrame
* frame
, BidiEmbeddingLevel level
) {
498 mLogicalFrames
.AppendElement(frame
);
499 mLevels
.AppendElement(level
);
500 mIndexMap
.AppendElement(0);
506 bool firstFrame
= true;
507 for (nsIFrame
* frame
= aFirstFrameOnLine
; frame
&& aNumFramesOnLine
--;
508 frame
= frame
->GetNextSibling()) {
509 FrameBidiData bidiData
= nsBidiPresUtils::GetFrameBidiData(frame
);
510 // Ignore virtual control before the first frame. Doing so should
511 // not affect the visual result, but could avoid running into the
512 // stripping code below for many cases.
513 if (!firstFrame
&& bidiData
.precedingControl
!= kBidiLevelNone
) {
514 appendFrame(NS_BIDI_CONTROL_FRAME
, bidiData
.precedingControl
);
515 hasVirtualControls
= true;
517 appendFrame(frame
, bidiData
.embeddingLevel
);
522 BidiEngine::ReorderVisual(mLevels
.Elements(), FrameCount(),
523 mIndexMap
.Elements());
525 // Strip virtual frames
526 if (hasVirtualControls
) {
527 auto originalCount
= mLogicalFrames
.Length();
528 AutoTArray
<int32_t, 16> realFrameMap
;
529 realFrameMap
.SetCapacity(originalCount
);
531 for (auto i
: IntegerRange(originalCount
)) {
532 if (mLogicalFrames
[i
] == NS_BIDI_CONTROL_FRAME
) {
533 realFrameMap
.AppendElement(-1);
535 mLogicalFrames
[count
] = mLogicalFrames
[i
];
536 mLevels
[count
] = mLevels
[i
];
537 realFrameMap
.AppendElement(count
);
541 // Only keep index map for real frames.
542 for (size_t i
= 0, j
= 0; i
< originalCount
; ++i
) {
543 auto newIndex
= realFrameMap
[mIndexMap
[i
]];
544 if (newIndex
!= -1) {
545 mIndexMap
[j
] = newIndex
;
549 mLogicalFrames
.TruncateLength(count
);
550 mLevels
.TruncateLength(count
);
551 mIndexMap
.TruncateLength(count
);
554 for (int32_t i
= 0; i
< FrameCount(); i
++) {
555 mVisualFrames
.AppendElement(LogicalFrameAt(mIndexMap
[i
]));
556 if (i
!= mIndexMap
[i
]) {
561 // If there's an RTL frame, assume the line is reordered
562 mIsReordered
= isReordered
|| hasRTLFrames
;
565 int32_t FrameCount() const { return mLogicalFrames
.Length(); }
567 nsIFrame
* LogicalFrameAt(int32_t aIndex
) const {
568 return mLogicalFrames
[aIndex
];
571 nsIFrame
* VisualFrameAt(int32_t aIndex
) const {
572 return mVisualFrames
[aIndex
];
578 void MOZ_EXPORT
DumpFrameArray(const nsTArray
<nsIFrame
*>& aFrames
) {
579 for (nsIFrame
* frame
: aFrames
) {
580 if (frame
== NS_BIDI_CONTROL_FRAME
) {
581 fprintf_stderr(stderr
, "(Bidi control frame)\n");
588 void MOZ_EXPORT
DumpBidiLine(BidiLineData
* aData
, bool aVisualOrder
) {
589 DumpFrameArray(aVisualOrder
? aData
->mVisualFrames
: aData
->mLogicalFrames
);
594 /* Some helper methods for Resolve() */
596 // Should this frame be split between text runs?
597 static bool IsBidiSplittable(nsIFrame
* aFrame
) {
599 // Bidi inline containers should be split, unless they're line frames.
600 LayoutFrameType frameType
= aFrame
->Type();
601 return (aFrame
->IsBidiInlineContainer() &&
602 frameType
!= LayoutFrameType::Line
) ||
603 frameType
== LayoutFrameType::Text
;
606 // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
607 static bool IsBidiLeaf(const nsIFrame
* aFrame
) {
608 nsIFrame
* kid
= aFrame
->PrincipalChildList().FirstChild();
610 if (aFrame
->IsBidiInlineContainer() ||
611 RubyUtils::IsRubyBox(aFrame
->Type())) {
619 * Create non-fluid continuations for the ancestors of a given frame all the way
620 * up the frame tree until we hit a non-splittable frame (a line or a block).
622 * @param aParent the first parent frame to be split
623 * @param aFrame the child frames after this frame are reparented to the
624 * newly-created continuation of aParent.
625 * If aFrame is null, all the children of aParent are reparented.
627 static void SplitInlineAncestors(nsContainerFrame
* aParent
,
628 nsLineList::iterator aLine
, nsIFrame
* aFrame
) {
629 PresShell
* presShell
= aParent
->PresShell();
630 nsIFrame
* frame
= aFrame
;
631 nsContainerFrame
* parent
= aParent
;
632 nsContainerFrame
* newParent
;
634 while (IsBidiSplittable(parent
)) {
635 nsContainerFrame
* grandparent
= parent
->GetParent();
636 NS_ASSERTION(grandparent
,
637 "Couldn't get parent's parent in "
638 "nsBidiPresUtils::SplitInlineAncestors");
640 // Split the child list after |frame|, unless it is the last child.
641 if (!frame
|| frame
->GetNextSibling()) {
642 newParent
= static_cast<nsContainerFrame
*>(
643 presShell
->FrameConstructor()->CreateContinuingFrame(
644 parent
, grandparent
, false));
646 nsFrameList tail
= parent
->StealFramesAfter(frame
);
648 // Reparent views as necessary
649 nsContainerFrame::ReparentFrameViewList(tail
, parent
, newParent
);
651 // The parent's continuation adopts the siblings after the split.
652 MOZ_ASSERT(!newParent
->IsBlockFrameOrSubclass(),
653 "blocks should not be IsBidiSplittable");
654 newParent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, nullptr,
655 nullptr, std::move(tail
));
657 // While passing &aLine to InsertFrames for a non-block isn't harmful
658 // because it's a no-op, it doesn't really make sense. However, the
659 // MOZ_ASSERT() we need to guarantee that it's safe only works if the
660 // parent is actually the block.
661 const nsLineList::iterator
* parentLine
;
662 if (grandparent
->IsBlockFrameOrSubclass()) {
663 MOZ_ASSERT(aLine
->Contains(parent
));
666 parentLine
= nullptr;
669 // The list name FrameChildListID::NoReflowPrincipal would indicate we
671 grandparent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, parent
,
672 parentLine
, nsFrameList(newParent
, newParent
));
676 parent
= grandparent
;
680 static void MakeContinuationFluid(nsIFrame
* aFrame
, nsIFrame
* aNext
) {
681 NS_ASSERTION(!aFrame
->GetNextInFlow() || aFrame
->GetNextInFlow() == aNext
,
682 "next-in-flow is not next continuation!");
683 NS_ASSERTION(!aNext
->GetPrevInFlow() || aNext
->GetPrevInFlow() == aFrame
,
684 "prev-in-flow is not prev continuation!");
685 aFrame
->SetNextInFlow(aNext
);
688 static void MakeContinuationsNonFluidUpParentChain(nsIFrame
* aFrame
,
693 for (frame
= aFrame
, next
= aNext
;
694 frame
&& next
&& next
!= frame
&& next
== frame
->GetNextInFlow() &&
695 IsBidiSplittable(frame
);
696 frame
= frame
->GetParent(), next
= next
->GetParent()) {
697 frame
->SetNextContinuation(next
);
701 // If aFrame is the last child of its parent, convert bidi continuations to
702 // fluid continuations for all of its inline ancestors.
703 // If it isn't the last child, make sure that its continuation is fluid.
704 static void JoinInlineAncestors(nsIFrame
* aFrame
) {
705 nsIFrame
* frame
= aFrame
;
706 while (frame
&& IsBidiSplittable(frame
)) {
707 nsIFrame
* next
= frame
->GetNextContinuation();
709 MakeContinuationFluid(frame
, next
);
711 // Join the parent only as long as we're its last child.
712 if (frame
->GetNextSibling()) break;
713 frame
= frame
->GetParent();
717 static void CreateContinuation(nsIFrame
* aFrame
,
718 const nsLineList::iterator aLine
,
719 nsIFrame
** aNewFrame
, bool aIsFluid
) {
720 MOZ_ASSERT(aNewFrame
, "null OUT ptr");
721 MOZ_ASSERT(aFrame
, "null ptr");
723 *aNewFrame
= nullptr;
725 nsPresContext
* presContext
= aFrame
->PresContext();
726 PresShell
* presShell
= presContext
->PresShell();
727 NS_ASSERTION(presShell
,
728 "PresShell must be set on PresContext before calling "
729 "nsBidiPresUtils::CreateContinuation");
731 nsContainerFrame
* parent
= aFrame
->GetParent();
734 "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
736 // While passing &aLine to InsertFrames for a non-block isn't harmful
737 // because it's a no-op, it doesn't really make sense. However, the
738 // MOZ_ASSERT() we need to guarantee that it's safe only works if the
739 // parent is actually the block.
740 const nsLineList::iterator
* parentLine
;
741 if (parent
->IsBlockFrameOrSubclass()) {
742 MOZ_ASSERT(aLine
->Contains(aFrame
));
745 parentLine
= nullptr;
748 // Have to special case floating first letter frames because the continuation
749 // doesn't go in the first letter frame. The continuation goes with the rest
750 // of the text that the first letter frame was made out of.
751 if (parent
->IsLetterFrame() && parent
->IsFloating()) {
752 nsFirstLetterFrame
* letterFrame
= do_QueryFrame(parent
);
753 letterFrame
->CreateContinuationForFloatingParent(aFrame
, aNewFrame
,
758 *aNewFrame
= presShell
->FrameConstructor()->CreateContinuingFrame(
759 aFrame
, parent
, aIsFluid
);
761 // The list name FrameChildListID::NoReflowPrincipal would indicate we don't
763 // XXXbz this needs higher-level framelist love
764 parent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, aFrame
, parentLine
,
765 nsFrameList(*aNewFrame
, *aNewFrame
));
768 // Split inline ancestor frames
769 SplitInlineAncestors(parent
, aLine
, aFrame
);
774 * Overview of the implementation of Resolve():
776 * Walk through the descendants of aBlockFrame and build:
777 * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
778 * * mBuffer: an nsString containing a representation of
779 * the content of the frames.
780 * In the case of text frames, this is the actual text context of the
781 * frames, but some other elements are represented in a symbolic form which
782 * will make the Unicode Bidi Algorithm give the correct results.
783 * Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo>
784 * elements are represented by the corresponding Unicode control characters.
785 * <br> elements are represented by U+2028 LINE SEPARATOR
786 * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
789 * Then pass mBuffer to the Bidi engine for resolving of embedding levels
790 * by nsBidi::SetPara() and division into directional runs by
791 * nsBidi::CountRuns().
793 * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
794 * correlate them with the frames indexed in mLogicalFrames, setting the
795 * baseLevel and embeddingLevel properties according to the results returned
796 * by the Bidi engine.
798 * The rendering layer requires each text frame to contain text in only one
799 * direction, so we may need to call EnsureBidiContinuation() to split frames.
800 * We may also need to call RemoveBidiContinuation() to convert frames created
801 * by EnsureBidiContinuation() in previous reflows into fluid continuations.
803 nsresult
nsBidiPresUtils::Resolve(nsBlockFrame
* aBlockFrame
) {
804 BidiParagraphData
bpd(aBlockFrame
);
806 // Handle bidi-override being set on the block itself before calling
808 // No need to call GetBidiControl as well, because isolate and embed
809 // values of unicode-bidi property are redundant on block elements.
810 // unicode-bidi:plaintext on a block element is handled by block frame
811 // via using nsIFrame::GetWritingMode(nsIFrame*).
812 char16_t ch
= GetBidiOverride(aBlockFrame
->Style());
814 bpd
.PushBidiControl(ch
);
815 bpd
.mRequiresBidi
= true;
817 // If there are no unicode-bidi properties and no RTL characters in the
818 // block's content, then it is pure LTR and we can skip the rest of bidi
820 nsIContent
* currContent
= nullptr;
821 for (nsBlockFrame
* block
= aBlockFrame
; block
;
822 block
= static_cast<nsBlockFrame
*>(block
->GetNextContinuation())) {
823 block
->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
824 if (!bpd
.mRequiresBidi
&&
825 ChildListMayRequireBidi(block
->PrincipalChildList().FirstChild(),
827 bpd
.mRequiresBidi
= true;
829 if (!bpd
.mRequiresBidi
) {
830 nsBlockFrame::FrameLines
* overflowLines
= block
->GetOverflowLines();
832 if (ChildListMayRequireBidi(overflowLines
->mFrames
.FirstChild(),
834 bpd
.mRequiresBidi
= true;
839 if (!bpd
.mRequiresBidi
) {
844 for (nsBlockFrame
* block
= aBlockFrame
; block
;
845 block
= static_cast<nsBlockFrame
*>(block
->GetNextContinuation())) {
847 bpd
.mCurrentBlock
= block
;
849 block
->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
850 bpd
.mCurrentTraverseLine
.mLineIterator
=
851 nsBlockInFlowLineIterator(block
, block
->LinesBegin());
852 bpd
.mCurrentTraverseLine
.mPrevFrame
= nullptr;
853 TraverseFrames(block
->PrincipalChildList().FirstChild(), &bpd
);
854 nsBlockFrame::FrameLines
* overflowLines
= block
->GetOverflowLines();
856 bpd
.mCurrentTraverseLine
.mLineIterator
=
857 nsBlockInFlowLineIterator(block
, overflowLines
->mLines
.begin(), true);
858 bpd
.mCurrentTraverseLine
.mPrevFrame
= nullptr;
859 TraverseFrames(overflowLines
->mFrames
.FirstChild(), &bpd
);
864 bpd
.PopBidiControl(ch
);
867 return ResolveParagraph(&bpd
);
870 nsresult
nsBidiPresUtils::ResolveParagraph(BidiParagraphData
* aBpd
) {
871 if (aBpd
->BufferLength() < 1) {
874 aBpd
->mBuffer
.ReplaceChar(kSeparators
, kSpace
);
878 nsresult rv
= aBpd
->SetPara();
879 NS_ENSURE_SUCCESS(rv
, rv
);
881 BidiEmbeddingLevel embeddingLevel
= aBpd
->GetParagraphEmbeddingLevel();
883 rv
= aBpd
->CountRuns(&runCount
);
884 NS_ENSURE_SUCCESS(rv
, rv
);
886 int32_t runLength
= 0; // the length of the current run of text
887 int32_t logicalLimit
= 0; // the end of the current run + 1
889 int32_t fragmentLength
= 0; // the length of the current text frame
890 int32_t frameIndex
= -1; // index to the frames in mLogicalFrames
891 int32_t frameCount
= aBpd
->FrameCount();
892 int32_t contentOffset
= 0; // offset of current frame in its content node
893 bool isTextFrame
= false;
894 nsIFrame
* frame
= nullptr;
895 BidiParagraphData::FrameInfo frameInfo
;
896 nsIContent
* content
= nullptr;
897 int32_t contentTextLength
= 0;
902 "Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, "
904 (void*)aBpd
->mCurrentBlock
, NS_ConvertUTF16toUTF8(aBpd
->mBuffer
).get(),
905 frameCount
, runCount
);
906 # ifdef REALLY_NOISY_BIDI
907 printf(" block frame tree=:\n");
908 aBpd
->mCurrentBlock
->List(stdout
);
913 if (runCount
== 1 && frameCount
== 1 &&
914 aBpd
->GetParagraphDirection() == BidiEngine::ParagraphDirection::LTR
&&
915 aBpd
->GetParagraphEmbeddingLevel() == 0) {
916 // We have a single left-to-right frame in a left-to-right paragraph,
917 // without bidi isolation from the surrounding text.
918 // Make sure that the embedding level and base level frame properties aren't
919 // set (because if they are this frame used to have some other direction,
920 // so we can't do this optimization), and we're done.
921 nsIFrame
* frame
= aBpd
->FrameAt(0);
922 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
923 FrameBidiData bidiData
= frame
->GetBidiData();
924 if (!bidiData
.embeddingLevel
&& !bidiData
.baseLevel
) {
927 printf("early return for single direction frame %p\n", (void*)frame
);
930 frame
->AddStateBits(NS_FRAME_IS_BIDI
);
936 BidiParagraphData::FrameInfo lastRealFrame
;
937 BidiEmbeddingLevel lastEmbeddingLevel
= kBidiLevelNone
;
938 BidiEmbeddingLevel precedingControl
= kBidiLevelNone
;
940 auto storeBidiDataToFrame
= [&]() {
941 FrameBidiData bidiData
;
942 bidiData
.embeddingLevel
= embeddingLevel
;
943 bidiData
.baseLevel
= aBpd
->GetParagraphEmbeddingLevel();
944 // If a control character doesn't have a lower embedding level than
945 // both the preceding and the following frame, it isn't something
946 // needed for getting the correct result. This optimization should
947 // remove almost all of embeds and overrides, and some of isolates.
948 if (precedingControl
>= embeddingLevel
||
949 precedingControl
>= lastEmbeddingLevel
) {
950 bidiData
.precedingControl
= kBidiLevelNone
;
952 bidiData
.precedingControl
= precedingControl
;
954 precedingControl
= kBidiLevelNone
;
955 lastEmbeddingLevel
= embeddingLevel
;
956 frame
->SetProperty(nsIFrame::BidiDataProperty(), bidiData
);
960 if (fragmentLength
<= 0) {
961 // Get the next frame from mLogicalFrames
962 if (++frameIndex
>= frameCount
) {
965 frameInfo
= aBpd
->FrameInfoAt(frameIndex
);
966 frame
= frameInfo
.mFrame
;
967 if (frame
== NS_BIDI_CONTROL_FRAME
|| !frame
->IsTextFrame()) {
969 * Any non-text frame corresponds to a single character in the text
970 * buffer (a bidi control character, LINE SEPARATOR, or OBJECT
976 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(frameInfo
);
977 content
= frame
->GetContent();
982 contentTextLength
= content
->TextLength();
983 auto [start
, end
] = frame
->GetOffsets();
984 NS_ASSERTION(!(contentTextLength
< end
- start
),
985 "Frame offsets don't fit in content");
986 fragmentLength
= std::min(contentTextLength
, end
- start
);
987 contentOffset
= start
;
990 } // if (fragmentLength <= 0)
992 if (runLength
<= 0) {
993 // Get the next run of text from the Bidi engine
994 if (++numRun
>= runCount
) {
995 // We've run out of runs of text; but don't forget to store bidi data
996 // to the frame before breaking out of the loop (bug 1426042).
997 storeBidiDataToFrame();
999 frame
->AdjustOffsetsForBidi(contentOffset
,
1000 contentOffset
+ fragmentLength
);
1004 int32_t lineOffset
= logicalLimit
;
1005 aBpd
->GetLogicalRun(lineOffset
, &logicalLimit
, &embeddingLevel
);
1006 runLength
= logicalLimit
- lineOffset
;
1007 } // if (runLength <= 0)
1009 if (frame
== NS_BIDI_CONTROL_FRAME
) {
1010 // In theory, we only need to do this for isolates. However, it is
1011 // easier to do this for all here because we do not maintain the
1012 // index to get corresponding character from buffer. Since we do
1013 // have proper embedding level for all those characters, including
1014 // them wouldn't affect the final result.
1015 precedingControl
= std::min(precedingControl
, embeddingLevel
);
1017 storeBidiDataToFrame();
1019 if (contentTextLength
== 0) {
1020 // Set the base level and embedding level of the current run even
1021 // on an empty frame. Otherwise frame reordering will not be correct.
1022 frame
->AdjustOffsetsForBidi(0, 0);
1023 // Nothing more to do for an empty frame, except update
1024 // lastRealFrame like we do below.
1025 lastRealFrame
= frameInfo
;
1028 nsLineList::iterator currentLine
= aBpd
->mCurrentResolveLine
.GetLine();
1029 if ((runLength
> 0) && (runLength
< fragmentLength
)) {
1031 * The text in this frame continues beyond the end of this directional
1032 * run. Create a non-fluid continuation frame for the next directional
1035 currentLine
->MarkDirty();
1037 int32_t runEnd
= contentOffset
+ runLength
;
1038 EnsureBidiContinuation(frame
, currentLine
, &nextBidi
, contentOffset
,
1040 nextBidi
->AdjustOffsetsForBidi(runEnd
,
1041 contentOffset
+ fragmentLength
);
1043 frameInfo
.mFrame
= frame
;
1044 contentOffset
= runEnd
;
1046 aBpd
->mCurrentResolveLine
.AdvanceToFrame(frame
);
1047 } // if (runLength < fragmentLength)
1049 if (contentOffset
+ fragmentLength
== contentTextLength
) {
1051 * We have finished all the text in this content node. Convert any
1052 * further non-fluid continuations to fluid continuations and
1053 * advance frameIndex to the last frame in the content node
1055 int32_t newIndex
= aBpd
->GetLastFrameForContent(content
);
1056 if (newIndex
> frameIndex
) {
1057 currentLine
->MarkDirty();
1058 RemoveBidiContinuation(aBpd
, frame
, frameIndex
, newIndex
);
1059 frameIndex
= newIndex
;
1060 frameInfo
= aBpd
->FrameInfoAt(frameIndex
);
1061 frame
= frameInfo
.mFrame
;
1063 } else if (fragmentLength
> 0 && runLength
> fragmentLength
) {
1065 * There is more text that belongs to this directional run in the
1066 * next text frame: make sure it is a fluid continuation of the
1067 * current frame. Do not advance frameIndex, because the next frame
1068 * may contain multi-directional text and need to be split
1070 int32_t newIndex
= frameIndex
;
1072 } while (++newIndex
< frameCount
&&
1073 aBpd
->FrameAt(newIndex
) == NS_BIDI_CONTROL_FRAME
);
1074 if (newIndex
< frameCount
) {
1075 currentLine
->MarkDirty();
1076 RemoveBidiContinuation(aBpd
, frame
, frameIndex
, newIndex
);
1078 } else if (runLength
== fragmentLength
) {
1080 * If the directional run ends at the end of the frame, make sure
1081 * that any continuation is non-fluid, and do the same up the
1084 nsIFrame
* next
= frame
->GetNextInFlow();
1086 currentLine
->MarkDirty();
1087 MakeContinuationsNonFluidUpParentChain(frame
, next
);
1090 frame
->AdjustOffsetsForBidi(contentOffset
,
1091 contentOffset
+ fragmentLength
);
1094 } // not bidi control frame
1095 int32_t temp
= runLength
;
1096 runLength
-= fragmentLength
;
1097 fragmentLength
-= temp
;
1099 // Record last real frame so that we can do splitting properly even
1100 // if a run ends after a virtual bidi control frame.
1101 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1102 lastRealFrame
= frameInfo
;
1104 if (lastRealFrame
.mFrame
&& fragmentLength
<= 0) {
1105 // If the frame is at the end of a run, and this is not the end of our
1106 // paragraph, split all ancestor inlines that need splitting.
1107 // To determine whether we're at the end of the run, we check that we've
1108 // finished processing the current run, and that the current frame
1109 // doesn't have a fluid continuation (it could have a fluid continuation
1110 // of zero length, so testing runLength alone is not sufficient).
1111 if (runLength
<= 0 && !lastRealFrame
.mFrame
->GetNextInFlow()) {
1112 if (numRun
+ 1 < runCount
) {
1113 nsIFrame
* child
= lastRealFrame
.mFrame
;
1114 nsContainerFrame
* parent
= child
->GetParent();
1115 // As long as we're on the last sibling, the parent doesn't have to
1117 // However, if the parent has a fluid continuation, we do have to make
1118 // it non-fluid. This can happen e.g. when we have a first-letter
1119 // frame and the end of the first-letter coincides with the end of a
1121 while (parent
&& IsBidiSplittable(parent
) &&
1122 !child
->GetNextSibling()) {
1123 nsIFrame
* next
= parent
->GetNextInFlow();
1125 parent
->SetNextContinuation(next
);
1128 parent
= child
->GetParent();
1130 if (parent
&& IsBidiSplittable(parent
)) {
1131 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(lastRealFrame
);
1132 SplitInlineAncestors(parent
, aBpd
->mCurrentResolveLine
.GetLine(),
1135 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(lastRealFrame
);
1138 } else if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1139 // We're not at an end of a run. If |frame| is the last child of its
1140 // parent, and its ancestors happen to have bidi continuations, convert
1141 // them into fluid continuations.
1142 JoinInlineAncestors(frame
);
1148 # ifdef REALLY_NOISY_BIDI
1149 printf("---\nAfter Resolve(), frameTree =:\n");
1150 aBpd
->mCurrentBlock
->List(stdout
);
1158 void nsBidiPresUtils::TraverseFrames(nsIFrame
* aCurrentFrame
,
1159 BidiParagraphData
* aBpd
) {
1160 if (!aCurrentFrame
) return;
1163 nsBlockFrame
* initialLineContainer
=
1164 aBpd
->mCurrentTraverseLine
.mLineIterator
.GetContainer();
1167 nsIFrame
* childFrame
= aCurrentFrame
;
1170 * It's important to get the next sibling and next continuation *before*
1171 * handling the frame: If we encounter a forced paragraph break and call
1172 * ResolveParagraph within this loop, doing GetNextSibling and
1173 * GetNextContinuation after that could return a bidi continuation that had
1174 * just been split from the original childFrame and we would process it
1177 nsIFrame
* nextSibling
= childFrame
->GetNextSibling();
1179 // If the real frame for a placeholder is a first letter frame, we need to
1180 // drill down into it and include its contents in Bidi resolution.
1181 // If not, we just use the placeholder.
1182 nsIFrame
* frame
= childFrame
;
1183 if (childFrame
->IsPlaceholderFrame()) {
1184 nsIFrame
* realFrame
=
1185 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame
);
1186 if (realFrame
->IsLetterFrame()) {
1191 auto DifferentBidiValues
= [](ComputedStyle
* aSC1
, nsIFrame
* aFrame2
) {
1192 ComputedStyle
* sc2
= aFrame2
->Style();
1193 return GetBidiControl(aSC1
) != GetBidiControl(sc2
) ||
1194 GetBidiOverride(aSC1
) != GetBidiOverride(sc2
);
1197 ComputedStyle
* sc
= frame
->Style();
1198 nsIFrame
* nextContinuation
= frame
->GetNextContinuation();
1199 nsIFrame
* prevContinuation
= frame
->GetPrevContinuation();
1201 !nextContinuation
|| DifferentBidiValues(sc
, nextContinuation
);
1203 !prevContinuation
|| DifferentBidiValues(sc
, prevContinuation
);
1205 char16_t controlChar
= 0;
1206 char16_t overrideChar
= 0;
1207 LayoutFrameType frameType
= frame
->Type();
1208 if (frame
->IsBidiInlineContainer() || RubyUtils::IsRubyBox(frameType
)) {
1209 if (!frame
->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
1210 nsContainerFrame
* c
= static_cast<nsContainerFrame
*>(frame
);
1211 MOZ_ASSERT(c
== do_QueryFrame(frame
),
1212 "eBidiInlineContainer and ruby frame must be"
1213 " a nsContainerFrame subclass");
1214 c
->DrainSelfOverflowList();
1217 controlChar
= GetBidiControl(sc
);
1218 overrideChar
= GetBidiOverride(sc
);
1220 // Add dummy frame pointers representing bidi control codes before
1221 // the first frames of elements specifying override, isolation, or
1224 if (controlChar
!= 0) {
1225 aBpd
->PushBidiControl(controlChar
);
1227 if (overrideChar
!= 0) {
1228 aBpd
->PushBidiControl(overrideChar
);
1233 if (IsBidiLeaf(frame
)) {
1234 /* Bidi leaf frame: add the frame to the mLogicalFrames array,
1235 * and add its index to the mContentToFrameIndex hashtable. This
1236 * will be used in RemoveBidiContinuation() to identify the last
1237 * frame in the array with a given content.
1239 nsIContent
* content
= frame
->GetContent();
1240 aBpd
->AppendFrame(frame
, aBpd
->mCurrentTraverseLine
, content
);
1242 // Append the content of the frame to the paragraph buffer
1243 if (LayoutFrameType::Text
== frameType
) {
1244 if (content
!= aBpd
->mPrevContent
) {
1245 aBpd
->mPrevContent
= content
;
1246 if (!frame
->StyleText()->NewlineIsSignificant(
1247 static_cast<nsTextFrame
*>(frame
))) {
1248 content
->GetAsText()->AppendTextTo(aBpd
->mBuffer
);
1251 * For preformatted text we have to do bidi resolution on each line
1255 content
->GetAsText()->AppendTextTo(text
);
1260 auto [start
, end
] = frame
->GetOffsets();
1261 int32_t endLine
= text
.FindChar('\n', start
);
1262 if (endLine
== -1) {
1264 * If there is no newline in the text content, just save the
1265 * text from this frame and its continuations, and do bidi
1268 aBpd
->AppendString(Substring(text
, start
));
1269 while (frame
&& nextSibling
) {
1270 aBpd
->AdvanceAndAppendFrame(
1271 &frame
, aBpd
->mCurrentTraverseLine
, &nextSibling
);
1277 * If there is a newline in the frame, break the frame after the
1278 * newline, do bidi resolution and repeat until the last sibling
1283 * If the frame ends before the new line, save the text and move
1284 * into the next continuation
1287 Substring(text
, start
, std::min(end
, endLine
) - start
));
1288 while (end
< endLine
&& nextSibling
) {
1289 aBpd
->AdvanceAndAppendFrame(&frame
, aBpd
->mCurrentTraverseLine
,
1291 NS_ASSERTION(frame
, "Premature end of continuation chain");
1292 std::tie(start
, end
) = frame
->GetOffsets();
1294 Substring(text
, start
, std::min(end
, endLine
) - start
));
1297 if (end
< endLine
) {
1298 aBpd
->mPrevContent
= nullptr;
1302 bool createdContinuation
= false;
1303 if (uint32_t(endLine
) < text
.Length()) {
1305 * Timing is everything here: if the frame already has a bidi
1306 * continuation, we need to make the continuation fluid *before*
1307 * resetting the length of the current frame. Otherwise
1308 * nsTextFrame::SetLength won't set the continuation frame's
1309 * text offsets correctly.
1311 * On the other hand, if the frame doesn't have a continuation,
1312 * we need to create one *after* resetting the length, or
1313 * CreateContinuingFrame will complain that there is no more
1314 * content for the continuation.
1316 next
= frame
->GetNextInFlow();
1318 // If the frame already has a bidi continuation, make it fluid
1319 next
= frame
->GetNextContinuation();
1321 MakeContinuationFluid(frame
, next
);
1322 JoinInlineAncestors(frame
);
1326 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
1327 textFrame
->SetLength(endLine
- start
, nullptr);
1329 // If it weren't for CreateContinuation needing this to
1330 // be current, we could restructure the marking dirty
1331 // below to use mCurrentResolveLine and eliminate
1332 // mCurrentTraverseLine entirely.
1333 aBpd
->mCurrentTraverseLine
.AdvanceToFrame(frame
);
1336 // If the frame has no next in flow, create one.
1338 frame
, aBpd
->mCurrentTraverseLine
.GetLine(), &next
, true);
1339 createdContinuation
= true;
1341 // Mark the line before the newline as dirty.
1342 aBpd
->mCurrentTraverseLine
.GetLine()->MarkDirty();
1344 ResolveParagraphWithinBlock(aBpd
);
1346 if (!nextSibling
&& !createdContinuation
) {
1351 aBpd
->AppendFrame(frame
, aBpd
->mCurrentTraverseLine
);
1352 // Mark the line after the newline as dirty.
1353 aBpd
->mCurrentTraverseLine
.AdvanceToFrame(frame
);
1354 aBpd
->mCurrentTraverseLine
.GetLine()->MarkDirty();
1358 * If we have already overshot the saved next-sibling while
1359 * scanning the frame's continuations, advance it.
1361 if (frame
&& frame
== nextSibling
) {
1362 nextSibling
= frame
->GetNextSibling();
1368 } else if (LayoutFrameType::Br
== frameType
) {
1369 // break frame -- append line separator
1370 aBpd
->AppendUnichar(kLineSeparator
);
1371 ResolveParagraphWithinBlock(aBpd
);
1373 // other frame type -- see the Unicode Bidi Algorithm:
1374 // "...inline objects (such as graphics) are treated as if they are ...
1376 // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
1377 // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1
1378 aBpd
->AppendUnichar(
1379 content
->IsHTMLElement(nsGkAtoms::wbr
) ? kZWSP
: kObjectSubstitute
);
1380 if (!frame
->IsInlineOutside()) {
1381 // if it is not inline, end the paragraph
1382 ResolveParagraphWithinBlock(aBpd
);
1386 // For a non-leaf frame, recurse into TraverseFrames
1387 nsIFrame
* kid
= frame
->PrincipalChildList().FirstChild();
1388 MOZ_ASSERT(!frame
->GetChildList(FrameChildListID::Overflow
).FirstChild(),
1389 "should have drained the overflow list above");
1391 TraverseFrames(kid
, aBpd
);
1395 // If the element is attributed by dir, indicate direction pop (add PDF
1398 // Add a dummy frame pointer representing a bidi control code after the
1399 // last frame of an element specifying embedding or override
1400 if (overrideChar
!= 0) {
1401 aBpd
->PopBidiControl(overrideChar
);
1403 if (controlChar
!= 0) {
1404 aBpd
->PopBidiControl(controlChar
);
1407 childFrame
= nextSibling
;
1408 } while (childFrame
);
1410 MOZ_ASSERT(initialLineContainer
==
1411 aBpd
->mCurrentTraverseLine
.mLineIterator
.GetContainer());
1414 bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame
* aFirstChild
,
1415 nsIContent
** aCurrContent
) {
1416 MOZ_ASSERT(!aFirstChild
|| !aFirstChild
->GetPrevSibling(),
1417 "Expecting to traverse from the start of a child list");
1419 for (nsIFrame
* childFrame
= aFirstChild
; childFrame
;
1420 childFrame
= childFrame
->GetNextSibling()) {
1421 nsIFrame
* frame
= childFrame
;
1423 // If the real frame for a placeholder is a first-letter frame, we need to
1424 // consider its contents for potential Bidi resolution.
1425 if (childFrame
->IsPlaceholderFrame()) {
1426 nsIFrame
* realFrame
=
1427 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame
);
1428 if (realFrame
->IsLetterFrame()) {
1433 // If unicode-bidi properties are present, we should do bidi resolution.
1434 ComputedStyle
* sc
= frame
->Style();
1435 if (GetBidiControl(sc
) || GetBidiOverride(sc
)) {
1439 if (IsBidiLeaf(frame
)) {
1440 if (frame
->IsTextFrame()) {
1441 // If the frame already has a BidiDataProperty, we know we need to
1442 // perform bidi resolution (even if no bidi content is NOW present --
1443 // we might need to remove the property set by a previous reflow, if
1444 // content has changed; see bug 1366623).
1445 if (frame
->HasProperty(nsIFrame::BidiDataProperty())) {
1449 // Check whether the text frame has any RTL characters; if so, bidi
1450 // resolution will be needed.
1451 dom::Text
* content
= frame
->GetContent()->AsText();
1452 if (content
!= *aCurrContent
) {
1453 *aCurrContent
= content
;
1454 const nsTextFragment
* txt
= &content
->TextFragment();
1456 HasRTLChars(Span(txt
->Get2b(), txt
->GetLength()))) {
1461 } else if (ChildListMayRequireBidi(frame
->PrincipalChildList().FirstChild(),
1470 void nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData
* aBpd
) {
1471 aBpd
->ClearBidiControls();
1472 ResolveParagraph(aBpd
);
1477 nscoord
nsBidiPresUtils::ReorderFrames(nsIFrame
* aFirstFrameOnLine
,
1478 int32_t aNumFramesOnLine
,
1479 WritingMode aLineWM
,
1480 const nsSize
& aContainerSize
,
1482 nsSize
containerSize(aContainerSize
);
1484 // If this line consists of a line frame, reorder the line frame's children.
1485 if (aFirstFrameOnLine
->IsLineFrame()) {
1486 // The line frame is positioned at the start-edge, so use its size
1487 // as the container size.
1488 containerSize
= aFirstFrameOnLine
->GetSize();
1490 aFirstFrameOnLine
= aFirstFrameOnLine
->PrincipalChildList().FirstChild();
1491 if (!aFirstFrameOnLine
) {
1494 // All children of the line frame are on the first line. Setting
1495 // aNumFramesOnLine to -1 makes InitLogicalArrayFromLine look at all of
1497 aNumFramesOnLine
= -1;
1498 // As the line frame itself has been adjusted at its inline-start position
1499 // by the caller, we do not want to apply this to its children.
1503 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1504 return RepositionInlineFrames(&bld
, aLineWM
, containerSize
, aStart
);
1507 nsIFrame
* nsBidiPresUtils::GetFirstLeaf(nsIFrame
* aFrame
) {
1508 nsIFrame
* firstLeaf
= aFrame
;
1509 while (!IsBidiLeaf(firstLeaf
)) {
1510 nsIFrame
* firstChild
= firstLeaf
->PrincipalChildList().FirstChild();
1511 nsIFrame
* realFrame
= nsPlaceholderFrame::GetRealFrameFor(firstChild
);
1512 firstLeaf
= (realFrame
->IsLetterFrame()) ? realFrame
: firstChild
;
1517 FrameBidiData
nsBidiPresUtils::GetFrameBidiData(nsIFrame
* aFrame
) {
1518 return GetFirstLeaf(aFrame
)->GetBidiData();
1521 BidiEmbeddingLevel
nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame
* aFrame
) {
1522 return GetFirstLeaf(aFrame
)->GetEmbeddingLevel();
1525 BidiEmbeddingLevel
nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame
* aFrame
) {
1526 const nsIFrame
* firstLeaf
= aFrame
;
1527 while (!IsBidiLeaf(firstLeaf
)) {
1528 firstLeaf
= firstLeaf
->PrincipalChildList().FirstChild();
1530 return firstLeaf
->GetBaseLevel();
1533 void nsBidiPresUtils::IsFirstOrLast(nsIFrame
* aFrame
,
1534 nsContinuationStates
* aContinuationStates
,
1535 bool aSpanDirMatchesLineDir
,
1536 bool& aIsFirst
/* out */,
1537 bool& aIsLast
/* out */) {
1539 * Since we lay out frames in the line's direction, visiting a frame with
1540 * 'mFirstVisualFrame == nullptr', means it's the first appearance of one
1541 * of its continuation chain frames on the line.
1542 * To determine if it's the last visual frame of its continuation chain on
1543 * the line or not, we count the number of frames of the chain on the line,
1544 * and then reduce it when we lay out a frame of the chain. If this value
1545 * becomes 1 it means that it's the last visual frame of its continuation
1546 * chain on this line.
1549 bool firstInLineOrder
, lastInLineOrder
;
1550 nsFrameContinuationState
* frameState
= aContinuationStates
->Get(aFrame
);
1551 nsFrameContinuationState
* firstFrameState
;
1553 if (!frameState
->mFirstVisualFrame
) {
1554 // aFrame is the first visual frame of its continuation chain
1555 nsFrameContinuationState
* contState
;
1558 frameState
->mFrameCount
= 1;
1559 frameState
->mFirstVisualFrame
= aFrame
;
1562 * Traverse continuation chain of aFrame in both backward and forward
1563 * directions while the frames are on this line. Count the frames and
1564 * set their mFirstVisualFrame to aFrame.
1566 // Traverse continuation chain backward
1567 for (frame
= aFrame
->GetPrevContinuation();
1568 frame
&& (contState
= aContinuationStates
->Get(frame
));
1569 frame
= frame
->GetPrevContinuation()) {
1570 frameState
->mFrameCount
++;
1571 contState
->mFirstVisualFrame
= aFrame
;
1573 frameState
->mHasContOnPrevLines
= (frame
!= nullptr);
1575 // Traverse continuation chain forward
1576 for (frame
= aFrame
->GetNextContinuation();
1577 frame
&& (contState
= aContinuationStates
->Get(frame
));
1578 frame
= frame
->GetNextContinuation()) {
1579 frameState
->mFrameCount
++;
1580 contState
->mFirstVisualFrame
= aFrame
;
1582 frameState
->mHasContOnNextLines
= (frame
!= nullptr);
1584 firstInLineOrder
= true;
1585 firstFrameState
= frameState
;
1587 // aFrame is not the first visual frame of its continuation chain
1588 firstInLineOrder
= false;
1589 firstFrameState
= aContinuationStates
->Get(frameState
->mFirstVisualFrame
);
1592 lastInLineOrder
= (firstFrameState
->mFrameCount
== 1);
1594 if (aSpanDirMatchesLineDir
) {
1595 aIsFirst
= firstInLineOrder
;
1596 aIsLast
= lastInLineOrder
;
1598 aIsFirst
= lastInLineOrder
;
1599 aIsLast
= firstInLineOrder
;
1602 if (frameState
->mHasContOnPrevLines
) {
1605 if (firstFrameState
->mHasContOnNextLines
) {
1609 if ((aIsFirst
|| aIsLast
) &&
1610 aFrame
->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT
)) {
1611 // For ib splits, don't treat anything except the last part as
1612 // endmost or anything except the first part as startmost.
1613 // As an optimization, only get the first continuation once.
1614 nsIFrame
* firstContinuation
= aFrame
->FirstContinuation();
1615 if (firstContinuation
->FrameIsNonLastInIBSplit()) {
1616 // We are not endmost
1619 if (firstContinuation
->FrameIsNonFirstInIBSplit()) {
1620 // We are not startmost
1625 // Reduce number of remaining frames of the continuation chain on the line.
1626 firstFrameState
->mFrameCount
--;
1628 nsInlineFrame
* testFrame
= do_QueryFrame(aFrame
);
1631 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET
);
1634 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST
);
1636 aFrame
->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST
);
1640 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST
);
1642 aFrame
->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST
);
1648 void nsBidiPresUtils::RepositionRubyContentFrame(
1649 nsIFrame
* aFrame
, WritingMode aFrameWM
,
1650 const LogicalMargin
& aBorderPadding
) {
1651 const nsFrameList
& childList
= aFrame
->PrincipalChildList();
1652 if (childList
.IsEmpty()) {
1656 // Reorder the children.
1658 ReorderFrames(childList
.FirstChild(), childList
.GetLength(), aFrameWM
,
1659 aFrame
->GetSize(), aBorderPadding
.IStart(aFrameWM
));
1660 isize
+= aBorderPadding
.IEnd(aFrameWM
);
1662 if (aFrame
->StyleText()->mRubyAlign
== StyleRubyAlign::Start
) {
1665 nscoord residualISize
= aFrame
->ISize(aFrameWM
) - isize
;
1666 if (residualISize
<= 0) {
1670 // When ruby-align is not "start", if the content does not fill this
1671 // frame, we need to center the children.
1672 const nsSize dummyContainerSize
;
1673 for (nsIFrame
* child
: childList
) {
1674 LogicalRect rect
= child
->GetLogicalRect(aFrameWM
, dummyContainerSize
);
1675 rect
.IStart(aFrameWM
) += residualISize
/ 2;
1676 child
->SetRect(aFrameWM
, rect
, dummyContainerSize
);
1681 nscoord
nsBidiPresUtils::RepositionRubyFrame(
1682 nsIFrame
* aFrame
, nsContinuationStates
* aContinuationStates
,
1683 const WritingMode aContainerWM
, const LogicalMargin
& aBorderPadding
) {
1684 LayoutFrameType frameType
= aFrame
->Type();
1685 MOZ_ASSERT(RubyUtils::IsRubyBox(frameType
));
1688 WritingMode frameWM
= aFrame
->GetWritingMode();
1689 bool isLTR
= frameWM
.IsBidiLTR();
1690 nsSize frameSize
= aFrame
->GetSize();
1691 if (frameType
== LayoutFrameType::Ruby
) {
1692 icoord
+= aBorderPadding
.IStart(frameWM
);
1693 // Reposition ruby segments in a ruby container
1694 for (RubySegmentEnumerator
e(static_cast<nsRubyFrame
*>(aFrame
)); !e
.AtEnd();
1696 nsRubyBaseContainerFrame
* rbc
= e
.GetBaseContainer();
1697 AutoRubyTextContainerArray
textContainers(rbc
);
1699 nscoord segmentISize
= RepositionFrame(
1700 rbc
, isLTR
, icoord
, aContinuationStates
, frameWM
, false, frameSize
);
1701 for (nsRubyTextContainerFrame
* rtc
: textContainers
) {
1702 nscoord isize
= RepositionFrame(rtc
, isLTR
, icoord
, aContinuationStates
,
1703 frameWM
, false, frameSize
);
1704 segmentISize
= std::max(segmentISize
, isize
);
1706 icoord
+= segmentISize
;
1708 icoord
+= aBorderPadding
.IEnd(frameWM
);
1709 } else if (frameType
== LayoutFrameType::RubyBaseContainer
) {
1710 // Reposition ruby columns in a ruby segment
1711 auto rbc
= static_cast<nsRubyBaseContainerFrame
*>(aFrame
);
1712 AutoRubyTextContainerArray
textContainers(rbc
);
1714 for (RubyColumnEnumerator
e(rbc
, textContainers
); !e
.AtEnd(); e
.Next()) {
1716 e
.GetColumn(column
);
1718 nscoord columnISize
=
1719 RepositionFrame(column
.mBaseFrame
, isLTR
, icoord
, aContinuationStates
,
1720 frameWM
, false, frameSize
);
1721 for (nsRubyTextFrame
* rt
: column
.mTextFrames
) {
1722 nscoord isize
= RepositionFrame(rt
, isLTR
, icoord
, aContinuationStates
,
1723 frameWM
, false, frameSize
);
1724 columnISize
= std::max(columnISize
, isize
);
1726 icoord
+= columnISize
;
1729 if (frameType
== LayoutFrameType::RubyBase
||
1730 frameType
== LayoutFrameType::RubyText
) {
1731 RepositionRubyContentFrame(aFrame
, frameWM
, aBorderPadding
);
1733 // Note that, ruby text container is not present in all conditions
1734 // above. It is intended, because the children of rtc are reordered
1735 // with the children of ruby base container simultaneously. We only
1736 // need to return its isize here, as it should not be changed.
1737 icoord
+= aFrame
->ISize(aContainerWM
);
1743 nscoord
nsBidiPresUtils::RepositionFrame(
1744 nsIFrame
* aFrame
, bool aIsEvenLevel
, nscoord aStartOrEnd
,
1745 nsContinuationStates
* aContinuationStates
, WritingMode aContainerWM
,
1746 bool aContainerReverseDir
, const nsSize
& aContainerSize
) {
1748 aContainerWM
.IsVertical() ? aContainerSize
.height
: aContainerSize
.width
;
1749 NS_ASSERTION(lineSize
!= NS_UNCONSTRAINEDSIZE
,
1750 "Unconstrained inline line size in bidi frame reordering");
1751 if (!aFrame
) return 0;
1753 bool isFirst
, isLast
;
1754 WritingMode frameWM
= aFrame
->GetWritingMode();
1755 IsFirstOrLast(aFrame
, aContinuationStates
,
1756 aContainerWM
.IsBidiLTR() == frameWM
.IsBidiLTR(),
1757 isFirst
/* out */, isLast
/* out */);
1759 // We only need the margin if the frame is first or last in its own
1760 // writing mode, but we're traversing the frames in the order of the
1761 // container's writing mode. To get the right values, we set start and
1762 // end margins on a logical margin in the frame's writing mode, and
1763 // then convert the margin to the container's writing mode to set the
1766 // This method is called from nsBlockFrame::PlaceLine via the call to
1767 // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
1768 // have been reflowed, which is required for GetUsedMargin/Border/Padding
1769 nscoord frameISize
= aFrame
->ISize();
1770 LogicalMargin frameMargin
= aFrame
->GetLogicalUsedMargin(frameWM
);
1771 LogicalMargin borderPadding
= aFrame
->GetLogicalUsedBorderAndPadding(frameWM
);
1772 // Since the visual order of frame could be different from the continuation
1773 // order, we need to remove any inline border/padding [that is already applied
1774 // based on continuation order] and then add it back based on the visual order
1775 // (i.e. isFirst/isLast) to get the correct isize for the current frame.
1776 // We don't need to do that for 'box-decoration-break:clone' because then all
1777 // continuations have border/padding/margin applied.
1778 if (aFrame
->StyleBorder()->mBoxDecorationBreak
==
1779 StyleBoxDecorationBreak::Slice
) {
1780 // First remove the border/padding that was applied based on logical order.
1781 if (!aFrame
->GetPrevContinuation()) {
1782 frameISize
-= borderPadding
.IStart(frameWM
);
1784 if (!aFrame
->GetNextContinuation()) {
1785 frameISize
-= borderPadding
.IEnd(frameWM
);
1787 // Set margin/border/padding based on visual order.
1789 frameMargin
.IStart(frameWM
) = 0;
1790 borderPadding
.IStart(frameWM
) = 0;
1793 frameMargin
.IEnd(frameWM
) = 0;
1794 borderPadding
.IEnd(frameWM
) = 0;
1796 // Add the border/padding which is now based on visual order.
1797 frameISize
+= borderPadding
.IStartEnd(frameWM
);
1801 if (IsBidiLeaf(aFrame
)) {
1803 frameWM
.IsOrthogonalTo(aContainerWM
) ? aFrame
->BSize() : frameISize
;
1804 } else if (RubyUtils::IsRubyBox(aFrame
->Type())) {
1805 icoord
+= RepositionRubyFrame(aFrame
, aContinuationStates
, aContainerWM
,
1808 bool reverseDir
= aIsEvenLevel
!= frameWM
.IsBidiLTR();
1809 icoord
+= reverseDir
? borderPadding
.IEnd(frameWM
)
1810 : borderPadding
.IStart(frameWM
);
1811 LogicalSize
logicalSize(frameWM
, frameISize
, aFrame
->BSize());
1812 nsSize frameSize
= logicalSize
.GetPhysicalSize(frameWM
);
1813 // Reposition the child frames
1814 for (nsIFrame
* f
: aFrame
->PrincipalChildList()) {
1815 icoord
+= RepositionFrame(f
, aIsEvenLevel
, icoord
, aContinuationStates
,
1816 frameWM
, reverseDir
, frameSize
);
1818 icoord
+= reverseDir
? borderPadding
.IStart(frameWM
)
1819 : borderPadding
.IEnd(frameWM
);
1822 // In the following variables, if aContainerReverseDir is true, i.e.
1823 // the container is positioning its children in reverse of its logical
1824 // direction, the "StartOrEnd" refers to the distance from the frame
1825 // to the inline end edge of the container, elsewise, it refers to the
1826 // distance to the inline start edge.
1827 const LogicalMargin margin
= frameMargin
.ConvertTo(aContainerWM
, frameWM
);
1828 nscoord marginStartOrEnd
= aContainerReverseDir
? margin
.IEnd(aContainerWM
)
1829 : margin
.IStart(aContainerWM
);
1830 nscoord frameStartOrEnd
= aStartOrEnd
+ marginStartOrEnd
;
1832 LogicalRect rect
= aFrame
->GetLogicalRect(aContainerWM
, aContainerSize
);
1833 rect
.ISize(aContainerWM
) = icoord
;
1834 rect
.IStart(aContainerWM
) = aContainerReverseDir
1835 ? lineSize
- frameStartOrEnd
- icoord
1837 aFrame
->SetRect(aContainerWM
, rect
, aContainerSize
);
1839 return icoord
+ margin
.IStartEnd(aContainerWM
);
1842 void nsBidiPresUtils::InitContinuationStates(
1843 nsIFrame
* aFrame
, nsContinuationStates
* aContinuationStates
) {
1844 aContinuationStates
->Insert(aFrame
);
1845 if (!IsBidiLeaf(aFrame
)) {
1846 // Continue for child frames
1847 for (nsIFrame
* frame
: aFrame
->PrincipalChildList()) {
1848 InitContinuationStates(frame
, aContinuationStates
);
1854 nscoord
nsBidiPresUtils::RepositionInlineFrames(BidiLineData
* aBld
,
1855 WritingMode aLineWM
,
1856 const nsSize
& aContainerSize
,
1858 nscoord start
= aStart
;
1860 int32_t count
= aBld
->mVisualFrames
.Length();
1862 nsContinuationStates continuationStates
;
1864 // Initialize continuation states to (nullptr, 0) for
1865 // each frame on the line.
1866 for (index
= 0; index
< count
; index
++) {
1867 InitContinuationStates(aBld
->VisualFrameAt(index
), &continuationStates
);
1870 // Reposition frames in visual order
1871 int32_t step
, limit
;
1872 if (aLineWM
.IsBidiLTR()) {
1881 for (; index
!= limit
; index
+= step
) {
1882 frame
= aBld
->VisualFrameAt(index
);
1883 start
+= RepositionFrame(
1884 frame
, !(aBld
->mLevels
[aBld
->mIndexMap
[index
]].IsRTL()), start
,
1885 &continuationStates
, aLineWM
, false, aContainerSize
);
1890 bool nsBidiPresUtils::CheckLineOrder(nsIFrame
* aFirstFrameOnLine
,
1891 int32_t aNumFramesOnLine
,
1892 nsIFrame
** aFirstVisual
,
1893 nsIFrame
** aLastVisual
) {
1894 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1895 int32_t count
= bld
.FrameCount();
1898 *aFirstVisual
= bld
.VisualFrameAt(0);
1901 *aLastVisual
= bld
.VisualFrameAt(count
- 1);
1904 return bld
.mIsReordered
;
1907 nsIFrame
* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame
* aFrame
,
1908 nsIFrame
* aFirstFrameOnLine
,
1909 int32_t aNumFramesOnLine
) {
1910 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1912 int32_t count
= bld
.mVisualFrames
.Length();
1914 if (aFrame
== nullptr && count
) return bld
.VisualFrameAt(0);
1916 for (int32_t i
= 0; i
< count
- 1; i
++) {
1917 if (bld
.VisualFrameAt(i
) == aFrame
) {
1918 return bld
.VisualFrameAt(i
+ 1);
1925 nsIFrame
* nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame
* aFrame
,
1926 nsIFrame
* aFirstFrameOnLine
,
1927 int32_t aNumFramesOnLine
) {
1928 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1930 int32_t count
= bld
.mVisualFrames
.Length();
1932 if (aFrame
== nullptr && count
) return bld
.VisualFrameAt(count
- 1);
1934 for (int32_t i
= 1; i
< count
; i
++) {
1935 if (bld
.VisualFrameAt(i
) == aFrame
) {
1936 return bld
.VisualFrameAt(i
- 1);
1943 inline void nsBidiPresUtils::EnsureBidiContinuation(
1944 nsIFrame
* aFrame
, const nsLineList::iterator aLine
, nsIFrame
** aNewFrame
,
1945 int32_t aStart
, int32_t aEnd
) {
1946 MOZ_ASSERT(aNewFrame
, "null OUT ptr");
1947 MOZ_ASSERT(aFrame
, "aFrame is null");
1949 aFrame
->AdjustOffsetsForBidi(aStart
, aEnd
);
1950 CreateContinuation(aFrame
, aLine
, aNewFrame
, false);
1953 void nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData
* aBpd
,
1955 int32_t aFirstIndex
,
1956 int32_t aLastIndex
) {
1957 FrameBidiData bidiData
= aFrame
->GetBidiData();
1958 bidiData
.precedingControl
= kBidiLevelNone
;
1959 for (int32_t index
= aFirstIndex
+ 1; index
<= aLastIndex
; index
++) {
1960 nsIFrame
* frame
= aBpd
->FrameAt(index
);
1961 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1962 // Make the frame and its continuation ancestors fluid,
1963 // so they can be reused or deleted by normal reflow code
1964 frame
->SetProperty(nsIFrame::BidiDataProperty(), bidiData
);
1965 frame
->AddStateBits(NS_FRAME_IS_BIDI
);
1966 while (frame
&& IsBidiSplittable(frame
)) {
1967 nsIFrame
* prev
= frame
->GetPrevContinuation();
1969 MakeContinuationFluid(prev
, frame
);
1970 frame
= frame
->GetParent();
1978 // Make sure that the last continuation we made fluid does not itself have a
1979 // fluid continuation (this can happen when re-resolving after dynamic changes
1981 nsIFrame
* lastFrame
= aBpd
->FrameAt(aLastIndex
);
1982 MakeContinuationsNonFluidUpParentChain(lastFrame
, lastFrame
->GetNextInFlow());
1985 nsresult
nsBidiPresUtils::FormatUnicodeText(nsPresContext
* aPresContext
,
1987 int32_t& aTextLength
,
1988 BidiClass aBidiClass
) {
1989 nsresult rv
= NS_OK
;
1991 // adjusted for correct numeral shaping
1992 uint32_t bidiOptions
= aPresContext
->GetBidi();
1993 switch (GET_BIDI_OPTION_NUMERAL(bidiOptions
)) {
1994 case IBMBIDI_NUMERAL_HINDI
:
1995 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
1998 case IBMBIDI_NUMERAL_ARABIC
:
1999 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2002 case IBMBIDI_NUMERAL_PERSIAN
:
2003 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_PERSIAN
);
2006 case IBMBIDI_NUMERAL_REGULAR
:
2008 switch (aBidiClass
) {
2009 case BidiClass::EuropeanNumber
:
2010 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2013 case BidiClass::ArabicNumber
:
2014 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2022 case IBMBIDI_NUMERAL_HINDICONTEXT
:
2023 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions
) ==
2024 IBMBIDI_TEXTDIRECTION_RTL
) &&
2025 (IS_ARABIC_DIGIT(aText
[0]))) ||
2026 (BidiClass::ArabicNumber
== aBidiClass
)) {
2027 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2028 } else if (BidiClass::EuropeanNumber
== aBidiClass
) {
2029 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2033 case IBMBIDI_NUMERAL_PERSIANCONTEXT
:
2034 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions
) ==
2035 IBMBIDI_TEXTDIRECTION_RTL
) &&
2036 (IS_ARABIC_DIGIT(aText
[0]))) ||
2037 (BidiClass::ArabicNumber
== aBidiClass
)) {
2038 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_PERSIAN
);
2039 } else if (BidiClass::EuropeanNumber
== aBidiClass
) {
2040 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2044 case IBMBIDI_NUMERAL_NOMINAL
:
2049 StripBidiControlCharacters(aText
, aTextLength
);
2053 void nsBidiPresUtils::StripBidiControlCharacters(char16_t
* aText
,
2054 int32_t& aTextLength
) {
2055 if ((nullptr == aText
) || (aTextLength
< 1)) {
2059 int32_t stripLen
= 0;
2061 for (int32_t i
= 0; i
< aTextLength
; i
++) {
2062 // XXX: This silently ignores surrogate characters.
2063 // As of Unicode 4.0, all Bidi control characters are within the BMP.
2064 if (IsBidiControl((uint32_t)aText
[i
])) {
2067 aText
[i
- stripLen
] = aText
[i
];
2070 aTextLength
-= stripLen
;
2073 void nsBidiPresUtils::CalculateBidiClass(
2074 const char16_t
* aText
, int32_t& aOffset
, int32_t aBidiClassLimit
,
2075 int32_t& aRunLimit
, int32_t& aRunLength
, int32_t& aRunCount
,
2076 BidiClass
& aBidiClass
, BidiClass
& aPrevBidiClass
) {
2077 bool strongTypeFound
= false;
2079 BidiClass bidiClass
;
2081 aBidiClass
= BidiClass::OtherNeutral
;
2084 for (offset
= aOffset
; offset
< aBidiClassLimit
; offset
+= charLen
) {
2085 // Make sure we give RTL chartype to all characters that would be classified
2086 // as Right-To-Left by a bidi platform.
2087 // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
2089 uint32_t ch
= aText
[offset
];
2090 if (IS_HEBREW_CHAR(ch
)) {
2091 bidiClass
= BidiClass::RightToLeft
;
2092 } else if (IS_ARABIC_ALPHABETIC(ch
)) {
2093 bidiClass
= BidiClass::RightToLeftArabic
;
2095 if (offset
+ 1 < aBidiClassLimit
&&
2096 NS_IS_SURROGATE_PAIR(ch
, aText
[offset
+ 1])) {
2097 ch
= SURROGATE_TO_UCS4(ch
, aText
[offset
+ 1]);
2100 bidiClass
= intl::UnicodeProperties::GetBidiClass(ch
);
2103 if (!BIDICLASS_IS_WEAK(bidiClass
)) {
2104 if (strongTypeFound
&& (bidiClass
!= aPrevBidiClass
) &&
2105 (BIDICLASS_IS_RTL(bidiClass
) || BIDICLASS_IS_RTL(aPrevBidiClass
))) {
2106 // Stop at this point to ensure uni-directionality of the text
2107 // (from platform's point of view).
2108 // Also, don't mix Arabic and Hebrew content (since platform may
2109 // provide BIDI support to one of them only).
2110 aRunLength
= offset
- aOffset
;
2116 if ((BidiClass::RightToLeftArabic
== aPrevBidiClass
||
2117 BidiClass::ArabicNumber
== aPrevBidiClass
) &&
2118 BidiClass::EuropeanNumber
== bidiClass
) {
2119 bidiClass
= BidiClass::ArabicNumber
;
2122 // Set PrevBidiClass to the last strong type in this frame
2123 // (for correct numeric shaping)
2124 aPrevBidiClass
= bidiClass
;
2126 strongTypeFound
= true;
2127 aBidiClass
= bidiClass
;
2133 nsresult
nsBidiPresUtils::ProcessText(const char16_t
* aText
, size_t aLength
,
2134 BidiEmbeddingLevel aBaseLevel
,
2135 nsPresContext
* aPresContext
,
2136 BidiProcessor
& aprocessor
, Mode aMode
,
2137 nsBidiPositionResolve
* aPosResolve
,
2138 int32_t aPosResolveCount
, nscoord
* aWidth
,
2139 BidiEngine
& aBidiEngine
) {
2140 MOZ_ASSERT((aPosResolve
== nullptr) != (aPosResolveCount
> 0),
2141 "Incorrect aPosResolve / aPosResolveCount arguments");
2143 // Caller should have already replaced any separators in the original text
2144 // with <space> characters.
2145 MOZ_ASSERT(nsDependentSubstring(aText
, aLength
).FindCharInSet(kSeparators
) ==
2148 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2149 aPosResolve
[nPosResolve
].visualIndex
= kNotFound
;
2150 aPosResolve
[nPosResolve
].visualLeftTwips
= kNotFound
;
2151 aPosResolve
[nPosResolve
].visualWidth
= kNotFound
;
2154 // For a single-char string, or a string that is purely LTR, use a simplified
2155 // path as it cannot have multiple direction or bidi-class runs.
2157 (aLength
== 2 && NS_IS_SURROGATE_PAIR(aText
[0], aText
[1])) ||
2158 (aBaseLevel
.Direction() == BidiDirection::LTR
&&
2159 !encoding_mem_is_utf16_bidi(aText
, aLength
))) {
2160 ProcessSimpleRun(aText
, aLength
, aBaseLevel
, aPresContext
, aprocessor
,
2161 aMode
, aPosResolve
, aPosResolveCount
, aWidth
);
2165 if (aBidiEngine
.SetParagraph(Span(aText
, aLength
), aBaseLevel
).isErr()) {
2166 return NS_ERROR_FAILURE
;
2169 auto result
= aBidiEngine
.CountRuns();
2170 if (result
.isErr()) {
2171 return NS_ERROR_FAILURE
;
2173 int32_t runCount
= result
.unwrap();
2175 nscoord xOffset
= 0;
2176 nscoord width
, xEndRun
= 0;
2177 nscoord totalWidth
= 0;
2178 int32_t i
, start
, limit
, length
;
2179 uint32_t visualStart
= 0;
2180 BidiClass bidiClass
;
2181 BidiClass prevClass
= BidiClass::LeftToRight
;
2183 for (i
= 0; i
< runCount
; i
++) {
2184 aBidiEngine
.GetVisualRun(i
, &start
, &length
);
2186 BidiEmbeddingLevel level
;
2187 aBidiEngine
.GetLogicalRun(start
, &limit
, &level
);
2189 BidiDirection dir
= level
.Direction();
2190 int32_t subRunLength
= limit
- start
;
2191 int32_t lineOffset
= start
;
2192 int32_t typeLimit
= std::min(limit
, AssertedCast
<int32_t>(aLength
));
2193 int32_t subRunCount
= 1;
2194 int32_t subRunLimit
= typeLimit
;
2197 * If |level| is even, i.e. the direction of the run is left-to-right, we
2198 * render the subruns from left to right and increment the x-coordinate
2199 * |xOffset| by the width of each subrun after rendering.
2201 * If |level| is odd, i.e. the direction of the run is right-to-left, we
2202 * render the subruns from right to left. We begin by incrementing |xOffset|
2203 * by the width of the whole run, and then decrement it by the width of each
2204 * subrun before rendering. After rendering all the subruns, we restore the
2205 * x-coordinate of the end of the run for the start of the next run.
2208 if (dir
== BidiDirection::RTL
) {
2209 aprocessor
.SetText(aText
+ start
, subRunLength
, BidiDirection::RTL
);
2210 width
= aprocessor
.GetWidth();
2215 while (subRunCount
> 0) {
2216 // CalculateBidiClass can increment subRunCount if the run
2217 // contains mixed character types
2218 CalculateBidiClass(aText
, lineOffset
, typeLimit
, subRunLimit
,
2219 subRunLength
, subRunCount
, bidiClass
, prevClass
);
2221 nsAutoString
runVisualText(aText
+ start
, subRunLength
);
2223 FormatUnicodeText(aPresContext
, runVisualText
.BeginWriting(),
2224 subRunLength
, bidiClass
);
2227 aprocessor
.SetText(runVisualText
.get(), subRunLength
, dir
);
2228 width
= aprocessor
.GetWidth();
2229 totalWidth
+= width
;
2230 if (dir
== BidiDirection::RTL
) {
2233 if (aMode
== MODE_DRAW
) {
2234 aprocessor
.DrawText(xOffset
);
2238 * The caller may request to calculate the visual position of one
2239 * or more characters.
2241 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2242 nsBidiPositionResolve
* posResolve
= &aPosResolve
[nPosResolve
];
2244 * Did we already resolve this position's visual metric? If so, skip.
2246 if (posResolve
->visualLeftTwips
!= kNotFound
) continue;
2249 * First find out if the logical position is within this run.
2251 if (start
<= posResolve
->logicalIndex
&&
2252 start
+ subRunLength
> posResolve
->logicalIndex
) {
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.
2258 if (subRunLength
== 1) {
2259 posResolve
->visualIndex
= visualStart
;
2260 posResolve
->visualLeftTwips
= xOffset
;
2261 posResolve
->visualWidth
= width
;
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.
2275 * Here is a description of how the width of the current character
2276 * (posResolve->visualWidth) is calculated:
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
2284 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2285 * ^^ (posResolve->visualWidth)
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
2293 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2294 * ^^ (posResolve->visualWidth)
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
== BidiDirection::RTL
) {
2301 // One day, son, this could all be replaced with
2302 // mPresContext->BidiEngine().GetVisualIndex() ...
2303 posResolve
->visualIndex
=
2305 (subRunLength
- (posResolve
->logicalIndex
+ 1 - start
));
2306 // Skipping to the "left part".
2307 visualLeftPart
= aText
+ posResolve
->logicalIndex
+ 1;
2308 // Skipping to the right side of the current character
2309 visualRightSide
= visualLeftPart
- 1;
2311 posResolve
->visualIndex
=
2312 visualStart
+ (posResolve
->logicalIndex
- start
);
2313 // Skipping to the "left part".
2314 visualLeftPart
= aText
+ start
;
2315 // In LTR mode this is the same as visualLeftPart
2316 visualRightSide
= visualLeftPart
;
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
;
2329 if (dir
== BidiDirection::LTR
) {
2335 subRunLimit
= typeLimit
;
2336 subRunLength
= typeLimit
- lineOffset
;
2338 if (dir
== BidiDirection::RTL
) {
2342 visualStart
+= length
;
2346 *aWidth
= totalWidth
;
2351 // This is called either for a single character (one code unit, or a surrogate
2352 // pair), or for a run that is known to be purely LTR.
2353 void nsBidiPresUtils::ProcessSimpleRun(const char16_t
* aText
, size_t aLength
,
2354 BidiEmbeddingLevel aBaseLevel
,
2355 nsPresContext
* aPresContext
,
2356 BidiProcessor
& aprocessor
, Mode aMode
,
2357 nsBidiPositionResolve
* aPosResolve
,
2358 int32_t aPosResolveCount
,
2360 // Get bidi class from the first (or only) character.
2361 uint32_t ch
= aText
[0];
2362 if (aLength
> 1 && NS_IS_HIGH_SURROGATE(ch
) &&
2363 NS_IS_LOW_SURROGATE(aText
[1])) {
2364 ch
= SURROGATE_TO_UCS4(aText
[0], aText
[1]);
2366 BidiClass bidiClass
= intl::UnicodeProperties::GetBidiClass(ch
);
2368 nsAutoString
runVisualText(aText
, aLength
);
2369 int32_t length
= aLength
;
2371 FormatUnicodeText(aPresContext
, runVisualText
.BeginWriting(), length
,
2375 BidiDirection dir
= bidiClass
== BidiClass::RightToLeft
||
2376 bidiClass
== BidiClass::RightToLeftArabic
2377 ? BidiDirection::RTL
2378 : BidiDirection::LTR
;
2379 aprocessor
.SetText(runVisualText
.get(), length
, dir
);
2381 if (aMode
== MODE_DRAW
) {
2382 aprocessor
.DrawText(0);
2385 if (!aWidth
&& !aPosResolve
) {
2389 nscoord width
= aprocessor
.GetWidth();
2391 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2392 nsBidiPositionResolve
* posResolve
= &aPosResolve
[nPosResolve
];
2393 if (posResolve
->visualLeftTwips
!= kNotFound
) {
2396 if (0 <= posResolve
->logicalIndex
&& length
> posResolve
->logicalIndex
) {
2397 posResolve
->visualIndex
= 0;
2398 posResolve
->visualLeftTwips
= 0;
2399 posResolve
->visualWidth
= width
;
2408 class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final
2409 : public nsBidiPresUtils::BidiProcessor
{
2411 typedef gfx::DrawTarget DrawTarget
;
2413 nsIRenderingContextBidiProcessor(gfxContext
* aCtx
,
2414 DrawTarget
* aTextRunConstructionDrawTarget
,
2415 nsFontMetrics
* aFontMetrics
,
2418 mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget
),
2419 mFontMetrics(aFontMetrics
),
2424 ~nsIRenderingContextBidiProcessor() { mFontMetrics
->SetTextRunRTL(false); }
2426 virtual void SetText(const char16_t
* aText
, int32_t aLength
,
2427 BidiDirection aDirection
) override
{
2428 mFontMetrics
->SetTextRunRTL(aDirection
== BidiDirection::RTL
);
2433 virtual nscoord
GetWidth() override
{
2434 return nsLayoutUtils::AppUnitWidthOfString(mText
, mLength
, *mFontMetrics
,
2435 mTextRunConstructionDrawTarget
);
2438 virtual void DrawText(nscoord aIOffset
) override
{
2440 if (mFontMetrics
->GetVertical()) {
2445 mFontMetrics
->DrawString(mText
, mLength
, pt
.x
, pt
.y
, mCtx
,
2446 mTextRunConstructionDrawTarget
);
2451 DrawTarget
* mTextRunConstructionDrawTarget
;
2452 nsFontMetrics
* mFontMetrics
;
2454 const char16_t
* mText
;
2458 nsresult
nsBidiPresUtils::ProcessTextForRenderingContext(
2459 const char16_t
* aText
, int32_t aLength
, BidiEmbeddingLevel aBaseLevel
,
2460 nsPresContext
* aPresContext
, gfxContext
& aRenderingContext
,
2461 DrawTarget
* aTextRunConstructionDrawTarget
, nsFontMetrics
& aFontMetrics
,
2462 Mode aMode
, nscoord aX
, nscoord aY
, nsBidiPositionResolve
* aPosResolve
,
2463 int32_t aPosResolveCount
, nscoord
* aWidth
) {
2464 nsIRenderingContextBidiProcessor
processor(&aRenderingContext
,
2465 aTextRunConstructionDrawTarget
,
2466 &aFontMetrics
, nsPoint(aX
, aY
));
2467 nsAutoString
text(aText
, aLength
);
2468 text
.ReplaceChar(kSeparators
, ' ');
2469 return ProcessText(text
.BeginReading(), text
.Length(), aBaseLevel
,
2470 aPresContext
, processor
, aMode
, aPosResolve
,
2471 aPosResolveCount
, aWidth
, aPresContext
->BidiEngine());
2475 BidiEmbeddingLevel
nsBidiPresUtils::BidiLevelFromStyle(
2476 ComputedStyle
* aComputedStyle
) {
2477 if (aComputedStyle
->StyleTextReset()->mUnicodeBidi
==
2478 StyleUnicodeBidi::Plaintext
) {
2479 return BidiEmbeddingLevel::DefaultLTR();
2482 if (aComputedStyle
->StyleVisibility()->mDirection
== StyleDirection::Rtl
) {
2483 return BidiEmbeddingLevel::RTL();
2486 return BidiEmbeddingLevel::LTR();