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
->IsFrameOfType(nsIFrame::eBidiInlineContainer
) &&
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
->IsFrameOfType(nsIFrame::eBidiInlineContainer
) ||
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 aFrame
->SetNextInFlow(aNext
);
685 NS_ASSERTION(!aNext
->GetPrevInFlow() || aNext
->GetPrevInFlow() == aFrame
,
686 "prev-in-flow is not prev continuation!");
687 aNext
->SetPrevInFlow(aFrame
);
690 static void MakeContinuationsNonFluidUpParentChain(nsIFrame
* aFrame
,
695 for (frame
= aFrame
, next
= aNext
;
696 frame
&& next
&& next
!= frame
&& next
== frame
->GetNextInFlow() &&
697 IsBidiSplittable(frame
);
698 frame
= frame
->GetParent(), next
= next
->GetParent()) {
699 frame
->SetNextContinuation(next
);
700 next
->SetPrevContinuation(frame
);
704 // If aFrame is the last child of its parent, convert bidi continuations to
705 // fluid continuations for all of its inline ancestors.
706 // If it isn't the last child, make sure that its continuation is fluid.
707 static void JoinInlineAncestors(nsIFrame
* aFrame
) {
708 nsIFrame
* frame
= aFrame
;
709 while (frame
&& IsBidiSplittable(frame
)) {
710 nsIFrame
* next
= frame
->GetNextContinuation();
712 MakeContinuationFluid(frame
, next
);
714 // Join the parent only as long as we're its last child.
715 if (frame
->GetNextSibling()) break;
716 frame
= frame
->GetParent();
720 static void CreateContinuation(nsIFrame
* aFrame
,
721 const nsLineList::iterator aLine
,
722 nsIFrame
** aNewFrame
, bool aIsFluid
) {
723 MOZ_ASSERT(aNewFrame
, "null OUT ptr");
724 MOZ_ASSERT(aFrame
, "null ptr");
726 *aNewFrame
= nullptr;
728 nsPresContext
* presContext
= aFrame
->PresContext();
729 PresShell
* presShell
= presContext
->PresShell();
730 NS_ASSERTION(presShell
,
731 "PresShell must be set on PresContext before calling "
732 "nsBidiPresUtils::CreateContinuation");
734 nsContainerFrame
* parent
= aFrame
->GetParent();
737 "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
739 // While passing &aLine to InsertFrames for a non-block isn't harmful
740 // because it's a no-op, it doesn't really make sense. However, the
741 // MOZ_ASSERT() we need to guarantee that it's safe only works if the
742 // parent is actually the block.
743 const nsLineList::iterator
* parentLine
;
744 if (parent
->IsBlockFrameOrSubclass()) {
745 MOZ_ASSERT(aLine
->Contains(aFrame
));
748 parentLine
= nullptr;
751 // Have to special case floating first letter frames because the continuation
752 // doesn't go in the first letter frame. The continuation goes with the rest
753 // of the text that the first letter frame was made out of.
754 if (parent
->IsLetterFrame() && parent
->IsFloating()) {
755 nsFirstLetterFrame
* letterFrame
= do_QueryFrame(parent
);
756 letterFrame
->CreateContinuationForFloatingParent(aFrame
, aNewFrame
,
761 *aNewFrame
= presShell
->FrameConstructor()->CreateContinuingFrame(
762 aFrame
, parent
, aIsFluid
);
764 // The list name FrameChildListID::NoReflowPrincipal would indicate we don't
766 // XXXbz this needs higher-level framelist love
767 parent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, aFrame
, parentLine
,
768 nsFrameList(*aNewFrame
, *aNewFrame
));
771 // Split inline ancestor frames
772 SplitInlineAncestors(parent
, aLine
, aFrame
);
777 * Overview of the implementation of Resolve():
779 * Walk through the descendants of aBlockFrame and build:
780 * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
781 * * mBuffer: an nsString containing a representation of
782 * the content of the frames.
783 * In the case of text frames, this is the actual text context of the
784 * frames, but some other elements are represented in a symbolic form which
785 * will make the Unicode Bidi Algorithm give the correct results.
786 * Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo>
787 * elements are represented by the corresponding Unicode control characters.
788 * <br> elements are represented by U+2028 LINE SEPARATOR
789 * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
792 * Then pass mBuffer to the Bidi engine for resolving of embedding levels
793 * by nsBidi::SetPara() and division into directional runs by
794 * nsBidi::CountRuns().
796 * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
797 * correlate them with the frames indexed in mLogicalFrames, setting the
798 * baseLevel and embeddingLevel properties according to the results returned
799 * by the Bidi engine.
801 * The rendering layer requires each text frame to contain text in only one
802 * direction, so we may need to call EnsureBidiContinuation() to split frames.
803 * We may also need to call RemoveBidiContinuation() to convert frames created
804 * by EnsureBidiContinuation() in previous reflows into fluid continuations.
806 nsresult
nsBidiPresUtils::Resolve(nsBlockFrame
* aBlockFrame
) {
807 BidiParagraphData
bpd(aBlockFrame
);
809 // Handle bidi-override being set on the block itself before calling
811 // No need to call GetBidiControl as well, because isolate and embed
812 // values of unicode-bidi property are redundant on block elements.
813 // unicode-bidi:plaintext on a block element is handled by block frame
814 // via using nsIFrame::GetWritingMode(nsIFrame*).
815 char16_t ch
= GetBidiOverride(aBlockFrame
->Style());
817 bpd
.PushBidiControl(ch
);
818 bpd
.mRequiresBidi
= true;
820 // If there are no unicode-bidi properties and no RTL characters in the
821 // block's content, then it is pure LTR and we can skip the rest of bidi
823 nsIContent
* currContent
= nullptr;
824 for (nsBlockFrame
* block
= aBlockFrame
; block
;
825 block
= static_cast<nsBlockFrame
*>(block
->GetNextContinuation())) {
826 block
->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
827 if (!bpd
.mRequiresBidi
&&
828 ChildListMayRequireBidi(block
->PrincipalChildList().FirstChild(),
830 bpd
.mRequiresBidi
= true;
832 if (!bpd
.mRequiresBidi
) {
833 nsBlockFrame::FrameLines
* overflowLines
= block
->GetOverflowLines();
835 if (ChildListMayRequireBidi(overflowLines
->mFrames
.FirstChild(),
837 bpd
.mRequiresBidi
= true;
842 if (!bpd
.mRequiresBidi
) {
847 for (nsBlockFrame
* block
= aBlockFrame
; block
;
848 block
= static_cast<nsBlockFrame
*>(block
->GetNextContinuation())) {
850 bpd
.mCurrentBlock
= block
;
852 block
->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
853 bpd
.mCurrentTraverseLine
.mLineIterator
=
854 nsBlockInFlowLineIterator(block
, block
->LinesBegin());
855 bpd
.mCurrentTraverseLine
.mPrevFrame
= nullptr;
856 TraverseFrames(block
->PrincipalChildList().FirstChild(), &bpd
);
857 nsBlockFrame::FrameLines
* overflowLines
= block
->GetOverflowLines();
859 bpd
.mCurrentTraverseLine
.mLineIterator
=
860 nsBlockInFlowLineIterator(block
, overflowLines
->mLines
.begin(), true);
861 bpd
.mCurrentTraverseLine
.mPrevFrame
= nullptr;
862 TraverseFrames(overflowLines
->mFrames
.FirstChild(), &bpd
);
867 bpd
.PopBidiControl(ch
);
870 return ResolveParagraph(&bpd
);
873 nsresult
nsBidiPresUtils::ResolveParagraph(BidiParagraphData
* aBpd
) {
874 if (aBpd
->BufferLength() < 1) {
877 aBpd
->mBuffer
.ReplaceChar(kSeparators
, kSpace
);
881 nsresult rv
= aBpd
->SetPara();
882 NS_ENSURE_SUCCESS(rv
, rv
);
884 BidiEmbeddingLevel embeddingLevel
= aBpd
->GetParagraphEmbeddingLevel();
886 rv
= aBpd
->CountRuns(&runCount
);
887 NS_ENSURE_SUCCESS(rv
, rv
);
889 int32_t runLength
= 0; // the length of the current run of text
890 int32_t logicalLimit
= 0; // the end of the current run + 1
892 int32_t fragmentLength
= 0; // the length of the current text frame
893 int32_t frameIndex
= -1; // index to the frames in mLogicalFrames
894 int32_t frameCount
= aBpd
->FrameCount();
895 int32_t contentOffset
= 0; // offset of current frame in its content node
896 bool isTextFrame
= false;
897 nsIFrame
* frame
= nullptr;
898 BidiParagraphData::FrameInfo frameInfo
;
899 nsIContent
* content
= nullptr;
900 int32_t contentTextLength
= 0;
905 "Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, "
907 (void*)aBpd
->mCurrentBlock
, NS_ConvertUTF16toUTF8(aBpd
->mBuffer
).get(),
908 frameCount
, runCount
);
909 # ifdef REALLY_NOISY_BIDI
910 printf(" block frame tree=:\n");
911 aBpd
->mCurrentBlock
->List(stdout
);
916 if (runCount
== 1 && frameCount
== 1 &&
917 aBpd
->GetParagraphDirection() == BidiEngine::ParagraphDirection::LTR
&&
918 aBpd
->GetParagraphEmbeddingLevel() == 0) {
919 // We have a single left-to-right frame in a left-to-right paragraph,
920 // without bidi isolation from the surrounding text.
921 // Make sure that the embedding level and base level frame properties aren't
922 // set (because if they are this frame used to have some other direction,
923 // so we can't do this optimization), and we're done.
924 nsIFrame
* frame
= aBpd
->FrameAt(0);
925 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
926 FrameBidiData bidiData
= frame
->GetBidiData();
927 if (!bidiData
.embeddingLevel
&& !bidiData
.baseLevel
) {
930 printf("early return for single direction frame %p\n", (void*)frame
);
933 frame
->AddStateBits(NS_FRAME_IS_BIDI
);
939 BidiParagraphData::FrameInfo lastRealFrame
;
940 BidiEmbeddingLevel lastEmbeddingLevel
= kBidiLevelNone
;
941 BidiEmbeddingLevel precedingControl
= kBidiLevelNone
;
943 auto storeBidiDataToFrame
= [&]() {
944 FrameBidiData bidiData
;
945 bidiData
.embeddingLevel
= embeddingLevel
;
946 bidiData
.baseLevel
= aBpd
->GetParagraphEmbeddingLevel();
947 // If a control character doesn't have a lower embedding level than
948 // both the preceding and the following frame, it isn't something
949 // needed for getting the correct result. This optimization should
950 // remove almost all of embeds and overrides, and some of isolates.
951 if (precedingControl
>= embeddingLevel
||
952 precedingControl
>= lastEmbeddingLevel
) {
953 bidiData
.precedingControl
= kBidiLevelNone
;
955 bidiData
.precedingControl
= precedingControl
;
957 precedingControl
= kBidiLevelNone
;
958 lastEmbeddingLevel
= embeddingLevel
;
959 frame
->SetProperty(nsIFrame::BidiDataProperty(), bidiData
);
963 if (fragmentLength
<= 0) {
964 // Get the next frame from mLogicalFrames
965 if (++frameIndex
>= frameCount
) {
968 frameInfo
= aBpd
->FrameInfoAt(frameIndex
);
969 frame
= frameInfo
.mFrame
;
970 if (frame
== NS_BIDI_CONTROL_FRAME
|| !frame
->IsTextFrame()) {
972 * Any non-text frame corresponds to a single character in the text
973 * buffer (a bidi control character, LINE SEPARATOR, or OBJECT
979 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(frameInfo
);
980 content
= frame
->GetContent();
985 contentTextLength
= content
->TextLength();
986 auto [start
, end
] = frame
->GetOffsets();
987 NS_ASSERTION(!(contentTextLength
< end
- start
),
988 "Frame offsets don't fit in content");
989 fragmentLength
= std::min(contentTextLength
, end
- start
);
990 contentOffset
= start
;
993 } // if (fragmentLength <= 0)
995 if (runLength
<= 0) {
996 // Get the next run of text from the Bidi engine
997 if (++numRun
>= runCount
) {
998 // We've run out of runs of text; but don't forget to store bidi data
999 // to the frame before breaking out of the loop (bug 1426042).
1000 storeBidiDataToFrame();
1002 frame
->AdjustOffsetsForBidi(contentOffset
,
1003 contentOffset
+ fragmentLength
);
1007 int32_t lineOffset
= logicalLimit
;
1008 aBpd
->GetLogicalRun(lineOffset
, &logicalLimit
, &embeddingLevel
);
1009 runLength
= logicalLimit
- lineOffset
;
1010 } // if (runLength <= 0)
1012 if (frame
== NS_BIDI_CONTROL_FRAME
) {
1013 // In theory, we only need to do this for isolates. However, it is
1014 // easier to do this for all here because we do not maintain the
1015 // index to get corresponding character from buffer. Since we do
1016 // have proper embedding level for all those characters, including
1017 // them wouldn't affect the final result.
1018 precedingControl
= std::min(precedingControl
, embeddingLevel
);
1020 storeBidiDataToFrame();
1022 if (contentTextLength
== 0) {
1023 // Set the base level and embedding level of the current run even
1024 // on an empty frame. Otherwise frame reordering will not be correct.
1025 frame
->AdjustOffsetsForBidi(0, 0);
1026 // Nothing more to do for an empty frame, except update
1027 // lastRealFrame like we do below.
1028 lastRealFrame
= frameInfo
;
1031 nsLineList::iterator currentLine
= aBpd
->mCurrentResolveLine
.GetLine();
1032 if ((runLength
> 0) && (runLength
< fragmentLength
)) {
1034 * The text in this frame continues beyond the end of this directional
1035 * run. Create a non-fluid continuation frame for the next directional
1038 currentLine
->MarkDirty();
1040 int32_t runEnd
= contentOffset
+ runLength
;
1041 EnsureBidiContinuation(frame
, currentLine
, &nextBidi
, contentOffset
,
1043 nextBidi
->AdjustOffsetsForBidi(runEnd
,
1044 contentOffset
+ fragmentLength
);
1046 frameInfo
.mFrame
= frame
;
1047 contentOffset
= runEnd
;
1049 aBpd
->mCurrentResolveLine
.AdvanceToFrame(frame
);
1050 } // if (runLength < fragmentLength)
1052 if (contentOffset
+ fragmentLength
== contentTextLength
) {
1054 * We have finished all the text in this content node. Convert any
1055 * further non-fluid continuations to fluid continuations and
1056 * advance frameIndex to the last frame in the content node
1058 int32_t newIndex
= aBpd
->GetLastFrameForContent(content
);
1059 if (newIndex
> frameIndex
) {
1060 currentLine
->MarkDirty();
1061 RemoveBidiContinuation(aBpd
, frame
, frameIndex
, newIndex
);
1062 frameIndex
= newIndex
;
1063 frameInfo
= aBpd
->FrameInfoAt(frameIndex
);
1064 frame
= frameInfo
.mFrame
;
1066 } else if (fragmentLength
> 0 && runLength
> fragmentLength
) {
1068 * There is more text that belongs to this directional run in the
1069 * next text frame: make sure it is a fluid continuation of the
1070 * current frame. Do not advance frameIndex, because the next frame
1071 * may contain multi-directional text and need to be split
1073 int32_t newIndex
= frameIndex
;
1075 } while (++newIndex
< frameCount
&&
1076 aBpd
->FrameAt(newIndex
) == NS_BIDI_CONTROL_FRAME
);
1077 if (newIndex
< frameCount
) {
1078 currentLine
->MarkDirty();
1079 RemoveBidiContinuation(aBpd
, frame
, frameIndex
, newIndex
);
1081 } else if (runLength
== fragmentLength
) {
1083 * If the directional run ends at the end of the frame, make sure
1084 * that any continuation is non-fluid, and do the same up the
1087 nsIFrame
* next
= frame
->GetNextInFlow();
1089 currentLine
->MarkDirty();
1090 MakeContinuationsNonFluidUpParentChain(frame
, next
);
1093 frame
->AdjustOffsetsForBidi(contentOffset
,
1094 contentOffset
+ fragmentLength
);
1097 } // not bidi control frame
1098 int32_t temp
= runLength
;
1099 runLength
-= fragmentLength
;
1100 fragmentLength
-= temp
;
1102 // Record last real frame so that we can do splitting properly even
1103 // if a run ends after a virtual bidi control frame.
1104 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1105 lastRealFrame
= frameInfo
;
1107 if (lastRealFrame
.mFrame
&& fragmentLength
<= 0) {
1108 // If the frame is at the end of a run, and this is not the end of our
1109 // paragraph, split all ancestor inlines that need splitting.
1110 // To determine whether we're at the end of the run, we check that we've
1111 // finished processing the current run, and that the current frame
1112 // doesn't have a fluid continuation (it could have a fluid continuation
1113 // of zero length, so testing runLength alone is not sufficient).
1114 if (runLength
<= 0 && !lastRealFrame
.mFrame
->GetNextInFlow()) {
1115 if (numRun
+ 1 < runCount
) {
1116 nsIFrame
* child
= lastRealFrame
.mFrame
;
1117 nsContainerFrame
* parent
= child
->GetParent();
1118 // As long as we're on the last sibling, the parent doesn't have to
1120 // However, if the parent has a fluid continuation, we do have to make
1121 // it non-fluid. This can happen e.g. when we have a first-letter
1122 // frame and the end of the first-letter coincides with the end of a
1124 while (parent
&& IsBidiSplittable(parent
) &&
1125 !child
->GetNextSibling()) {
1126 nsIFrame
* next
= parent
->GetNextInFlow();
1128 parent
->SetNextContinuation(next
);
1129 next
->SetPrevContinuation(parent
);
1132 parent
= child
->GetParent();
1134 if (parent
&& IsBidiSplittable(parent
)) {
1135 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(lastRealFrame
);
1136 SplitInlineAncestors(parent
, aBpd
->mCurrentResolveLine
.GetLine(),
1139 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(lastRealFrame
);
1142 } else if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1143 // We're not at an end of a run. If |frame| is the last child of its
1144 // parent, and its ancestors happen to have bidi continuations, convert
1145 // them into fluid continuations.
1146 JoinInlineAncestors(frame
);
1152 # ifdef REALLY_NOISY_BIDI
1153 printf("---\nAfter Resolve(), frameTree =:\n");
1154 aBpd
->mCurrentBlock
->List(stdout
);
1162 void nsBidiPresUtils::TraverseFrames(nsIFrame
* aCurrentFrame
,
1163 BidiParagraphData
* aBpd
) {
1164 if (!aCurrentFrame
) return;
1167 nsBlockFrame
* initialLineContainer
=
1168 aBpd
->mCurrentTraverseLine
.mLineIterator
.GetContainer();
1171 nsIFrame
* childFrame
= aCurrentFrame
;
1174 * It's important to get the next sibling and next continuation *before*
1175 * handling the frame: If we encounter a forced paragraph break and call
1176 * ResolveParagraph within this loop, doing GetNextSibling and
1177 * GetNextContinuation after that could return a bidi continuation that had
1178 * just been split from the original childFrame and we would process it
1181 nsIFrame
* nextSibling
= childFrame
->GetNextSibling();
1183 // If the real frame for a placeholder is a first letter frame, we need to
1184 // drill down into it and include its contents in Bidi resolution.
1185 // If not, we just use the placeholder.
1186 nsIFrame
* frame
= childFrame
;
1187 if (childFrame
->IsPlaceholderFrame()) {
1188 nsIFrame
* realFrame
=
1189 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame
);
1190 if (realFrame
->IsLetterFrame()) {
1195 auto DifferentBidiValues
= [](ComputedStyle
* aSC1
, nsIFrame
* aFrame2
) {
1196 ComputedStyle
* sc2
= aFrame2
->Style();
1197 return GetBidiControl(aSC1
) != GetBidiControl(sc2
) ||
1198 GetBidiOverride(aSC1
) != GetBidiOverride(sc2
);
1201 ComputedStyle
* sc
= frame
->Style();
1202 nsIFrame
* nextContinuation
= frame
->GetNextContinuation();
1203 nsIFrame
* prevContinuation
= frame
->GetPrevContinuation();
1205 !nextContinuation
|| DifferentBidiValues(sc
, nextContinuation
);
1207 !prevContinuation
|| DifferentBidiValues(sc
, prevContinuation
);
1209 char16_t controlChar
= 0;
1210 char16_t overrideChar
= 0;
1211 LayoutFrameType frameType
= frame
->Type();
1212 if (frame
->IsFrameOfType(nsIFrame::eBidiInlineContainer
) ||
1213 RubyUtils::IsRubyBox(frameType
)) {
1214 if (!frame
->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
1215 nsContainerFrame
* c
= static_cast<nsContainerFrame
*>(frame
);
1216 MOZ_ASSERT(c
== do_QueryFrame(frame
),
1217 "eBidiInlineContainer and ruby frame must be"
1218 " a nsContainerFrame subclass");
1219 c
->DrainSelfOverflowList();
1222 controlChar
= GetBidiControl(sc
);
1223 overrideChar
= GetBidiOverride(sc
);
1225 // Add dummy frame pointers representing bidi control codes before
1226 // the first frames of elements specifying override, isolation, or
1229 if (controlChar
!= 0) {
1230 aBpd
->PushBidiControl(controlChar
);
1232 if (overrideChar
!= 0) {
1233 aBpd
->PushBidiControl(overrideChar
);
1238 if (IsBidiLeaf(frame
)) {
1239 /* Bidi leaf frame: add the frame to the mLogicalFrames array,
1240 * and add its index to the mContentToFrameIndex hashtable. This
1241 * will be used in RemoveBidiContinuation() to identify the last
1242 * frame in the array with a given content.
1244 nsIContent
* content
= frame
->GetContent();
1245 aBpd
->AppendFrame(frame
, aBpd
->mCurrentTraverseLine
, content
);
1247 // Append the content of the frame to the paragraph buffer
1248 if (LayoutFrameType::Text
== frameType
) {
1249 if (content
!= aBpd
->mPrevContent
) {
1250 aBpd
->mPrevContent
= content
;
1251 if (!frame
->StyleText()->NewlineIsSignificant(
1252 static_cast<nsTextFrame
*>(frame
))) {
1253 content
->GetAsText()->AppendTextTo(aBpd
->mBuffer
);
1256 * For preformatted text we have to do bidi resolution on each line
1260 content
->GetAsText()->AppendTextTo(text
);
1265 auto [start
, end
] = frame
->GetOffsets();
1266 int32_t endLine
= text
.FindChar('\n', start
);
1267 if (endLine
== -1) {
1269 * If there is no newline in the text content, just save the
1270 * text from this frame and its continuations, and do bidi
1273 aBpd
->AppendString(Substring(text
, start
));
1274 while (frame
&& nextSibling
) {
1275 aBpd
->AdvanceAndAppendFrame(
1276 &frame
, aBpd
->mCurrentTraverseLine
, &nextSibling
);
1282 * If there is a newline in the frame, break the frame after the
1283 * newline, do bidi resolution and repeat until the last sibling
1288 * If the frame ends before the new line, save the text and move
1289 * into the next continuation
1292 Substring(text
, start
, std::min(end
, endLine
) - start
));
1293 while (end
< endLine
&& nextSibling
) {
1294 aBpd
->AdvanceAndAppendFrame(&frame
, aBpd
->mCurrentTraverseLine
,
1296 NS_ASSERTION(frame
, "Premature end of continuation chain");
1297 std::tie(start
, end
) = frame
->GetOffsets();
1299 Substring(text
, start
, std::min(end
, endLine
) - start
));
1302 if (end
< endLine
) {
1303 aBpd
->mPrevContent
= nullptr;
1307 bool createdContinuation
= false;
1308 if (uint32_t(endLine
) < text
.Length()) {
1310 * Timing is everything here: if the frame already has a bidi
1311 * continuation, we need to make the continuation fluid *before*
1312 * resetting the length of the current frame. Otherwise
1313 * nsTextFrame::SetLength won't set the continuation frame's
1314 * text offsets correctly.
1316 * On the other hand, if the frame doesn't have a continuation,
1317 * we need to create one *after* resetting the length, or
1318 * CreateContinuingFrame will complain that there is no more
1319 * content for the continuation.
1321 next
= frame
->GetNextInFlow();
1323 // If the frame already has a bidi continuation, make it fluid
1324 next
= frame
->GetNextContinuation();
1326 MakeContinuationFluid(frame
, next
);
1327 JoinInlineAncestors(frame
);
1331 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
1332 textFrame
->SetLength(endLine
- start
, nullptr);
1334 // If it weren't for CreateContinuation needing this to
1335 // be current, we could restructure the marking dirty
1336 // below to use mCurrentResolveLine and eliminate
1337 // mCurrentTraverseLine entirely.
1338 aBpd
->mCurrentTraverseLine
.AdvanceToFrame(frame
);
1341 // If the frame has no next in flow, create one.
1343 frame
, aBpd
->mCurrentTraverseLine
.GetLine(), &next
, true);
1344 createdContinuation
= true;
1346 // Mark the line before the newline as dirty.
1347 aBpd
->mCurrentTraverseLine
.GetLine()->MarkDirty();
1349 ResolveParagraphWithinBlock(aBpd
);
1351 if (!nextSibling
&& !createdContinuation
) {
1356 aBpd
->AppendFrame(frame
, aBpd
->mCurrentTraverseLine
);
1357 // Mark the line after the newline as dirty.
1358 aBpd
->mCurrentTraverseLine
.AdvanceToFrame(frame
);
1359 aBpd
->mCurrentTraverseLine
.GetLine()->MarkDirty();
1363 * If we have already overshot the saved next-sibling while
1364 * scanning the frame's continuations, advance it.
1366 if (frame
&& frame
== nextSibling
) {
1367 nextSibling
= frame
->GetNextSibling();
1373 } else if (LayoutFrameType::Br
== frameType
) {
1374 // break frame -- append line separator
1375 aBpd
->AppendUnichar(kLineSeparator
);
1376 ResolveParagraphWithinBlock(aBpd
);
1378 // other frame type -- see the Unicode Bidi Algorithm:
1379 // "...inline objects (such as graphics) are treated as if they are ...
1381 // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
1382 // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1
1383 aBpd
->AppendUnichar(
1384 content
->IsHTMLElement(nsGkAtoms::wbr
) ? kZWSP
: kObjectSubstitute
);
1385 if (!frame
->IsInlineOutside()) {
1386 // if it is not inline, end the paragraph
1387 ResolveParagraphWithinBlock(aBpd
);
1391 // For a non-leaf frame, recurse into TraverseFrames
1392 nsIFrame
* kid
= frame
->PrincipalChildList().FirstChild();
1393 MOZ_ASSERT(!frame
->GetChildList(FrameChildListID::Overflow
).FirstChild(),
1394 "should have drained the overflow list above");
1396 TraverseFrames(kid
, aBpd
);
1400 // If the element is attributed by dir, indicate direction pop (add PDF
1403 // Add a dummy frame pointer representing a bidi control code after the
1404 // last frame of an element specifying embedding or override
1405 if (overrideChar
!= 0) {
1406 aBpd
->PopBidiControl(overrideChar
);
1408 if (controlChar
!= 0) {
1409 aBpd
->PopBidiControl(controlChar
);
1412 childFrame
= nextSibling
;
1413 } while (childFrame
);
1415 MOZ_ASSERT(initialLineContainer
==
1416 aBpd
->mCurrentTraverseLine
.mLineIterator
.GetContainer());
1419 bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame
* aFirstChild
,
1420 nsIContent
** aCurrContent
) {
1421 MOZ_ASSERT(!aFirstChild
|| !aFirstChild
->GetPrevSibling(),
1422 "Expecting to traverse from the start of a child list");
1424 for (nsIFrame
* childFrame
= aFirstChild
; childFrame
;
1425 childFrame
= childFrame
->GetNextSibling()) {
1426 nsIFrame
* frame
= childFrame
;
1428 // If the real frame for a placeholder is a first-letter frame, we need to
1429 // consider its contents for potential Bidi resolution.
1430 if (childFrame
->IsPlaceholderFrame()) {
1431 nsIFrame
* realFrame
=
1432 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame
);
1433 if (realFrame
->IsLetterFrame()) {
1438 // If unicode-bidi properties are present, we should do bidi resolution.
1439 ComputedStyle
* sc
= frame
->Style();
1440 if (GetBidiControl(sc
) || GetBidiOverride(sc
)) {
1444 if (IsBidiLeaf(frame
)) {
1445 if (frame
->IsTextFrame()) {
1446 // If the frame already has a BidiDataProperty, we know we need to
1447 // perform bidi resolution (even if no bidi content is NOW present --
1448 // we might need to remove the property set by a previous reflow, if
1449 // content has changed; see bug 1366623).
1450 if (frame
->HasProperty(nsIFrame::BidiDataProperty())) {
1454 // Check whether the text frame has any RTL characters; if so, bidi
1455 // resolution will be needed.
1456 dom::Text
* content
= frame
->GetContent()->AsText();
1457 if (content
!= *aCurrContent
) {
1458 *aCurrContent
= content
;
1459 const nsTextFragment
* txt
= &content
->TextFragment();
1461 HasRTLChars(Span(txt
->Get2b(), txt
->GetLength()))) {
1466 } else if (ChildListMayRequireBidi(frame
->PrincipalChildList().FirstChild(),
1475 void nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData
* aBpd
) {
1476 aBpd
->ClearBidiControls();
1477 ResolveParagraph(aBpd
);
1482 nscoord
nsBidiPresUtils::ReorderFrames(nsIFrame
* aFirstFrameOnLine
,
1483 int32_t aNumFramesOnLine
,
1484 WritingMode aLineWM
,
1485 const nsSize
& aContainerSize
,
1487 nsSize
containerSize(aContainerSize
);
1489 // If this line consists of a line frame, reorder the line frame's children.
1490 if (aFirstFrameOnLine
->IsLineFrame()) {
1491 // The line frame is positioned at the start-edge, so use its size
1492 // as the container size.
1493 containerSize
= aFirstFrameOnLine
->GetSize();
1495 aFirstFrameOnLine
= aFirstFrameOnLine
->PrincipalChildList().FirstChild();
1496 if (!aFirstFrameOnLine
) {
1499 // All children of the line frame are on the first line. Setting
1500 // aNumFramesOnLine to -1 makes InitLogicalArrayFromLine look at all of
1502 aNumFramesOnLine
= -1;
1503 // As the line frame itself has been adjusted at its inline-start position
1504 // by the caller, we do not want to apply this to its children.
1508 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1509 return RepositionInlineFrames(&bld
, aLineWM
, containerSize
, aStart
);
1512 nsIFrame
* nsBidiPresUtils::GetFirstLeaf(nsIFrame
* aFrame
) {
1513 nsIFrame
* firstLeaf
= aFrame
;
1514 while (!IsBidiLeaf(firstLeaf
)) {
1515 nsIFrame
* firstChild
= firstLeaf
->PrincipalChildList().FirstChild();
1516 nsIFrame
* realFrame
= nsPlaceholderFrame::GetRealFrameFor(firstChild
);
1517 firstLeaf
= (realFrame
->IsLetterFrame()) ? realFrame
: firstChild
;
1522 FrameBidiData
nsBidiPresUtils::GetFrameBidiData(nsIFrame
* aFrame
) {
1523 return GetFirstLeaf(aFrame
)->GetBidiData();
1526 BidiEmbeddingLevel
nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame
* aFrame
) {
1527 return GetFirstLeaf(aFrame
)->GetEmbeddingLevel();
1530 BidiEmbeddingLevel
nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame
* aFrame
) {
1531 const nsIFrame
* firstLeaf
= aFrame
;
1532 while (!IsBidiLeaf(firstLeaf
)) {
1533 firstLeaf
= firstLeaf
->PrincipalChildList().FirstChild();
1535 return firstLeaf
->GetBaseLevel();
1538 void nsBidiPresUtils::IsFirstOrLast(nsIFrame
* aFrame
,
1539 nsContinuationStates
* aContinuationStates
,
1540 bool aSpanDirMatchesLineDir
,
1541 bool& aIsFirst
/* out */,
1542 bool& aIsLast
/* out */) {
1544 * Since we lay out frames in the line's direction, visiting a frame with
1545 * 'mFirstVisualFrame == nullptr', means it's the first appearance of one
1546 * of its continuation chain frames on the line.
1547 * To determine if it's the last visual frame of its continuation chain on
1548 * the line or not, we count the number of frames of the chain on the line,
1549 * and then reduce it when we lay out a frame of the chain. If this value
1550 * becomes 1 it means that it's the last visual frame of its continuation
1551 * chain on this line.
1554 bool firstInLineOrder
, lastInLineOrder
;
1555 nsFrameContinuationState
* frameState
= aContinuationStates
->Get(aFrame
);
1556 nsFrameContinuationState
* firstFrameState
;
1558 if (!frameState
->mFirstVisualFrame
) {
1559 // aFrame is the first visual frame of its continuation chain
1560 nsFrameContinuationState
* contState
;
1563 frameState
->mFrameCount
= 1;
1564 frameState
->mFirstVisualFrame
= aFrame
;
1567 * Traverse continuation chain of aFrame in both backward and forward
1568 * directions while the frames are on this line. Count the frames and
1569 * set their mFirstVisualFrame to aFrame.
1571 // Traverse continuation chain backward
1572 for (frame
= aFrame
->GetPrevContinuation();
1573 frame
&& (contState
= aContinuationStates
->Get(frame
));
1574 frame
= frame
->GetPrevContinuation()) {
1575 frameState
->mFrameCount
++;
1576 contState
->mFirstVisualFrame
= aFrame
;
1578 frameState
->mHasContOnPrevLines
= (frame
!= nullptr);
1580 // Traverse continuation chain forward
1581 for (frame
= aFrame
->GetNextContinuation();
1582 frame
&& (contState
= aContinuationStates
->Get(frame
));
1583 frame
= frame
->GetNextContinuation()) {
1584 frameState
->mFrameCount
++;
1585 contState
->mFirstVisualFrame
= aFrame
;
1587 frameState
->mHasContOnNextLines
= (frame
!= nullptr);
1589 firstInLineOrder
= true;
1590 firstFrameState
= frameState
;
1592 // aFrame is not the first visual frame of its continuation chain
1593 firstInLineOrder
= false;
1594 firstFrameState
= aContinuationStates
->Get(frameState
->mFirstVisualFrame
);
1597 lastInLineOrder
= (firstFrameState
->mFrameCount
== 1);
1599 if (aSpanDirMatchesLineDir
) {
1600 aIsFirst
= firstInLineOrder
;
1601 aIsLast
= lastInLineOrder
;
1603 aIsFirst
= lastInLineOrder
;
1604 aIsLast
= firstInLineOrder
;
1607 if (frameState
->mHasContOnPrevLines
) {
1610 if (firstFrameState
->mHasContOnNextLines
) {
1614 if ((aIsFirst
|| aIsLast
) &&
1615 aFrame
->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT
)) {
1616 // For ib splits, don't treat anything except the last part as
1617 // endmost or anything except the first part as startmost.
1618 // As an optimization, only get the first continuation once.
1619 nsIFrame
* firstContinuation
= aFrame
->FirstContinuation();
1620 if (firstContinuation
->FrameIsNonLastInIBSplit()) {
1621 // We are not endmost
1624 if (firstContinuation
->FrameIsNonFirstInIBSplit()) {
1625 // We are not startmost
1630 // Reduce number of remaining frames of the continuation chain on the line.
1631 firstFrameState
->mFrameCount
--;
1633 nsInlineFrame
* testFrame
= do_QueryFrame(aFrame
);
1636 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET
);
1639 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST
);
1641 aFrame
->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST
);
1645 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST
);
1647 aFrame
->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST
);
1653 void nsBidiPresUtils::RepositionRubyContentFrame(
1654 nsIFrame
* aFrame
, WritingMode aFrameWM
,
1655 const LogicalMargin
& aBorderPadding
) {
1656 const nsFrameList
& childList
= aFrame
->PrincipalChildList();
1657 if (childList
.IsEmpty()) {
1661 // Reorder the children.
1663 ReorderFrames(childList
.FirstChild(), childList
.GetLength(), aFrameWM
,
1664 aFrame
->GetSize(), aBorderPadding
.IStart(aFrameWM
));
1665 isize
+= aBorderPadding
.IEnd(aFrameWM
);
1667 if (aFrame
->StyleText()->mRubyAlign
== StyleRubyAlign::Start
) {
1670 nscoord residualISize
= aFrame
->ISize(aFrameWM
) - isize
;
1671 if (residualISize
<= 0) {
1675 // When ruby-align is not "start", if the content does not fill this
1676 // frame, we need to center the children.
1677 const nsSize dummyContainerSize
;
1678 for (nsIFrame
* child
: childList
) {
1679 LogicalRect rect
= child
->GetLogicalRect(aFrameWM
, dummyContainerSize
);
1680 rect
.IStart(aFrameWM
) += residualISize
/ 2;
1681 child
->SetRect(aFrameWM
, rect
, dummyContainerSize
);
1686 nscoord
nsBidiPresUtils::RepositionRubyFrame(
1687 nsIFrame
* aFrame
, nsContinuationStates
* aContinuationStates
,
1688 const WritingMode aContainerWM
, const LogicalMargin
& aBorderPadding
) {
1689 LayoutFrameType frameType
= aFrame
->Type();
1690 MOZ_ASSERT(RubyUtils::IsRubyBox(frameType
));
1693 WritingMode frameWM
= aFrame
->GetWritingMode();
1694 bool isLTR
= frameWM
.IsBidiLTR();
1695 nsSize frameSize
= aFrame
->GetSize();
1696 if (frameType
== LayoutFrameType::Ruby
) {
1697 icoord
+= aBorderPadding
.IStart(frameWM
);
1698 // Reposition ruby segments in a ruby container
1699 for (RubySegmentEnumerator
e(static_cast<nsRubyFrame
*>(aFrame
)); !e
.AtEnd();
1701 nsRubyBaseContainerFrame
* rbc
= e
.GetBaseContainer();
1702 AutoRubyTextContainerArray
textContainers(rbc
);
1704 nscoord segmentISize
= RepositionFrame(
1705 rbc
, isLTR
, icoord
, aContinuationStates
, frameWM
, false, frameSize
);
1706 for (nsRubyTextContainerFrame
* rtc
: textContainers
) {
1707 nscoord isize
= RepositionFrame(rtc
, isLTR
, icoord
, aContinuationStates
,
1708 frameWM
, false, frameSize
);
1709 segmentISize
= std::max(segmentISize
, isize
);
1711 icoord
+= segmentISize
;
1713 icoord
+= aBorderPadding
.IEnd(frameWM
);
1714 } else if (frameType
== LayoutFrameType::RubyBaseContainer
) {
1715 // Reposition ruby columns in a ruby segment
1716 auto rbc
= static_cast<nsRubyBaseContainerFrame
*>(aFrame
);
1717 AutoRubyTextContainerArray
textContainers(rbc
);
1719 for (RubyColumnEnumerator
e(rbc
, textContainers
); !e
.AtEnd(); e
.Next()) {
1721 e
.GetColumn(column
);
1723 nscoord columnISize
=
1724 RepositionFrame(column
.mBaseFrame
, isLTR
, icoord
, aContinuationStates
,
1725 frameWM
, false, frameSize
);
1726 for (nsRubyTextFrame
* rt
: column
.mTextFrames
) {
1727 nscoord isize
= RepositionFrame(rt
, isLTR
, icoord
, aContinuationStates
,
1728 frameWM
, false, frameSize
);
1729 columnISize
= std::max(columnISize
, isize
);
1731 icoord
+= columnISize
;
1734 if (frameType
== LayoutFrameType::RubyBase
||
1735 frameType
== LayoutFrameType::RubyText
) {
1736 RepositionRubyContentFrame(aFrame
, frameWM
, aBorderPadding
);
1738 // Note that, ruby text container is not present in all conditions
1739 // above. It is intended, because the children of rtc are reordered
1740 // with the children of ruby base container simultaneously. We only
1741 // need to return its isize here, as it should not be changed.
1742 icoord
+= aFrame
->ISize(aContainerWM
);
1748 nscoord
nsBidiPresUtils::RepositionFrame(
1749 nsIFrame
* aFrame
, bool aIsEvenLevel
, nscoord aStartOrEnd
,
1750 nsContinuationStates
* aContinuationStates
, WritingMode aContainerWM
,
1751 bool aContainerReverseDir
, const nsSize
& aContainerSize
) {
1753 aContainerWM
.IsVertical() ? aContainerSize
.height
: aContainerSize
.width
;
1754 NS_ASSERTION(lineSize
!= NS_UNCONSTRAINEDSIZE
,
1755 "Unconstrained inline line size in bidi frame reordering");
1756 if (!aFrame
) return 0;
1758 bool isFirst
, isLast
;
1759 WritingMode frameWM
= aFrame
->GetWritingMode();
1760 IsFirstOrLast(aFrame
, aContinuationStates
,
1761 aContainerWM
.IsBidiLTR() == frameWM
.IsBidiLTR(),
1762 isFirst
/* out */, isLast
/* out */);
1764 // We only need the margin if the frame is first or last in its own
1765 // writing mode, but we're traversing the frames in the order of the
1766 // container's writing mode. To get the right values, we set start and
1767 // end margins on a logical margin in the frame's writing mode, and
1768 // then convert the margin to the container's writing mode to set the
1771 // This method is called from nsBlockFrame::PlaceLine via the call to
1772 // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
1773 // have been reflowed, which is required for GetUsedMargin/Border/Padding
1774 nscoord frameISize
= aFrame
->ISize();
1775 LogicalMargin frameMargin
= aFrame
->GetLogicalUsedMargin(frameWM
);
1776 LogicalMargin borderPadding
= aFrame
->GetLogicalUsedBorderAndPadding(frameWM
);
1777 // Since the visual order of frame could be different from the continuation
1778 // order, we need to remove any inline border/padding [that is already applied
1779 // based on continuation order] and then add it back based on the visual order
1780 // (i.e. isFirst/isLast) to get the correct isize for the current frame.
1781 // We don't need to do that for 'box-decoration-break:clone' because then all
1782 // continuations have border/padding/margin applied.
1783 if (aFrame
->StyleBorder()->mBoxDecorationBreak
==
1784 StyleBoxDecorationBreak::Slice
) {
1785 // First remove the border/padding that was applied based on logical order.
1786 if (!aFrame
->GetPrevContinuation()) {
1787 frameISize
-= borderPadding
.IStart(frameWM
);
1789 if (!aFrame
->GetNextContinuation()) {
1790 frameISize
-= borderPadding
.IEnd(frameWM
);
1792 // Set margin/border/padding based on visual order.
1794 frameMargin
.IStart(frameWM
) = 0;
1795 borderPadding
.IStart(frameWM
) = 0;
1798 frameMargin
.IEnd(frameWM
) = 0;
1799 borderPadding
.IEnd(frameWM
) = 0;
1801 // Add the border/padding which is now based on visual order.
1802 frameISize
+= borderPadding
.IStartEnd(frameWM
);
1806 if (IsBidiLeaf(aFrame
)) {
1808 frameWM
.IsOrthogonalTo(aContainerWM
) ? aFrame
->BSize() : frameISize
;
1809 } else if (RubyUtils::IsRubyBox(aFrame
->Type())) {
1810 icoord
+= RepositionRubyFrame(aFrame
, aContinuationStates
, aContainerWM
,
1813 bool reverseDir
= aIsEvenLevel
!= frameWM
.IsBidiLTR();
1814 icoord
+= reverseDir
? borderPadding
.IEnd(frameWM
)
1815 : borderPadding
.IStart(frameWM
);
1816 LogicalSize
logicalSize(frameWM
, frameISize
, aFrame
->BSize());
1817 nsSize frameSize
= logicalSize
.GetPhysicalSize(frameWM
);
1818 // Reposition the child frames
1819 for (nsIFrame
* f
: aFrame
->PrincipalChildList()) {
1820 icoord
+= RepositionFrame(f
, aIsEvenLevel
, icoord
, aContinuationStates
,
1821 frameWM
, reverseDir
, frameSize
);
1823 icoord
+= reverseDir
? borderPadding
.IStart(frameWM
)
1824 : borderPadding
.IEnd(frameWM
);
1827 // In the following variables, if aContainerReverseDir is true, i.e.
1828 // the container is positioning its children in reverse of its logical
1829 // direction, the "StartOrEnd" refers to the distance from the frame
1830 // to the inline end edge of the container, elsewise, it refers to the
1831 // distance to the inline start edge.
1832 const LogicalMargin margin
= frameMargin
.ConvertTo(aContainerWM
, frameWM
);
1833 nscoord marginStartOrEnd
= aContainerReverseDir
? margin
.IEnd(aContainerWM
)
1834 : margin
.IStart(aContainerWM
);
1835 nscoord frameStartOrEnd
= aStartOrEnd
+ marginStartOrEnd
;
1837 LogicalRect rect
= aFrame
->GetLogicalRect(aContainerWM
, aContainerSize
);
1838 rect
.ISize(aContainerWM
) = icoord
;
1839 rect
.IStart(aContainerWM
) = aContainerReverseDir
1840 ? lineSize
- frameStartOrEnd
- icoord
1842 aFrame
->SetRect(aContainerWM
, rect
, aContainerSize
);
1844 return icoord
+ margin
.IStartEnd(aContainerWM
);
1847 void nsBidiPresUtils::InitContinuationStates(
1848 nsIFrame
* aFrame
, nsContinuationStates
* aContinuationStates
) {
1849 aContinuationStates
->Insert(aFrame
);
1850 if (!IsBidiLeaf(aFrame
)) {
1851 // Continue for child frames
1852 for (nsIFrame
* frame
: aFrame
->PrincipalChildList()) {
1853 InitContinuationStates(frame
, aContinuationStates
);
1859 nscoord
nsBidiPresUtils::RepositionInlineFrames(BidiLineData
* aBld
,
1860 WritingMode aLineWM
,
1861 const nsSize
& aContainerSize
,
1863 nscoord start
= aStart
;
1865 int32_t count
= aBld
->mVisualFrames
.Length();
1867 nsContinuationStates continuationStates
;
1869 // Initialize continuation states to (nullptr, 0) for
1870 // each frame on the line.
1871 for (index
= 0; index
< count
; index
++) {
1872 InitContinuationStates(aBld
->VisualFrameAt(index
), &continuationStates
);
1875 // Reposition frames in visual order
1876 int32_t step
, limit
;
1877 if (aLineWM
.IsBidiLTR()) {
1886 for (; index
!= limit
; index
+= step
) {
1887 frame
= aBld
->VisualFrameAt(index
);
1888 start
+= RepositionFrame(
1889 frame
, !(aBld
->mLevels
[aBld
->mIndexMap
[index
]].IsRTL()), start
,
1890 &continuationStates
, aLineWM
, false, aContainerSize
);
1895 bool nsBidiPresUtils::CheckLineOrder(nsIFrame
* aFirstFrameOnLine
,
1896 int32_t aNumFramesOnLine
,
1897 nsIFrame
** aFirstVisual
,
1898 nsIFrame
** aLastVisual
) {
1899 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1900 int32_t count
= bld
.FrameCount();
1903 *aFirstVisual
= bld
.VisualFrameAt(0);
1906 *aLastVisual
= bld
.VisualFrameAt(count
- 1);
1909 return bld
.mIsReordered
;
1912 nsIFrame
* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame
* aFrame
,
1913 nsIFrame
* aFirstFrameOnLine
,
1914 int32_t aNumFramesOnLine
) {
1915 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1917 int32_t count
= bld
.mVisualFrames
.Length();
1919 if (aFrame
== nullptr && count
) return bld
.VisualFrameAt(0);
1921 for (int32_t i
= 0; i
< count
- 1; i
++) {
1922 if (bld
.VisualFrameAt(i
) == aFrame
) {
1923 return bld
.VisualFrameAt(i
+ 1);
1930 nsIFrame
* nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame
* aFrame
,
1931 nsIFrame
* aFirstFrameOnLine
,
1932 int32_t aNumFramesOnLine
) {
1933 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1935 int32_t count
= bld
.mVisualFrames
.Length();
1937 if (aFrame
== nullptr && count
) return bld
.VisualFrameAt(count
- 1);
1939 for (int32_t i
= 1; i
< count
; i
++) {
1940 if (bld
.VisualFrameAt(i
) == aFrame
) {
1941 return bld
.VisualFrameAt(i
- 1);
1948 inline void nsBidiPresUtils::EnsureBidiContinuation(
1949 nsIFrame
* aFrame
, const nsLineList::iterator aLine
, nsIFrame
** aNewFrame
,
1950 int32_t aStart
, int32_t aEnd
) {
1951 MOZ_ASSERT(aNewFrame
, "null OUT ptr");
1952 MOZ_ASSERT(aFrame
, "aFrame is null");
1954 aFrame
->AdjustOffsetsForBidi(aStart
, aEnd
);
1955 CreateContinuation(aFrame
, aLine
, aNewFrame
, false);
1958 void nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData
* aBpd
,
1960 int32_t aFirstIndex
,
1961 int32_t aLastIndex
) {
1962 FrameBidiData bidiData
= aFrame
->GetBidiData();
1963 bidiData
.precedingControl
= kBidiLevelNone
;
1964 for (int32_t index
= aFirstIndex
+ 1; index
<= aLastIndex
; index
++) {
1965 nsIFrame
* frame
= aBpd
->FrameAt(index
);
1966 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1967 // Make the frame and its continuation ancestors fluid,
1968 // so they can be reused or deleted by normal reflow code
1969 frame
->SetProperty(nsIFrame::BidiDataProperty(), bidiData
);
1970 frame
->AddStateBits(NS_FRAME_IS_BIDI
);
1971 while (frame
&& IsBidiSplittable(frame
)) {
1972 nsIFrame
* prev
= frame
->GetPrevContinuation();
1974 MakeContinuationFluid(prev
, frame
);
1975 frame
= frame
->GetParent();
1983 // Make sure that the last continuation we made fluid does not itself have a
1984 // fluid continuation (this can happen when re-resolving after dynamic changes
1986 nsIFrame
* lastFrame
= aBpd
->FrameAt(aLastIndex
);
1987 MakeContinuationsNonFluidUpParentChain(lastFrame
, lastFrame
->GetNextInFlow());
1990 nsresult
nsBidiPresUtils::FormatUnicodeText(nsPresContext
* aPresContext
,
1992 int32_t& aTextLength
,
1993 BidiClass aBidiClass
) {
1994 nsresult rv
= NS_OK
;
1996 // adjusted for correct numeral shaping
1997 uint32_t bidiOptions
= aPresContext
->GetBidi();
1998 switch (GET_BIDI_OPTION_NUMERAL(bidiOptions
)) {
1999 case IBMBIDI_NUMERAL_HINDI
:
2000 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2003 case IBMBIDI_NUMERAL_ARABIC
:
2004 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2007 case IBMBIDI_NUMERAL_PERSIAN
:
2008 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_PERSIAN
);
2011 case IBMBIDI_NUMERAL_REGULAR
:
2013 switch (aBidiClass
) {
2014 case BidiClass::EuropeanNumber
:
2015 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2018 case BidiClass::ArabicNumber
:
2019 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2027 case IBMBIDI_NUMERAL_HINDICONTEXT
:
2028 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions
) ==
2029 IBMBIDI_TEXTDIRECTION_RTL
) &&
2030 (IS_ARABIC_DIGIT(aText
[0]))) ||
2031 (BidiClass::ArabicNumber
== aBidiClass
)) {
2032 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2033 } else if (BidiClass::EuropeanNumber
== aBidiClass
) {
2034 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2038 case IBMBIDI_NUMERAL_PERSIANCONTEXT
:
2039 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions
) ==
2040 IBMBIDI_TEXTDIRECTION_RTL
) &&
2041 (IS_ARABIC_DIGIT(aText
[0]))) ||
2042 (BidiClass::ArabicNumber
== aBidiClass
)) {
2043 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_PERSIAN
);
2044 } else if (BidiClass::EuropeanNumber
== aBidiClass
) {
2045 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2049 case IBMBIDI_NUMERAL_NOMINAL
:
2054 StripBidiControlCharacters(aText
, aTextLength
);
2058 void nsBidiPresUtils::StripBidiControlCharacters(char16_t
* aText
,
2059 int32_t& aTextLength
) {
2060 if ((nullptr == aText
) || (aTextLength
< 1)) {
2064 int32_t stripLen
= 0;
2066 for (int32_t i
= 0; i
< aTextLength
; i
++) {
2067 // XXX: This silently ignores surrogate characters.
2068 // As of Unicode 4.0, all Bidi control characters are within the BMP.
2069 if (IsBidiControl((uint32_t)aText
[i
])) {
2072 aText
[i
- stripLen
] = aText
[i
];
2075 aTextLength
-= stripLen
;
2078 void nsBidiPresUtils::CalculateBidiClass(
2079 const char16_t
* aText
, int32_t& aOffset
, int32_t aBidiClassLimit
,
2080 int32_t& aRunLimit
, int32_t& aRunLength
, int32_t& aRunCount
,
2081 BidiClass
& aBidiClass
, BidiClass
& aPrevBidiClass
) {
2082 bool strongTypeFound
= false;
2084 BidiClass bidiClass
;
2086 aBidiClass
= BidiClass::OtherNeutral
;
2089 for (offset
= aOffset
; offset
< aBidiClassLimit
; offset
+= charLen
) {
2090 // Make sure we give RTL chartype to all characters that would be classified
2091 // as Right-To-Left by a bidi platform.
2092 // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
2094 uint32_t ch
= aText
[offset
];
2095 if (IS_HEBREW_CHAR(ch
)) {
2096 bidiClass
= BidiClass::RightToLeft
;
2097 } else if (IS_ARABIC_ALPHABETIC(ch
)) {
2098 bidiClass
= BidiClass::RightToLeftArabic
;
2100 if (offset
+ 1 < aBidiClassLimit
&&
2101 NS_IS_SURROGATE_PAIR(ch
, aText
[offset
+ 1])) {
2102 ch
= SURROGATE_TO_UCS4(ch
, aText
[offset
+ 1]);
2105 bidiClass
= intl::UnicodeProperties::GetBidiClass(ch
);
2108 if (!BIDICLASS_IS_WEAK(bidiClass
)) {
2109 if (strongTypeFound
&& (bidiClass
!= aPrevBidiClass
) &&
2110 (BIDICLASS_IS_RTL(bidiClass
) || BIDICLASS_IS_RTL(aPrevBidiClass
))) {
2111 // Stop at this point to ensure uni-directionality of the text
2112 // (from platform's point of view).
2113 // Also, don't mix Arabic and Hebrew content (since platform may
2114 // provide BIDI support to one of them only).
2115 aRunLength
= offset
- aOffset
;
2121 if ((BidiClass::RightToLeftArabic
== aPrevBidiClass
||
2122 BidiClass::ArabicNumber
== aPrevBidiClass
) &&
2123 BidiClass::EuropeanNumber
== bidiClass
) {
2124 bidiClass
= BidiClass::ArabicNumber
;
2127 // Set PrevBidiClass to the last strong type in this frame
2128 // (for correct numeric shaping)
2129 aPrevBidiClass
= bidiClass
;
2131 strongTypeFound
= true;
2132 aBidiClass
= bidiClass
;
2138 nsresult
nsBidiPresUtils::ProcessText(const char16_t
* aText
, size_t aLength
,
2139 BidiEmbeddingLevel aBaseLevel
,
2140 nsPresContext
* aPresContext
,
2141 BidiProcessor
& aprocessor
, Mode aMode
,
2142 nsBidiPositionResolve
* aPosResolve
,
2143 int32_t aPosResolveCount
, nscoord
* aWidth
,
2144 BidiEngine
& aBidiEngine
) {
2145 MOZ_ASSERT((aPosResolve
== nullptr) != (aPosResolveCount
> 0),
2146 "Incorrect aPosResolve / aPosResolveCount arguments");
2148 // Caller should have already replaced any separators in the original text
2149 // with <space> characters.
2150 MOZ_ASSERT(nsDependentSubstring(aText
, aLength
).FindCharInSet(kSeparators
) ==
2153 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2154 aPosResolve
[nPosResolve
].visualIndex
= kNotFound
;
2155 aPosResolve
[nPosResolve
].visualLeftTwips
= kNotFound
;
2156 aPosResolve
[nPosResolve
].visualWidth
= kNotFound
;
2159 // For a single-char string, or a string that is purely LTR, use a simplified
2160 // path as it cannot have multiple direction or bidi-class runs.
2162 (aLength
== 2 && NS_IS_SURROGATE_PAIR(aText
[0], aText
[1])) ||
2163 (aBaseLevel
.Direction() == BidiDirection::LTR
&&
2164 !encoding_mem_is_utf16_bidi(aText
, aLength
))) {
2165 ProcessSimpleRun(aText
, aLength
, aBaseLevel
, aPresContext
, aprocessor
,
2166 aMode
, aPosResolve
, aPosResolveCount
, aWidth
);
2170 if (aBidiEngine
.SetParagraph(Span(aText
, aLength
), aBaseLevel
).isErr()) {
2171 return NS_ERROR_FAILURE
;
2174 auto result
= aBidiEngine
.CountRuns();
2175 if (result
.isErr()) {
2176 return NS_ERROR_FAILURE
;
2178 int32_t runCount
= result
.unwrap();
2180 nscoord xOffset
= 0;
2181 nscoord width
, xEndRun
= 0;
2182 nscoord totalWidth
= 0;
2183 int32_t i
, start
, limit
, length
;
2184 uint32_t visualStart
= 0;
2185 BidiClass bidiClass
;
2186 BidiClass prevClass
= BidiClass::LeftToRight
;
2188 for (i
= 0; i
< runCount
; i
++) {
2189 aBidiEngine
.GetVisualRun(i
, &start
, &length
);
2191 BidiEmbeddingLevel level
;
2192 aBidiEngine
.GetLogicalRun(start
, &limit
, &level
);
2194 BidiDirection dir
= level
.Direction();
2195 int32_t subRunLength
= limit
- start
;
2196 int32_t lineOffset
= start
;
2197 int32_t typeLimit
= std::min(limit
, AssertedCast
<int32_t>(aLength
));
2198 int32_t subRunCount
= 1;
2199 int32_t subRunLimit
= typeLimit
;
2202 * If |level| is even, i.e. the direction of the run is left-to-right, we
2203 * render the subruns from left to right and increment the x-coordinate
2204 * |xOffset| by the width of each subrun after rendering.
2206 * If |level| is odd, i.e. the direction of the run is right-to-left, we
2207 * render the subruns from right to left. We begin by incrementing |xOffset|
2208 * by the width of the whole run, and then decrement it by the width of each
2209 * subrun before rendering. After rendering all the subruns, we restore the
2210 * x-coordinate of the end of the run for the start of the next run.
2213 if (dir
== BidiDirection::RTL
) {
2214 aprocessor
.SetText(aText
+ start
, subRunLength
, BidiDirection::RTL
);
2215 width
= aprocessor
.GetWidth();
2220 while (subRunCount
> 0) {
2221 // CalculateBidiClass can increment subRunCount if the run
2222 // contains mixed character types
2223 CalculateBidiClass(aText
, lineOffset
, typeLimit
, subRunLimit
,
2224 subRunLength
, subRunCount
, bidiClass
, prevClass
);
2226 nsAutoString
runVisualText(aText
+ start
, subRunLength
);
2228 FormatUnicodeText(aPresContext
, runVisualText
.BeginWriting(),
2229 subRunLength
, bidiClass
);
2232 aprocessor
.SetText(runVisualText
.get(), subRunLength
, dir
);
2233 width
= aprocessor
.GetWidth();
2234 totalWidth
+= width
;
2235 if (dir
== BidiDirection::RTL
) {
2238 if (aMode
== MODE_DRAW
) {
2239 aprocessor
.DrawText(xOffset
);
2243 * The caller may request to calculate the visual position of one
2244 * or more characters.
2246 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2247 nsBidiPositionResolve
* posResolve
= &aPosResolve
[nPosResolve
];
2249 * Did we already resolve this position's visual metric? If so, skip.
2251 if (posResolve
->visualLeftTwips
!= kNotFound
) continue;
2254 * First find out if the logical position is within this run.
2256 if (start
<= posResolve
->logicalIndex
&&
2257 start
+ subRunLength
> posResolve
->logicalIndex
) {
2259 * If this run is only one character long, we have an easy case:
2260 * the visual position is the x-coord of the start of the run
2261 * less the x-coord of the start of the whole text.
2263 if (subRunLength
== 1) {
2264 posResolve
->visualIndex
= visualStart
;
2265 posResolve
->visualLeftTwips
= xOffset
;
2266 posResolve
->visualWidth
= width
;
2269 * Otherwise, we need to measure the width of the run's part
2270 * which is to the visual left of the index.
2271 * In other words, the run is broken in two, around the logical index,
2272 * and we measure the part which is visually left.
2273 * If the run is right-to-left, this part will span from after the
2274 * index up to the end of the run; if it is left-to-right, this part
2275 * will span from the start of the run up to (and inclduing) the
2276 * character before the index.
2280 * Here is a description of how the width of the current character
2281 * (posResolve->visualWidth) is calculated:
2283 * LTR (current char: "P"):
2284 * S A M P L E (logical index: 3, visual index: 3)
2285 * ^ (visualLeftPart)
2286 * ^ (visualRightSide)
2287 * visualLeftLength == 3
2289 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2290 * ^^ (posResolve->visualWidth)
2292 * RTL (current char: "M"):
2293 * E L P M A S (logical index: 2, visual index: 3)
2294 * ^ (visualLeftPart)
2295 * ^ (visualRightSide)
2296 * visualLeftLength == 3
2298 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2299 * ^^ (posResolve->visualWidth)
2302 // The position in the text where this run's "left part" begins.
2303 const char16_t
* visualLeftPart
;
2304 const char16_t
* visualRightSide
;
2305 if (dir
== BidiDirection::RTL
) {
2306 // One day, son, this could all be replaced with
2307 // mPresContext->BidiEngine().GetVisualIndex() ...
2308 posResolve
->visualIndex
=
2310 (subRunLength
- (posResolve
->logicalIndex
+ 1 - start
));
2311 // Skipping to the "left part".
2312 visualLeftPart
= aText
+ posResolve
->logicalIndex
+ 1;
2313 // Skipping to the right side of the current character
2314 visualRightSide
= visualLeftPart
- 1;
2316 posResolve
->visualIndex
=
2317 visualStart
+ (posResolve
->logicalIndex
- start
);
2318 // Skipping to the "left part".
2319 visualLeftPart
= aText
+ start
;
2320 // In LTR mode this is the same as visualLeftPart
2321 visualRightSide
= visualLeftPart
;
2323 // The delta between the start of the run and the left part's end.
2324 int32_t visualLeftLength
= posResolve
->visualIndex
- visualStart
;
2325 aprocessor
.SetText(visualLeftPart
, visualLeftLength
, dir
);
2326 subWidth
= aprocessor
.GetWidth();
2327 aprocessor
.SetText(visualRightSide
, visualLeftLength
+ 1, dir
);
2328 posResolve
->visualLeftTwips
= xOffset
+ subWidth
;
2329 posResolve
->visualWidth
= aprocessor
.GetWidth() - subWidth
;
2334 if (dir
== BidiDirection::LTR
) {
2340 subRunLimit
= typeLimit
;
2341 subRunLength
= typeLimit
- lineOffset
;
2343 if (dir
== BidiDirection::RTL
) {
2347 visualStart
+= length
;
2351 *aWidth
= totalWidth
;
2356 // This is called either for a single character (one code unit, or a surrogate
2357 // pair), or for a run that is known to be purely LTR.
2358 void nsBidiPresUtils::ProcessSimpleRun(const char16_t
* aText
, size_t aLength
,
2359 BidiEmbeddingLevel aBaseLevel
,
2360 nsPresContext
* aPresContext
,
2361 BidiProcessor
& aprocessor
, Mode aMode
,
2362 nsBidiPositionResolve
* aPosResolve
,
2363 int32_t aPosResolveCount
,
2365 // Get bidi class from the first (or only) character.
2366 uint32_t ch
= aText
[0];
2367 if (aLength
> 1 && NS_IS_HIGH_SURROGATE(ch
) &&
2368 NS_IS_LOW_SURROGATE(aText
[1])) {
2369 ch
= SURROGATE_TO_UCS4(aText
[0], aText
[1]);
2371 BidiClass bidiClass
= intl::UnicodeProperties::GetBidiClass(ch
);
2373 nsAutoString
runVisualText(aText
, aLength
);
2374 int32_t length
= aLength
;
2376 FormatUnicodeText(aPresContext
, runVisualText
.BeginWriting(), length
,
2380 BidiDirection dir
= bidiClass
== BidiClass::RightToLeft
||
2381 bidiClass
== BidiClass::RightToLeftArabic
2382 ? BidiDirection::RTL
2383 : BidiDirection::LTR
;
2384 aprocessor
.SetText(runVisualText
.get(), length
, dir
);
2386 if (aMode
== MODE_DRAW
) {
2387 aprocessor
.DrawText(0);
2390 if (!aWidth
&& !aPosResolve
) {
2394 nscoord width
= aprocessor
.GetWidth();
2396 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2397 nsBidiPositionResolve
* posResolve
= &aPosResolve
[nPosResolve
];
2398 if (posResolve
->visualLeftTwips
!= kNotFound
) {
2401 if (0 <= posResolve
->logicalIndex
&& length
> posResolve
->logicalIndex
) {
2402 posResolve
->visualIndex
= 0;
2403 posResolve
->visualLeftTwips
= 0;
2404 posResolve
->visualWidth
= width
;
2413 class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final
2414 : public nsBidiPresUtils::BidiProcessor
{
2416 typedef gfx::DrawTarget DrawTarget
;
2418 nsIRenderingContextBidiProcessor(gfxContext
* aCtx
,
2419 DrawTarget
* aTextRunConstructionDrawTarget
,
2420 nsFontMetrics
* aFontMetrics
,
2423 mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget
),
2424 mFontMetrics(aFontMetrics
),
2429 ~nsIRenderingContextBidiProcessor() { mFontMetrics
->SetTextRunRTL(false); }
2431 virtual void SetText(const char16_t
* aText
, int32_t aLength
,
2432 BidiDirection aDirection
) override
{
2433 mFontMetrics
->SetTextRunRTL(aDirection
== BidiDirection::RTL
);
2438 virtual nscoord
GetWidth() override
{
2439 return nsLayoutUtils::AppUnitWidthOfString(mText
, mLength
, *mFontMetrics
,
2440 mTextRunConstructionDrawTarget
);
2443 virtual void DrawText(nscoord aIOffset
) override
{
2445 if (mFontMetrics
->GetVertical()) {
2450 mFontMetrics
->DrawString(mText
, mLength
, pt
.x
, pt
.y
, mCtx
,
2451 mTextRunConstructionDrawTarget
);
2456 DrawTarget
* mTextRunConstructionDrawTarget
;
2457 nsFontMetrics
* mFontMetrics
;
2459 const char16_t
* mText
;
2463 nsresult
nsBidiPresUtils::ProcessTextForRenderingContext(
2464 const char16_t
* aText
, int32_t aLength
, BidiEmbeddingLevel aBaseLevel
,
2465 nsPresContext
* aPresContext
, gfxContext
& aRenderingContext
,
2466 DrawTarget
* aTextRunConstructionDrawTarget
, nsFontMetrics
& aFontMetrics
,
2467 Mode aMode
, nscoord aX
, nscoord aY
, nsBidiPositionResolve
* aPosResolve
,
2468 int32_t aPosResolveCount
, nscoord
* aWidth
) {
2469 nsIRenderingContextBidiProcessor
processor(&aRenderingContext
,
2470 aTextRunConstructionDrawTarget
,
2471 &aFontMetrics
, nsPoint(aX
, aY
));
2472 nsAutoString
text(aText
, aLength
);
2473 text
.ReplaceChar(kSeparators
, ' ');
2474 return ProcessText(text
.BeginReading(), text
.Length(), aBaseLevel
,
2475 aPresContext
, processor
, aMode
, aPosResolve
,
2476 aPosResolveCount
, aWidth
, aPresContext
->BidiEngine());
2480 BidiEmbeddingLevel
nsBidiPresUtils::BidiLevelFromStyle(
2481 ComputedStyle
* aComputedStyle
) {
2482 if (aComputedStyle
->StyleTextReset()->mUnicodeBidi
==
2483 StyleUnicodeBidi::Plaintext
) {
2484 return BidiEmbeddingLevel::DefaultLTR();
2487 if (aComputedStyle
->StyleVisibility()->mDirection
== StyleDirection::Rtl
) {
2488 return BidiEmbeddingLevel::RTL();
2491 return BidiEmbeddingLevel::LTR();