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
;
44 using BidiEmbeddingLevel
= mozilla::intl::BidiEmbeddingLevel
;
46 static const char16_t kSpace
= 0x0020;
47 static const char16_t kZWSP
= 0x200B;
48 static const char16_t kLineSeparator
= 0x2028;
49 static const char16_t kObjectSubstitute
= 0xFFFC;
50 static const char16_t kLRE
= 0x202A;
51 static const char16_t kRLE
= 0x202B;
52 static const char16_t kLRO
= 0x202D;
53 static const char16_t kRLO
= 0x202E;
54 static const char16_t kPDF
= 0x202C;
55 static const char16_t kLRI
= 0x2066;
56 static const char16_t kRLI
= 0x2067;
57 static const char16_t kFSI
= 0x2068;
58 static const char16_t kPDI
= 0x2069;
59 // All characters with Bidi type Segment Separator or Block Separator
60 static const char16_t kSeparators
[] = {
61 char16_t('\t'), char16_t('\r'), char16_t('\n'), char16_t(0xb),
62 char16_t(0x1c), char16_t(0x1d), char16_t(0x1e), char16_t(0x1f),
63 char16_t(0x85), char16_t(0x2029), char16_t(0)};
65 #define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1)
67 // This exists just to be a type; the value doesn't matter.
68 enum class BidiControlFrameType
{ Value
};
70 static bool IsIsolateControl(char16_t aChar
) {
71 return aChar
== kLRI
|| aChar
== kRLI
|| aChar
== kFSI
;
74 // Given a ComputedStyle, return any bidi control character necessary to
75 // implement style properties that override directionality (i.e. if it has
76 // unicode-bidi:bidi-override, or text-orientation:upright in vertical
77 // writing mode) when applying the bidi algorithm.
79 // Returns 0 if no override control character is implied by this style.
80 static char16_t
GetBidiOverride(ComputedStyle
* aComputedStyle
) {
81 const nsStyleVisibility
* vis
= aComputedStyle
->StyleVisibility();
82 if ((vis
->mWritingMode
== StyleWritingModeProperty::VerticalRl
||
83 vis
->mWritingMode
== StyleWritingModeProperty::VerticalLr
) &&
84 vis
->mTextOrientation
== StyleTextOrientation::Upright
) {
87 const nsStyleTextReset
* text
= aComputedStyle
->StyleTextReset();
88 if (text
->mUnicodeBidi
== StyleUnicodeBidi::BidiOverride
||
89 text
->mUnicodeBidi
== StyleUnicodeBidi::IsolateOverride
) {
90 return StyleDirection::Rtl
== vis
->mDirection
? kRLO
: kLRO
;
95 // Given a ComputedStyle, return any bidi control character necessary to
96 // implement style properties that affect bidi resolution (i.e. if it
97 // has unicode-bidiembed, isolate, or plaintext) when applying the bidi
100 // Returns 0 if no control character is implied by the style.
102 // Note that GetBidiOverride and GetBidiControl need to be separate
103 // because in the case of unicode-bidi:isolate-override we need both
105 static char16_t
GetBidiControl(ComputedStyle
* aComputedStyle
) {
106 const nsStyleVisibility
* vis
= aComputedStyle
->StyleVisibility();
107 const nsStyleTextReset
* text
= aComputedStyle
->StyleTextReset();
108 switch (text
->mUnicodeBidi
) {
109 case StyleUnicodeBidi::Embed
:
110 return StyleDirection::Rtl
== vis
->mDirection
? kRLE
: kLRE
;
111 case StyleUnicodeBidi::Isolate
:
112 // <bdi> element already has its directionality set from content so
113 // we never need to return kFSI.
114 return StyleDirection::Rtl
== vis
->mDirection
? kRLI
: kLRI
;
115 case StyleUnicodeBidi::IsolateOverride
:
116 case StyleUnicodeBidi::Plaintext
:
118 case StyleUnicodeBidi::Normal
:
119 case StyleUnicodeBidi::BidiOverride
:
127 static inline bool AreContinuationsInOrder(nsIFrame
* aFrame1
,
129 nsIFrame
* f
= aFrame1
;
131 f
= f
->GetNextContinuation();
132 } while (f
&& f
!= aFrame2
);
137 struct MOZ_STACK_CLASS BidiParagraphData
{
139 FrameInfo(nsIFrame
* aFrame
, nsBlockInFlowLineIterator
& aLineIter
)
141 mBlockContainer(aLineIter
.GetContainer()),
142 mInOverflow(aLineIter
.GetInOverflow()) {}
144 explicit FrameInfo(BidiControlFrameType aValue
)
145 : mFrame(NS_BIDI_CONTROL_FRAME
),
146 mBlockContainer(nullptr),
147 mInOverflow(false) {}
150 : mFrame(nullptr), mBlockContainer(nullptr), mInOverflow(false) {}
154 // The block containing mFrame (i.e., which continuation).
155 nsBlockFrame
* mBlockContainer
;
157 // true if mFrame is in mBlockContainer's overflow lines, false if
162 nsAutoString mBuffer
;
163 AutoTArray
<char16_t
, 16> mEmbeddingStack
;
164 AutoTArray
<FrameInfo
, 16> mLogicalFrames
;
165 nsTHashMap
<nsPtrHashKey
<const nsIContent
>, int32_t> mContentToFrameIndex
;
166 // Cached presentation context for the frames we're processing.
167 nsPresContext
* mPresContext
;
170 BidiEmbeddingLevel mParaLevel
;
171 nsIContent
* mPrevContent
;
174 * This class is designed to manage the process of mapping a frame to
175 * the line that it's in, when we know that (a) the frames we ask it
176 * about are always in the block's lines and (b) each successive frame
177 * we ask it about is the same as or after (in depth-first search
178 * order) the previous.
180 * Since we move through the lines at a different pace in Traverse and
181 * ResolveParagraph, we use one of these for each.
183 * The state of the mapping is also different between TraverseFrames
184 * and ResolveParagraph since since resolving can call functions
185 * (EnsureBidiContinuation or SplitInlineAncestors) that can create
186 * new frames and thus break lines.
188 * The TraverseFrames iterator is only used in some edge cases.
190 struct FastLineIterator
{
191 FastLineIterator() : mPrevFrame(nullptr), mNextLineStart(nullptr) {}
193 // These iterators *and* mPrevFrame track the line list that we're
196 // mPrevFrame, if non-null, should be either the frame we're currently
197 // handling (in ResolveParagraph or TraverseFrames, depending on the
198 // iterator) or a frame before it, and is also guaranteed to either be in
199 // mCurrentLine or have been in mCurrentLine until recently.
201 // In case the splitting causes block frames to break lines, however, we
202 // also track the first frame of the next line. If that changes, it means
203 // we've broken lines and we have to invalidate mPrevFrame.
204 nsBlockInFlowLineIterator mLineIterator
;
205 nsIFrame
* mPrevFrame
;
206 nsIFrame
* mNextLineStart
;
208 nsLineList::iterator
GetLine() { return mLineIterator
.GetLine(); }
210 static bool IsFrameInCurrentLine(nsBlockInFlowLineIterator
* aLineIter
,
211 nsIFrame
* aPrevFrame
, nsIFrame
* aFrame
) {
212 MOZ_ASSERT(!aPrevFrame
|| aLineIter
->GetLine()->Contains(aPrevFrame
),
213 "aPrevFrame must be in aLineIter's current line");
214 nsIFrame
* endFrame
= aLineIter
->IsLastLineInList()
216 : aLineIter
->GetLine().next()->mFirstChild
;
217 nsIFrame
* startFrame
=
218 aPrevFrame
? aPrevFrame
: aLineIter
->GetLine()->mFirstChild
;
219 for (nsIFrame
* frame
= startFrame
; frame
&& frame
!= endFrame
;
220 frame
= frame
->GetNextSibling()) {
221 if (frame
== aFrame
) return true;
226 static nsIFrame
* FirstChildOfNextLine(
227 nsBlockInFlowLineIterator
& aIterator
) {
228 const nsLineList::iterator line
= aIterator
.GetLine();
229 const nsLineList::iterator lineEnd
= aIterator
.End();
230 MOZ_ASSERT(line
!= lineEnd
, "iterator should start off valid");
231 const nsLineList::iterator nextLine
= line
.next();
233 return nextLine
!= lineEnd
? nextLine
->mFirstChild
: nullptr;
236 // Advance line iterator to the line containing aFrame, assuming
237 // that aFrame is already in the line list our iterator is iterating
239 void AdvanceToFrame(nsIFrame
* aFrame
) {
240 if (mPrevFrame
&& FirstChildOfNextLine(mLineIterator
) != mNextLineStart
) {
241 // Something has caused a line to split. We need to invalidate
242 // mPrevFrame since it may now be in a *later* line, though it may
243 // still be in this line, so we need to start searching for it from
244 // the start of this line.
245 mPrevFrame
= nullptr;
247 nsIFrame
* child
= aFrame
;
248 nsIFrame
* parent
= nsLayoutUtils::GetParentOrPlaceholderFor(child
);
249 while (parent
&& !parent
->IsBlockFrameOrSubclass()) {
251 parent
= nsLayoutUtils::GetParentOrPlaceholderFor(child
);
253 MOZ_ASSERT(parent
, "aFrame is not a descendent of a block frame");
254 while (!IsFrameInCurrentLine(&mLineIterator
, mPrevFrame
, child
)) {
258 mLineIterator
.Next();
259 MOZ_ASSERT(hasNext
, "Can't find frame in lines!");
260 mPrevFrame
= nullptr;
263 mNextLineStart
= FirstChildOfNextLine(mLineIterator
);
266 // Advance line iterator to the line containing aFrame, which may
267 // require moving forward into overflow lines or into a later
268 // continuation (or both).
269 void AdvanceToLinesAndFrame(const FrameInfo
& aFrameInfo
) {
270 if (mLineIterator
.GetContainer() != aFrameInfo
.mBlockContainer
||
271 mLineIterator
.GetInOverflow() != aFrameInfo
.mInOverflow
) {
273 mLineIterator
.GetContainer() == aFrameInfo
.mBlockContainer
274 ? (!mLineIterator
.GetInOverflow() && aFrameInfo
.mInOverflow
)
275 : (!mLineIterator
.GetContainer() ||
276 AreContinuationsInOrder(mLineIterator
.GetContainer(),
277 aFrameInfo
.mBlockContainer
)),
278 "must move forwards");
279 nsBlockFrame
* block
= aFrameInfo
.mBlockContainer
;
280 nsLineList::iterator lines
=
281 aFrameInfo
.mInOverflow
? block
->GetOverflowLines()->mLines
.begin()
282 : block
->LinesBegin();
284 nsBlockInFlowLineIterator(block
, lines
, aFrameInfo
.mInOverflow
);
285 mPrevFrame
= nullptr;
287 AdvanceToFrame(aFrameInfo
.mFrame
);
291 FastLineIterator mCurrentTraverseLine
, mCurrentResolveLine
;
294 // Only used for NOISY debug output.
295 // Matches the current TraverseFrames state, not the ResolveParagraph
297 nsBlockFrame
* mCurrentBlock
;
300 explicit BidiParagraphData(nsBlockFrame
* aBlockFrame
)
301 : mPresContext(aBlockFrame
->PresContext()),
302 mIsVisual(mPresContext
->IsVisualMode()),
303 mRequiresBidi(false),
304 mParaLevel(nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame
->Style())),
305 mPrevContent(nullptr)
308 mCurrentBlock(aBlockFrame
)
311 if (mParaLevel
> 0) {
312 mRequiresBidi
= true;
317 * Drill up in content to detect whether this is an element that needs to
318 * be rendered with logical order even on visual pages.
320 * We always use logical order on form controls, firstly so that text
321 * entry will be in logical order, but also because visual pages were
322 * written with the assumption that even if the browser had no support
323 * for right-to-left text rendering, it would use native widgets with
324 * bidi support to display form controls.
326 * We also use logical order in XUL elements, since we expect that if a
327 * XUL element appears in a visual page, it will be generated by an XBL
328 * binding and contain localized text which will be in logical order.
330 for (nsIContent
* content
= aBlockFrame
->GetContent(); content
;
331 content
= content
->GetParent()) {
332 if (content
->IsNodeOfType(nsINode::eHTML_FORM_CONTROL
) ||
333 content
->IsXULElement()) {
342 if (mPresContext
->GetBidiEngine()
343 .SetParagraph(mBuffer
, mParaLevel
)
345 return NS_ERROR_FAILURE
;
351 * mParaLevel can be intl::BidiDirection::LTR as well as
352 * intl::BidiDirection::LTR or intl::BidiDirection::RTL.
353 * GetParagraphEmbeddingLevel() returns the actual (resolved) paragraph level
354 * which is always either intl::BidiDirection::LTR or
355 * intl::BidiDirection::RTL
357 BidiEmbeddingLevel
GetParagraphEmbeddingLevel() {
358 BidiEmbeddingLevel paraLevel
= mParaLevel
;
359 if (paraLevel
== BidiEmbeddingLevel::DefaultLTR() ||
360 paraLevel
== BidiEmbeddingLevel::DefaultRTL()) {
361 paraLevel
= mPresContext
->GetBidiEngine().GetParagraphEmbeddingLevel();
366 intl::Bidi::ParagraphDirection
GetParagraphDirection() {
367 return mPresContext
->GetBidiEngine().GetParagraphDirection();
370 nsresult
CountRuns(int32_t* runCount
) {
371 auto result
= mPresContext
->GetBidiEngine().CountRuns();
372 if (result
.isErr()) {
373 return NS_ERROR_FAILURE
;
375 *runCount
= result
.unwrap();
379 void GetLogicalRun(int32_t aLogicalStart
, int32_t* aLogicalLimit
,
380 BidiEmbeddingLevel
* aLevel
) {
381 mPresContext
->GetBidiEngine().GetLogicalRun(aLogicalStart
, aLogicalLimit
,
384 *aLevel
= GetParagraphEmbeddingLevel();
389 mLogicalFrames
.Clear();
390 mContentToFrameIndex
.Clear();
391 mBuffer
.SetLength(0);
392 mPrevContent
= nullptr;
393 for (uint32_t i
= 0; i
< mEmbeddingStack
.Length(); ++i
) {
394 mBuffer
.Append(mEmbeddingStack
[i
]);
395 mLogicalFrames
.AppendElement(FrameInfo(BidiControlFrameType::Value
));
399 void AppendFrame(nsIFrame
* aFrame
, FastLineIterator
& aLineIter
,
400 nsIContent
* aContent
= nullptr) {
402 mContentToFrameIndex
.InsertOrUpdate(aContent
, FrameCount());
405 // We don't actually need to advance aLineIter to aFrame, since all we use
406 // from it is the block and is-overflow state, which are correct already.
407 mLogicalFrames
.AppendElement(FrameInfo(aFrame
, aLineIter
.mLineIterator
));
410 void AdvanceAndAppendFrame(nsIFrame
** aFrame
, FastLineIterator
& aLineIter
,
411 nsIFrame
** aNextSibling
) {
412 nsIFrame
* frame
= *aFrame
;
413 nsIFrame
* nextSibling
= *aNextSibling
;
415 frame
= frame
->GetNextContinuation();
417 AppendFrame(frame
, aLineIter
, nullptr);
420 * If we have already overshot the saved next-sibling while
421 * scanning the frame's continuations, advance it.
423 if (frame
== nextSibling
) {
424 nextSibling
= frame
->GetNextSibling();
429 *aNextSibling
= nextSibling
;
432 int32_t GetLastFrameForContent(nsIContent
* aContent
) {
433 return mContentToFrameIndex
.Get(aContent
);
436 int32_t FrameCount() { return mLogicalFrames
.Length(); }
438 int32_t BufferLength() { return mBuffer
.Length(); }
440 nsIFrame
* FrameAt(int32_t aIndex
) { return mLogicalFrames
[aIndex
].mFrame
; }
442 const FrameInfo
& FrameInfoAt(int32_t aIndex
) {
443 return mLogicalFrames
[aIndex
];
446 void AppendUnichar(char16_t aCh
) { mBuffer
.Append(aCh
); }
448 void AppendString(const nsDependentSubstring
& aString
) {
449 mBuffer
.Append(aString
);
452 void AppendControlChar(char16_t aCh
) {
453 mLogicalFrames
.AppendElement(FrameInfo(BidiControlFrameType::Value
));
457 void PushBidiControl(char16_t aCh
) {
458 AppendControlChar(aCh
);
459 mEmbeddingStack
.AppendElement(aCh
);
462 void AppendPopChar(char16_t aCh
) {
463 AppendControlChar(IsIsolateControl(aCh
) ? kPDI
: kPDF
);
466 void PopBidiControl(char16_t aCh
) {
467 MOZ_ASSERT(mEmbeddingStack
.Length(), "embedding/override underflow");
468 MOZ_ASSERT(aCh
== mEmbeddingStack
.LastElement());
470 mEmbeddingStack
.RemoveLastElement();
473 void ClearBidiControls() {
474 for (char16_t c
: Reversed(mEmbeddingStack
)) {
480 struct MOZ_STACK_CLASS BidiLineData
{
481 AutoTArray
<nsIFrame
*, 16> mLogicalFrames
;
482 AutoTArray
<nsIFrame
*, 16> mVisualFrames
;
483 AutoTArray
<int32_t, 16> mIndexMap
;
484 AutoTArray
<BidiEmbeddingLevel
, 16> mLevels
;
487 BidiLineData(nsIFrame
* aFirstFrameOnLine
, int32_t aNumFramesOnLine
) {
489 * Initialize the logically-ordered array of frames using the top-level
490 * frames of a single line
492 bool isReordered
= false;
493 bool hasRTLFrames
= false;
494 bool hasVirtualControls
= false;
496 auto appendFrame
= [&](nsIFrame
* frame
, BidiEmbeddingLevel level
) {
497 mLogicalFrames
.AppendElement(frame
);
498 mLevels
.AppendElement(level
);
499 mIndexMap
.AppendElement(0);
505 bool firstFrame
= true;
506 for (nsIFrame
* frame
= aFirstFrameOnLine
; frame
&& aNumFramesOnLine
--;
507 frame
= frame
->GetNextSibling()) {
508 FrameBidiData bidiData
= nsBidiPresUtils::GetFrameBidiData(frame
);
509 // Ignore virtual control before the first frame. Doing so should
510 // not affect the visual result, but could avoid running into the
511 // stripping code below for many cases.
512 if (!firstFrame
&& bidiData
.precedingControl
!= kBidiLevelNone
) {
513 appendFrame(NS_BIDI_CONTROL_FRAME
, bidiData
.precedingControl
);
514 hasVirtualControls
= true;
516 appendFrame(frame
, bidiData
.embeddingLevel
);
521 mozilla::intl::Bidi::ReorderVisual(mLevels
.Elements(), FrameCount(),
522 mIndexMap
.Elements());
524 // Strip virtual frames
525 if (hasVirtualControls
) {
526 auto originalCount
= mLogicalFrames
.Length();
527 AutoTArray
<int32_t, 16> realFrameMap
;
528 realFrameMap
.SetCapacity(originalCount
);
530 for (auto i
: IntegerRange(originalCount
)) {
531 if (mLogicalFrames
[i
] == NS_BIDI_CONTROL_FRAME
) {
532 realFrameMap
.AppendElement(-1);
534 mLogicalFrames
[count
] = mLogicalFrames
[i
];
535 mLevels
[count
] = mLevels
[i
];
536 realFrameMap
.AppendElement(count
);
540 // Only keep index map for real frames.
541 for (size_t i
= 0, j
= 0; i
< originalCount
; ++i
) {
542 auto newIndex
= realFrameMap
[mIndexMap
[i
]];
543 if (newIndex
!= -1) {
544 mIndexMap
[j
] = newIndex
;
548 mLogicalFrames
.TruncateLength(count
);
549 mLevels
.TruncateLength(count
);
550 mIndexMap
.TruncateLength(count
);
553 for (int32_t i
= 0; i
< FrameCount(); i
++) {
554 mVisualFrames
.AppendElement(LogicalFrameAt(mIndexMap
[i
]));
555 if (i
!= mIndexMap
[i
]) {
560 // If there's an RTL frame, assume the line is reordered
561 mIsReordered
= isReordered
|| hasRTLFrames
;
564 int32_t FrameCount() const { return mLogicalFrames
.Length(); }
566 nsIFrame
* LogicalFrameAt(int32_t aIndex
) const {
567 return mLogicalFrames
[aIndex
];
570 nsIFrame
* VisualFrameAt(int32_t aIndex
) const {
571 return mVisualFrames
[aIndex
];
577 void MOZ_EXPORT
DumpFrameArray(const nsTArray
<nsIFrame
*>& aFrames
) {
578 for (nsIFrame
* frame
: aFrames
) {
579 if (frame
== NS_BIDI_CONTROL_FRAME
) {
580 fprintf_stderr(stderr
, "(Bidi control frame)\n");
587 void MOZ_EXPORT
DumpBidiLine(BidiLineData
* aData
, bool aVisualOrder
) {
588 DumpFrameArray(aVisualOrder
? aData
->mVisualFrames
: aData
->mLogicalFrames
);
593 /* Some helper methods for Resolve() */
595 // Should this frame be split between text runs?
596 static bool IsBidiSplittable(nsIFrame
* aFrame
) {
598 // Bidi inline containers should be split, unless they're line frames.
599 LayoutFrameType frameType
= aFrame
->Type();
600 return (aFrame
->IsFrameOfType(nsIFrame::eBidiInlineContainer
) &&
601 frameType
!= LayoutFrameType::Line
) ||
602 frameType
== LayoutFrameType::Text
;
605 // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
606 static bool IsBidiLeaf(const nsIFrame
* aFrame
) {
607 nsIFrame
* kid
= aFrame
->PrincipalChildList().FirstChild();
609 if (aFrame
->IsFrameOfType(nsIFrame::eBidiInlineContainer
) ||
610 RubyUtils::IsRubyBox(aFrame
->Type())) {
618 * Create non-fluid continuations for the ancestors of a given frame all the way
619 * up the frame tree until we hit a non-splittable frame (a line or a block).
621 * @param aParent the first parent frame to be split
622 * @param aFrame the child frames after this frame are reparented to the
623 * newly-created continuation of aParent.
624 * If aFrame is null, all the children of aParent are reparented.
626 static void SplitInlineAncestors(nsContainerFrame
* aParent
,
627 nsLineList::iterator aLine
, nsIFrame
* aFrame
) {
628 PresShell
* presShell
= aParent
->PresShell();
629 nsIFrame
* frame
= aFrame
;
630 nsContainerFrame
* parent
= aParent
;
631 nsContainerFrame
* newParent
;
633 while (IsBidiSplittable(parent
)) {
634 nsContainerFrame
* grandparent
= parent
->GetParent();
635 NS_ASSERTION(grandparent
,
636 "Couldn't get parent's parent in "
637 "nsBidiPresUtils::SplitInlineAncestors");
639 // Split the child list after |frame|, unless it is the last child.
640 if (!frame
|| frame
->GetNextSibling()) {
641 newParent
= static_cast<nsContainerFrame
*>(
642 presShell
->FrameConstructor()->CreateContinuingFrame(
643 parent
, grandparent
, false));
645 nsFrameList tail
= parent
->StealFramesAfter(frame
);
647 // Reparent views as necessary
648 nsContainerFrame::ReparentFrameViewList(tail
, parent
, newParent
);
650 // The parent's continuation adopts the siblings after the split.
651 MOZ_ASSERT(!newParent
->IsBlockFrameOrSubclass(),
652 "blocks should not be IsBidiSplittable");
653 newParent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, nullptr,
654 nullptr, std::move(tail
));
656 // While passing &aLine to InsertFrames for a non-block isn't harmful
657 // because it's a no-op, it doesn't really make sense. However, the
658 // MOZ_ASSERT() we need to guarantee that it's safe only works if the
659 // parent is actually the block.
660 const nsLineList::iterator
* parentLine
;
661 if (grandparent
->IsBlockFrameOrSubclass()) {
662 MOZ_ASSERT(aLine
->Contains(parent
));
665 parentLine
= nullptr;
668 // The list name FrameChildListID::NoReflowPrincipal would indicate we
670 grandparent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, parent
,
671 parentLine
, nsFrameList(newParent
, newParent
));
675 parent
= grandparent
;
679 static void MakeContinuationFluid(nsIFrame
* aFrame
, nsIFrame
* aNext
) {
680 NS_ASSERTION(!aFrame
->GetNextInFlow() || aFrame
->GetNextInFlow() == aNext
,
681 "next-in-flow is not next continuation!");
682 aFrame
->SetNextInFlow(aNext
);
684 NS_ASSERTION(!aNext
->GetPrevInFlow() || aNext
->GetPrevInFlow() == aFrame
,
685 "prev-in-flow is not prev continuation!");
686 aNext
->SetPrevInFlow(aFrame
);
689 static void MakeContinuationsNonFluidUpParentChain(nsIFrame
* aFrame
,
694 for (frame
= aFrame
, next
= aNext
;
695 frame
&& next
&& next
!= frame
&& next
== frame
->GetNextInFlow() &&
696 IsBidiSplittable(frame
);
697 frame
= frame
->GetParent(), next
= next
->GetParent()) {
698 frame
->SetNextContinuation(next
);
699 next
->SetPrevContinuation(frame
);
703 // If aFrame is the last child of its parent, convert bidi continuations to
704 // fluid continuations for all of its inline ancestors.
705 // If it isn't the last child, make sure that its continuation is fluid.
706 static void JoinInlineAncestors(nsIFrame
* aFrame
) {
707 nsIFrame
* frame
= aFrame
;
708 while (frame
&& IsBidiSplittable(frame
)) {
709 nsIFrame
* next
= frame
->GetNextContinuation();
711 MakeContinuationFluid(frame
, next
);
713 // Join the parent only as long as we're its last child.
714 if (frame
->GetNextSibling()) break;
715 frame
= frame
->GetParent();
719 static void CreateContinuation(nsIFrame
* aFrame
,
720 const nsLineList::iterator aLine
,
721 nsIFrame
** aNewFrame
, bool aIsFluid
) {
722 MOZ_ASSERT(aNewFrame
, "null OUT ptr");
723 MOZ_ASSERT(aFrame
, "null ptr");
725 *aNewFrame
= nullptr;
727 nsPresContext
* presContext
= aFrame
->PresContext();
728 PresShell
* presShell
= presContext
->PresShell();
729 NS_ASSERTION(presShell
,
730 "PresShell must be set on PresContext before calling "
731 "nsBidiPresUtils::CreateContinuation");
733 nsContainerFrame
* parent
= aFrame
->GetParent();
736 "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
738 // While passing &aLine to InsertFrames for a non-block isn't harmful
739 // because it's a no-op, it doesn't really make sense. However, the
740 // MOZ_ASSERT() we need to guarantee that it's safe only works if the
741 // parent is actually the block.
742 const nsLineList::iterator
* parentLine
;
743 if (parent
->IsBlockFrameOrSubclass()) {
744 MOZ_ASSERT(aLine
->Contains(aFrame
));
747 parentLine
= nullptr;
750 // Have to special case floating first letter frames because the continuation
751 // doesn't go in the first letter frame. The continuation goes with the rest
752 // of the text that the first letter frame was made out of.
753 if (parent
->IsLetterFrame() && parent
->IsFloating()) {
754 nsFirstLetterFrame
* letterFrame
= do_QueryFrame(parent
);
755 letterFrame
->CreateContinuationForFloatingParent(aFrame
, aNewFrame
,
760 *aNewFrame
= presShell
->FrameConstructor()->CreateContinuingFrame(
761 aFrame
, parent
, aIsFluid
);
763 // The list name FrameChildListID::NoReflowPrincipal would indicate we don't
765 // XXXbz this needs higher-level framelist love
766 parent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, aFrame
, parentLine
,
767 nsFrameList(*aNewFrame
, *aNewFrame
));
770 // Split inline ancestor frames
771 SplitInlineAncestors(parent
, aLine
, aFrame
);
776 * Overview of the implementation of Resolve():
778 * Walk through the descendants of aBlockFrame and build:
779 * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
780 * * mBuffer: an nsString containing a representation of
781 * the content of the frames.
782 * In the case of text frames, this is the actual text context of the
783 * frames, but some other elements are represented in a symbolic form which
784 * will make the Unicode Bidi Algorithm give the correct results.
785 * Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo>
786 * elements are represented by the corresponding Unicode control characters.
787 * <br> elements are represented by U+2028 LINE SEPARATOR
788 * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
791 * Then pass mBuffer to the Bidi engine for resolving of embedding levels
792 * by nsBidi::SetPara() and division into directional runs by
793 * nsBidi::CountRuns().
795 * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
796 * correlate them with the frames indexed in mLogicalFrames, setting the
797 * baseLevel and embeddingLevel properties according to the results returned
798 * by the Bidi engine.
800 * The rendering layer requires each text frame to contain text in only one
801 * direction, so we may need to call EnsureBidiContinuation() to split frames.
802 * We may also need to call RemoveBidiContinuation() to convert frames created
803 * by EnsureBidiContinuation() in previous reflows into fluid continuations.
805 nsresult
nsBidiPresUtils::Resolve(nsBlockFrame
* aBlockFrame
) {
806 BidiParagraphData
bpd(aBlockFrame
);
808 // Handle bidi-override being set on the block itself before calling
810 // No need to call GetBidiControl as well, because isolate and embed
811 // values of unicode-bidi property are redundant on block elements.
812 // unicode-bidi:plaintext on a block element is handled by block frame
813 // via using nsIFrame::GetWritingMode(nsIFrame*).
814 char16_t ch
= GetBidiOverride(aBlockFrame
->Style());
816 bpd
.PushBidiControl(ch
);
817 bpd
.mRequiresBidi
= true;
819 // If there are no unicode-bidi properties and no RTL characters in the
820 // block's content, then it is pure LTR and we can skip the rest of bidi
822 nsIContent
* currContent
= nullptr;
823 for (nsBlockFrame
* block
= aBlockFrame
; block
;
824 block
= static_cast<nsBlockFrame
*>(block
->GetNextContinuation())) {
825 block
->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
826 if (!bpd
.mRequiresBidi
&&
827 ChildListMayRequireBidi(block
->PrincipalChildList().FirstChild(),
829 bpd
.mRequiresBidi
= true;
831 if (!bpd
.mRequiresBidi
) {
832 nsBlockFrame::FrameLines
* overflowLines
= block
->GetOverflowLines();
834 if (ChildListMayRequireBidi(overflowLines
->mFrames
.FirstChild(),
836 bpd
.mRequiresBidi
= true;
841 if (!bpd
.mRequiresBidi
) {
846 for (nsBlockFrame
* block
= aBlockFrame
; block
;
847 block
= static_cast<nsBlockFrame
*>(block
->GetNextContinuation())) {
849 bpd
.mCurrentBlock
= block
;
851 block
->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
852 bpd
.mCurrentTraverseLine
.mLineIterator
=
853 nsBlockInFlowLineIterator(block
, block
->LinesBegin());
854 bpd
.mCurrentTraverseLine
.mPrevFrame
= nullptr;
855 TraverseFrames(block
->PrincipalChildList().FirstChild(), &bpd
);
856 nsBlockFrame::FrameLines
* overflowLines
= block
->GetOverflowLines();
858 bpd
.mCurrentTraverseLine
.mLineIterator
=
859 nsBlockInFlowLineIterator(block
, overflowLines
->mLines
.begin(), true);
860 bpd
.mCurrentTraverseLine
.mPrevFrame
= nullptr;
861 TraverseFrames(overflowLines
->mFrames
.FirstChild(), &bpd
);
866 bpd
.PopBidiControl(ch
);
869 return ResolveParagraph(&bpd
);
872 nsresult
nsBidiPresUtils::ResolveParagraph(BidiParagraphData
* aBpd
) {
873 if (aBpd
->BufferLength() < 1) {
876 aBpd
->mBuffer
.ReplaceChar(kSeparators
, kSpace
);
880 nsresult rv
= aBpd
->SetPara();
881 NS_ENSURE_SUCCESS(rv
, rv
);
883 BidiEmbeddingLevel embeddingLevel
= aBpd
->GetParagraphEmbeddingLevel();
885 rv
= aBpd
->CountRuns(&runCount
);
886 NS_ENSURE_SUCCESS(rv
, rv
);
888 int32_t runLength
= 0; // the length of the current run of text
889 int32_t logicalLimit
= 0; // the end of the current run + 1
891 int32_t fragmentLength
= 0; // the length of the current text frame
892 int32_t frameIndex
= -1; // index to the frames in mLogicalFrames
893 int32_t frameCount
= aBpd
->FrameCount();
894 int32_t contentOffset
= 0; // offset of current frame in its content node
895 bool isTextFrame
= false;
896 nsIFrame
* frame
= nullptr;
897 BidiParagraphData::FrameInfo frameInfo
;
898 nsIContent
* content
= nullptr;
899 int32_t contentTextLength
= 0;
904 "Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, "
906 (void*)aBpd
->mCurrentBlock
, NS_ConvertUTF16toUTF8(aBpd
->mBuffer
).get(),
907 frameCount
, runCount
);
908 # ifdef REALLY_NOISY_BIDI
909 printf(" block frame tree=:\n");
910 aBpd
->mCurrentBlock
->List(stdout
);
915 if (runCount
== 1 && frameCount
== 1 &&
916 aBpd
->GetParagraphDirection() == intl::Bidi::ParagraphDirection::LTR
&&
917 aBpd
->GetParagraphEmbeddingLevel() == 0) {
918 // We have a single left-to-right frame in a left-to-right paragraph,
919 // without bidi isolation from the surrounding text.
920 // Make sure that the embedding level and base level frame properties aren't
921 // set (because if they are this frame used to have some other direction,
922 // so we can't do this optimization), and we're done.
923 nsIFrame
* frame
= aBpd
->FrameAt(0);
924 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
925 FrameBidiData bidiData
= frame
->GetBidiData();
926 if (!bidiData
.embeddingLevel
&& !bidiData
.baseLevel
) {
929 printf("early return for single direction frame %p\n", (void*)frame
);
932 frame
->AddStateBits(NS_FRAME_IS_BIDI
);
938 BidiParagraphData::FrameInfo lastRealFrame
;
939 BidiEmbeddingLevel lastEmbeddingLevel
= kBidiLevelNone
;
940 BidiEmbeddingLevel precedingControl
= kBidiLevelNone
;
942 auto storeBidiDataToFrame
= [&]() {
943 FrameBidiData bidiData
;
944 bidiData
.embeddingLevel
= embeddingLevel
;
945 bidiData
.baseLevel
= aBpd
->GetParagraphEmbeddingLevel();
946 // If a control character doesn't have a lower embedding level than
947 // both the preceding and the following frame, it isn't something
948 // needed for getting the correct result. This optimization should
949 // remove almost all of embeds and overrides, and some of isolates.
950 if (precedingControl
>= embeddingLevel
||
951 precedingControl
>= lastEmbeddingLevel
) {
952 bidiData
.precedingControl
= kBidiLevelNone
;
954 bidiData
.precedingControl
= precedingControl
;
956 precedingControl
= kBidiLevelNone
;
957 lastEmbeddingLevel
= embeddingLevel
;
958 frame
->SetProperty(nsIFrame::BidiDataProperty(), bidiData
);
962 if (fragmentLength
<= 0) {
963 // Get the next frame from mLogicalFrames
964 if (++frameIndex
>= frameCount
) {
967 frameInfo
= aBpd
->FrameInfoAt(frameIndex
);
968 frame
= frameInfo
.mFrame
;
969 if (frame
== NS_BIDI_CONTROL_FRAME
|| !frame
->IsTextFrame()) {
971 * Any non-text frame corresponds to a single character in the text
972 * buffer (a bidi control character, LINE SEPARATOR, or OBJECT
978 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(frameInfo
);
979 content
= frame
->GetContent();
984 contentTextLength
= content
->TextLength();
985 auto [start
, end
] = frame
->GetOffsets();
986 NS_ASSERTION(!(contentTextLength
< end
- start
),
987 "Frame offsets don't fit in content");
988 fragmentLength
= std::min(contentTextLength
, end
- start
);
989 contentOffset
= start
;
992 } // if (fragmentLength <= 0)
994 if (runLength
<= 0) {
995 // Get the next run of text from the Bidi engine
996 if (++numRun
>= runCount
) {
997 // We've run out of runs of text; but don't forget to store bidi data
998 // to the frame before breaking out of the loop (bug 1426042).
999 storeBidiDataToFrame();
1001 frame
->AdjustOffsetsForBidi(contentOffset
,
1002 contentOffset
+ fragmentLength
);
1006 int32_t lineOffset
= logicalLimit
;
1007 aBpd
->GetLogicalRun(lineOffset
, &logicalLimit
, &embeddingLevel
);
1008 runLength
= logicalLimit
- lineOffset
;
1009 } // if (runLength <= 0)
1011 if (frame
== NS_BIDI_CONTROL_FRAME
) {
1012 // In theory, we only need to do this for isolates. However, it is
1013 // easier to do this for all here because we do not maintain the
1014 // index to get corresponding character from buffer. Since we do
1015 // have proper embedding level for all those characters, including
1016 // them wouldn't affect the final result.
1017 precedingControl
= std::min(precedingControl
, embeddingLevel
);
1019 storeBidiDataToFrame();
1021 if (contentTextLength
== 0) {
1022 // Set the base level and embedding level of the current run even
1023 // on an empty frame. Otherwise frame reordering will not be correct.
1024 frame
->AdjustOffsetsForBidi(0, 0);
1025 // Nothing more to do for an empty frame, except update
1026 // lastRealFrame like we do below.
1027 lastRealFrame
= frameInfo
;
1030 nsLineList::iterator currentLine
= aBpd
->mCurrentResolveLine
.GetLine();
1031 if ((runLength
> 0) && (runLength
< fragmentLength
)) {
1033 * The text in this frame continues beyond the end of this directional
1034 * run. Create a non-fluid continuation frame for the next directional
1037 currentLine
->MarkDirty();
1039 int32_t runEnd
= contentOffset
+ runLength
;
1040 EnsureBidiContinuation(frame
, currentLine
, &nextBidi
, contentOffset
,
1042 nextBidi
->AdjustOffsetsForBidi(runEnd
,
1043 contentOffset
+ fragmentLength
);
1045 frameInfo
.mFrame
= frame
;
1046 contentOffset
= runEnd
;
1048 aBpd
->mCurrentResolveLine
.AdvanceToFrame(frame
);
1049 } // if (runLength < fragmentLength)
1051 if (contentOffset
+ fragmentLength
== contentTextLength
) {
1053 * We have finished all the text in this content node. Convert any
1054 * further non-fluid continuations to fluid continuations and
1055 * advance frameIndex to the last frame in the content node
1057 int32_t newIndex
= aBpd
->GetLastFrameForContent(content
);
1058 if (newIndex
> frameIndex
) {
1059 currentLine
->MarkDirty();
1060 RemoveBidiContinuation(aBpd
, frame
, frameIndex
, newIndex
);
1061 frameIndex
= newIndex
;
1062 frameInfo
= aBpd
->FrameInfoAt(frameIndex
);
1063 frame
= frameInfo
.mFrame
;
1065 } else if (fragmentLength
> 0 && runLength
> fragmentLength
) {
1067 * There is more text that belongs to this directional run in the
1068 * next text frame: make sure it is a fluid continuation of the
1069 * current frame. Do not advance frameIndex, because the next frame
1070 * may contain multi-directional text and need to be split
1072 int32_t newIndex
= frameIndex
;
1074 } while (++newIndex
< frameCount
&&
1075 aBpd
->FrameAt(newIndex
) == NS_BIDI_CONTROL_FRAME
);
1076 if (newIndex
< frameCount
) {
1077 currentLine
->MarkDirty();
1078 RemoveBidiContinuation(aBpd
, frame
, frameIndex
, newIndex
);
1080 } else if (runLength
== fragmentLength
) {
1082 * If the directional run ends at the end of the frame, make sure
1083 * that any continuation is non-fluid, and do the same up the
1086 nsIFrame
* next
= frame
->GetNextInFlow();
1088 currentLine
->MarkDirty();
1089 MakeContinuationsNonFluidUpParentChain(frame
, next
);
1092 frame
->AdjustOffsetsForBidi(contentOffset
,
1093 contentOffset
+ fragmentLength
);
1096 } // not bidi control frame
1097 int32_t temp
= runLength
;
1098 runLength
-= fragmentLength
;
1099 fragmentLength
-= temp
;
1101 // Record last real frame so that we can do splitting properly even
1102 // if a run ends after a virtual bidi control frame.
1103 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1104 lastRealFrame
= frameInfo
;
1106 if (lastRealFrame
.mFrame
&& fragmentLength
<= 0) {
1107 // If the frame is at the end of a run, and this is not the end of our
1108 // paragraph, split all ancestor inlines that need splitting.
1109 // To determine whether we're at the end of the run, we check that we've
1110 // finished processing the current run, and that the current frame
1111 // doesn't have a fluid continuation (it could have a fluid continuation
1112 // of zero length, so testing runLength alone is not sufficient).
1113 if (runLength
<= 0 && !lastRealFrame
.mFrame
->GetNextInFlow()) {
1114 if (numRun
+ 1 < runCount
) {
1115 nsIFrame
* child
= lastRealFrame
.mFrame
;
1116 nsContainerFrame
* parent
= child
->GetParent();
1117 // As long as we're on the last sibling, the parent doesn't have to
1119 // However, if the parent has a fluid continuation, we do have to make
1120 // it non-fluid. This can happen e.g. when we have a first-letter
1121 // frame and the end of the first-letter coincides with the end of a
1123 while (parent
&& IsBidiSplittable(parent
) &&
1124 !child
->GetNextSibling()) {
1125 nsIFrame
* next
= parent
->GetNextInFlow();
1127 parent
->SetNextContinuation(next
);
1128 next
->SetPrevContinuation(parent
);
1131 parent
= child
->GetParent();
1133 if (parent
&& IsBidiSplittable(parent
)) {
1134 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(lastRealFrame
);
1135 SplitInlineAncestors(parent
, aBpd
->mCurrentResolveLine
.GetLine(),
1138 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(lastRealFrame
);
1141 } else if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1142 // We're not at an end of a run. If |frame| is the last child of its
1143 // parent, and its ancestors happen to have bidi continuations, convert
1144 // them into fluid continuations.
1145 JoinInlineAncestors(frame
);
1151 # ifdef REALLY_NOISY_BIDI
1152 printf("---\nAfter Resolve(), frameTree =:\n");
1153 aBpd
->mCurrentBlock
->List(stdout
);
1161 void nsBidiPresUtils::TraverseFrames(nsIFrame
* aCurrentFrame
,
1162 BidiParagraphData
* aBpd
) {
1163 if (!aCurrentFrame
) return;
1166 nsBlockFrame
* initialLineContainer
=
1167 aBpd
->mCurrentTraverseLine
.mLineIterator
.GetContainer();
1170 nsIFrame
* childFrame
= aCurrentFrame
;
1173 * It's important to get the next sibling and next continuation *before*
1174 * handling the frame: If we encounter a forced paragraph break and call
1175 * ResolveParagraph within this loop, doing GetNextSibling and
1176 * GetNextContinuation after that could return a bidi continuation that had
1177 * just been split from the original childFrame and we would process it
1180 nsIFrame
* nextSibling
= childFrame
->GetNextSibling();
1182 // If the real frame for a placeholder is a first letter frame, we need to
1183 // drill down into it and include its contents in Bidi resolution.
1184 // If not, we just use the placeholder.
1185 nsIFrame
* frame
= childFrame
;
1186 if (childFrame
->IsPlaceholderFrame()) {
1187 nsIFrame
* realFrame
=
1188 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame
);
1189 if (realFrame
->IsLetterFrame()) {
1194 auto DifferentBidiValues
= [](ComputedStyle
* aSC1
, nsIFrame
* aFrame2
) {
1195 ComputedStyle
* sc2
= aFrame2
->Style();
1196 return GetBidiControl(aSC1
) != GetBidiControl(sc2
) ||
1197 GetBidiOverride(aSC1
) != GetBidiOverride(sc2
);
1200 ComputedStyle
* sc
= frame
->Style();
1201 nsIFrame
* nextContinuation
= frame
->GetNextContinuation();
1202 nsIFrame
* prevContinuation
= frame
->GetPrevContinuation();
1204 !nextContinuation
|| DifferentBidiValues(sc
, nextContinuation
);
1206 !prevContinuation
|| DifferentBidiValues(sc
, prevContinuation
);
1208 char16_t controlChar
= 0;
1209 char16_t overrideChar
= 0;
1210 LayoutFrameType frameType
= frame
->Type();
1211 if (frame
->IsFrameOfType(nsIFrame::eBidiInlineContainer
) ||
1212 frameType
== LayoutFrameType::Ruby
) {
1213 if (!frame
->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
1214 nsContainerFrame
* c
= static_cast<nsContainerFrame
*>(frame
);
1215 MOZ_ASSERT(c
== do_QueryFrame(frame
),
1216 "eBidiInlineContainer and ruby frame must be"
1217 " a nsContainerFrame subclass");
1218 c
->DrainSelfOverflowList();
1221 controlChar
= GetBidiControl(sc
);
1222 overrideChar
= GetBidiOverride(sc
);
1224 // Add dummy frame pointers representing bidi control codes before
1225 // the first frames of elements specifying override, isolation, or
1228 if (controlChar
!= 0) {
1229 aBpd
->PushBidiControl(controlChar
);
1231 if (overrideChar
!= 0) {
1232 aBpd
->PushBidiControl(overrideChar
);
1237 if (IsBidiLeaf(frame
)) {
1238 /* Bidi leaf frame: add the frame to the mLogicalFrames array,
1239 * and add its index to the mContentToFrameIndex hashtable. This
1240 * will be used in RemoveBidiContinuation() to identify the last
1241 * frame in the array with a given content.
1243 nsIContent
* content
= frame
->GetContent();
1244 aBpd
->AppendFrame(frame
, aBpd
->mCurrentTraverseLine
, content
);
1246 // Append the content of the frame to the paragraph buffer
1247 if (LayoutFrameType::Text
== frameType
) {
1248 if (content
!= aBpd
->mPrevContent
) {
1249 aBpd
->mPrevContent
= content
;
1250 if (!frame
->StyleText()->NewlineIsSignificant(
1251 static_cast<nsTextFrame
*>(frame
))) {
1252 content
->GetAsText()->AppendTextTo(aBpd
->mBuffer
);
1255 * For preformatted text we have to do bidi resolution on each line
1259 content
->GetAsText()->AppendTextTo(text
);
1264 auto [start
, end
] = frame
->GetOffsets();
1265 int32_t endLine
= text
.FindChar('\n', start
);
1266 if (endLine
== -1) {
1268 * If there is no newline in the text content, just save the
1269 * text from this frame and its continuations, and do bidi
1272 aBpd
->AppendString(Substring(text
, start
));
1273 while (frame
&& nextSibling
) {
1274 aBpd
->AdvanceAndAppendFrame(
1275 &frame
, aBpd
->mCurrentTraverseLine
, &nextSibling
);
1281 * If there is a newline in the frame, break the frame after the
1282 * newline, do bidi resolution and repeat until the last sibling
1287 * If the frame ends before the new line, save the text and move
1288 * into the next continuation
1291 Substring(text
, start
, std::min(end
, endLine
) - start
));
1292 while (end
< endLine
&& nextSibling
) {
1293 aBpd
->AdvanceAndAppendFrame(&frame
, aBpd
->mCurrentTraverseLine
,
1295 NS_ASSERTION(frame
, "Premature end of continuation chain");
1296 std::tie(start
, end
) = frame
->GetOffsets();
1298 Substring(text
, start
, std::min(end
, endLine
) - start
));
1301 if (end
< endLine
) {
1302 aBpd
->mPrevContent
= nullptr;
1306 bool createdContinuation
= false;
1307 if (uint32_t(endLine
) < text
.Length()) {
1309 * Timing is everything here: if the frame already has a bidi
1310 * continuation, we need to make the continuation fluid *before*
1311 * resetting the length of the current frame. Otherwise
1312 * nsTextFrame::SetLength won't set the continuation frame's
1313 * text offsets correctly.
1315 * On the other hand, if the frame doesn't have a continuation,
1316 * we need to create one *after* resetting the length, or
1317 * CreateContinuingFrame will complain that there is no more
1318 * content for the continuation.
1320 next
= frame
->GetNextInFlow();
1322 // If the frame already has a bidi continuation, make it fluid
1323 next
= frame
->GetNextContinuation();
1325 MakeContinuationFluid(frame
, next
);
1326 JoinInlineAncestors(frame
);
1330 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
1331 textFrame
->SetLength(endLine
- start
, nullptr);
1333 // If it weren't for CreateContinuation needing this to
1334 // be current, we could restructure the marking dirty
1335 // below to use mCurrentResolveLine and eliminate
1336 // mCurrentTraverseLine entirely.
1337 aBpd
->mCurrentTraverseLine
.AdvanceToFrame(frame
);
1340 // If the frame has no next in flow, create one.
1342 frame
, aBpd
->mCurrentTraverseLine
.GetLine(), &next
, true);
1343 createdContinuation
= true;
1345 // Mark the line before the newline as dirty.
1346 aBpd
->mCurrentTraverseLine
.GetLine()->MarkDirty();
1348 ResolveParagraphWithinBlock(aBpd
);
1350 if (!nextSibling
&& !createdContinuation
) {
1355 aBpd
->AppendFrame(frame
, aBpd
->mCurrentTraverseLine
);
1356 // Mark the line after the newline as dirty.
1357 aBpd
->mCurrentTraverseLine
.AdvanceToFrame(frame
);
1358 aBpd
->mCurrentTraverseLine
.GetLine()->MarkDirty();
1362 * If we have already overshot the saved next-sibling while
1363 * scanning the frame's continuations, advance it.
1365 if (frame
&& frame
== nextSibling
) {
1366 nextSibling
= frame
->GetNextSibling();
1372 } else if (LayoutFrameType::Br
== frameType
) {
1373 // break frame -- append line separator
1374 aBpd
->AppendUnichar(kLineSeparator
);
1375 ResolveParagraphWithinBlock(aBpd
);
1377 // other frame type -- see the Unicode Bidi Algorithm:
1378 // "...inline objects (such as graphics) are treated as if they are ...
1380 // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
1381 // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1
1382 aBpd
->AppendUnichar(
1383 content
->IsHTMLElement(nsGkAtoms::wbr
) ? kZWSP
: kObjectSubstitute
);
1384 if (!frame
->IsInlineOutside()) {
1385 // if it is not inline, end the paragraph
1386 ResolveParagraphWithinBlock(aBpd
);
1390 // For a non-leaf frame, recurse into TraverseFrames
1391 nsIFrame
* kid
= frame
->PrincipalChildList().FirstChild();
1392 MOZ_ASSERT(!frame
->GetChildList(FrameChildListID::Overflow
).FirstChild(),
1393 "should have drained the overflow list above");
1395 TraverseFrames(kid
, aBpd
);
1399 // If the element is attributed by dir, indicate direction pop (add PDF
1402 // Add a dummy frame pointer representing a bidi control code after the
1403 // last frame of an element specifying embedding or override
1404 if (overrideChar
!= 0) {
1405 aBpd
->PopBidiControl(overrideChar
);
1407 if (controlChar
!= 0) {
1408 aBpd
->PopBidiControl(controlChar
);
1411 childFrame
= nextSibling
;
1412 } while (childFrame
);
1414 MOZ_ASSERT(initialLineContainer
==
1415 aBpd
->mCurrentTraverseLine
.mLineIterator
.GetContainer());
1418 bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame
* aFirstChild
,
1419 nsIContent
** aCurrContent
) {
1420 MOZ_ASSERT(!aFirstChild
|| !aFirstChild
->GetPrevSibling(),
1421 "Expecting to traverse from the start of a child list");
1423 for (nsIFrame
* childFrame
= aFirstChild
; childFrame
;
1424 childFrame
= childFrame
->GetNextSibling()) {
1425 nsIFrame
* frame
= childFrame
;
1427 // If the real frame for a placeholder is a first-letter frame, we need to
1428 // consider its contents for potential Bidi resolution.
1429 if (childFrame
->IsPlaceholderFrame()) {
1430 nsIFrame
* realFrame
=
1431 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame
);
1432 if (realFrame
->IsLetterFrame()) {
1437 // If unicode-bidi properties are present, we should do bidi resolution.
1438 ComputedStyle
* sc
= frame
->Style();
1439 if (GetBidiControl(sc
) || GetBidiOverride(sc
)) {
1443 if (IsBidiLeaf(frame
)) {
1444 if (frame
->IsTextFrame()) {
1445 // If the frame already has a BidiDataProperty, we know we need to
1446 // perform bidi resolution (even if no bidi content is NOW present --
1447 // we might need to remove the property set by a previous reflow, if
1448 // content has changed; see bug 1366623).
1449 if (frame
->HasProperty(nsIFrame::BidiDataProperty())) {
1453 // Check whether the text frame has any RTL characters; if so, bidi
1454 // resolution will be needed.
1455 dom::Text
* content
= frame
->GetContent()->AsText();
1456 if (content
!= *aCurrContent
) {
1457 *aCurrContent
= content
;
1458 const nsTextFragment
* txt
= &content
->TextFragment();
1460 HasRTLChars(Span(txt
->Get2b(), txt
->GetLength()))) {
1465 } else if (ChildListMayRequireBidi(frame
->PrincipalChildList().FirstChild(),
1474 void nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData
* aBpd
) {
1475 aBpd
->ClearBidiControls();
1476 ResolveParagraph(aBpd
);
1481 nscoord
nsBidiPresUtils::ReorderFrames(nsIFrame
* aFirstFrameOnLine
,
1482 int32_t aNumFramesOnLine
,
1483 WritingMode aLineWM
,
1484 const nsSize
& aContainerSize
,
1486 nsSize
containerSize(aContainerSize
);
1488 // If this line consists of a line frame, reorder the line frame's children.
1489 if (aFirstFrameOnLine
->IsLineFrame()) {
1490 // The line frame is positioned at the start-edge, so use its size
1491 // as the container size.
1492 containerSize
= aFirstFrameOnLine
->GetSize();
1494 aFirstFrameOnLine
= aFirstFrameOnLine
->PrincipalChildList().FirstChild();
1495 if (!aFirstFrameOnLine
) {
1498 // All children of the line frame are on the first line. Setting
1499 // aNumFramesOnLine to -1 makes InitLogicalArrayFromLine look at all of
1501 aNumFramesOnLine
= -1;
1502 // As the line frame itself has been adjusted at its inline-start position
1503 // by the caller, we do not want to apply this to its children.
1507 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1508 return RepositionInlineFrames(&bld
, aLineWM
, containerSize
, aStart
);
1511 nsIFrame
* nsBidiPresUtils::GetFirstLeaf(nsIFrame
* aFrame
) {
1512 nsIFrame
* firstLeaf
= aFrame
;
1513 while (!IsBidiLeaf(firstLeaf
)) {
1514 nsIFrame
* firstChild
= firstLeaf
->PrincipalChildList().FirstChild();
1515 nsIFrame
* realFrame
= nsPlaceholderFrame::GetRealFrameFor(firstChild
);
1516 firstLeaf
= (realFrame
->IsLetterFrame()) ? realFrame
: firstChild
;
1521 FrameBidiData
nsBidiPresUtils::GetFrameBidiData(nsIFrame
* aFrame
) {
1522 return GetFirstLeaf(aFrame
)->GetBidiData();
1525 BidiEmbeddingLevel
nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame
* aFrame
) {
1526 return GetFirstLeaf(aFrame
)->GetEmbeddingLevel();
1529 BidiEmbeddingLevel
nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame
* aFrame
) {
1530 const nsIFrame
* firstLeaf
= aFrame
;
1531 while (!IsBidiLeaf(firstLeaf
)) {
1532 firstLeaf
= firstLeaf
->PrincipalChildList().FirstChild();
1534 return firstLeaf
->GetBaseLevel();
1537 void nsBidiPresUtils::IsFirstOrLast(nsIFrame
* aFrame
,
1538 nsContinuationStates
* aContinuationStates
,
1539 bool aSpanDirMatchesLineDir
,
1540 bool& aIsFirst
/* out */,
1541 bool& aIsLast
/* out */) {
1543 * Since we lay out frames in the line's direction, visiting a frame with
1544 * 'mFirstVisualFrame == nullptr', means it's the first appearance of one
1545 * of its continuation chain frames on the line.
1546 * To determine if it's the last visual frame of its continuation chain on
1547 * the line or not, we count the number of frames of the chain on the line,
1548 * and then reduce it when we lay out a frame of the chain. If this value
1549 * becomes 1 it means that it's the last visual frame of its continuation
1550 * chain on this line.
1553 bool firstInLineOrder
, lastInLineOrder
;
1554 nsFrameContinuationState
* frameState
= aContinuationStates
->Get(aFrame
);
1555 nsFrameContinuationState
* firstFrameState
;
1557 if (!frameState
->mFirstVisualFrame
) {
1558 // aFrame is the first visual frame of its continuation chain
1559 nsFrameContinuationState
* contState
;
1562 frameState
->mFrameCount
= 1;
1563 frameState
->mFirstVisualFrame
= aFrame
;
1566 * Traverse continuation chain of aFrame in both backward and forward
1567 * directions while the frames are on this line. Count the frames and
1568 * set their mFirstVisualFrame to aFrame.
1570 // Traverse continuation chain backward
1571 for (frame
= aFrame
->GetPrevContinuation();
1572 frame
&& (contState
= aContinuationStates
->Get(frame
));
1573 frame
= frame
->GetPrevContinuation()) {
1574 frameState
->mFrameCount
++;
1575 contState
->mFirstVisualFrame
= aFrame
;
1577 frameState
->mHasContOnPrevLines
= (frame
!= nullptr);
1579 // Traverse continuation chain forward
1580 for (frame
= aFrame
->GetNextContinuation();
1581 frame
&& (contState
= aContinuationStates
->Get(frame
));
1582 frame
= frame
->GetNextContinuation()) {
1583 frameState
->mFrameCount
++;
1584 contState
->mFirstVisualFrame
= aFrame
;
1586 frameState
->mHasContOnNextLines
= (frame
!= nullptr);
1588 firstInLineOrder
= true;
1589 firstFrameState
= frameState
;
1591 // aFrame is not the first visual frame of its continuation chain
1592 firstInLineOrder
= false;
1593 firstFrameState
= aContinuationStates
->Get(frameState
->mFirstVisualFrame
);
1596 lastInLineOrder
= (firstFrameState
->mFrameCount
== 1);
1598 if (aSpanDirMatchesLineDir
) {
1599 aIsFirst
= firstInLineOrder
;
1600 aIsLast
= lastInLineOrder
;
1602 aIsFirst
= lastInLineOrder
;
1603 aIsLast
= firstInLineOrder
;
1606 if (frameState
->mHasContOnPrevLines
) {
1609 if (firstFrameState
->mHasContOnNextLines
) {
1613 if ((aIsFirst
|| aIsLast
) &&
1614 aFrame
->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT
)) {
1615 // For ib splits, don't treat anything except the last part as
1616 // endmost or anything except the first part as startmost.
1617 // As an optimization, only get the first continuation once.
1618 nsIFrame
* firstContinuation
= aFrame
->FirstContinuation();
1619 if (firstContinuation
->FrameIsNonLastInIBSplit()) {
1620 // We are not endmost
1623 if (firstContinuation
->FrameIsNonFirstInIBSplit()) {
1624 // We are not startmost
1629 // Reduce number of remaining frames of the continuation chain on the line.
1630 firstFrameState
->mFrameCount
--;
1632 nsInlineFrame
* testFrame
= do_QueryFrame(aFrame
);
1635 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET
);
1638 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST
);
1640 aFrame
->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST
);
1644 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST
);
1646 aFrame
->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST
);
1652 void nsBidiPresUtils::RepositionRubyContentFrame(
1653 nsIFrame
* aFrame
, WritingMode aFrameWM
,
1654 const LogicalMargin
& aBorderPadding
) {
1655 const nsFrameList
& childList
= aFrame
->PrincipalChildList();
1656 if (childList
.IsEmpty()) {
1660 // Reorder the children.
1662 ReorderFrames(childList
.FirstChild(), childList
.GetLength(), aFrameWM
,
1663 aFrame
->GetSize(), aBorderPadding
.IStart(aFrameWM
));
1664 isize
+= aBorderPadding
.IEnd(aFrameWM
);
1666 if (aFrame
->StyleText()->mRubyAlign
== StyleRubyAlign::Start
) {
1669 nscoord residualISize
= aFrame
->ISize(aFrameWM
) - isize
;
1670 if (residualISize
<= 0) {
1674 // When ruby-align is not "start", if the content does not fill this
1675 // frame, we need to center the children.
1676 const nsSize dummyContainerSize
;
1677 for (nsIFrame
* child
: childList
) {
1678 LogicalRect rect
= child
->GetLogicalRect(aFrameWM
, dummyContainerSize
);
1679 rect
.IStart(aFrameWM
) += residualISize
/ 2;
1680 child
->SetRect(aFrameWM
, rect
, dummyContainerSize
);
1685 nscoord
nsBidiPresUtils::RepositionRubyFrame(
1686 nsIFrame
* aFrame
, nsContinuationStates
* aContinuationStates
,
1687 const WritingMode aContainerWM
, const LogicalMargin
& aBorderPadding
) {
1688 LayoutFrameType frameType
= aFrame
->Type();
1689 MOZ_ASSERT(RubyUtils::IsRubyBox(frameType
));
1692 WritingMode frameWM
= aFrame
->GetWritingMode();
1693 bool isLTR
= frameWM
.IsBidiLTR();
1694 nsSize frameSize
= aFrame
->GetSize();
1695 if (frameType
== LayoutFrameType::Ruby
) {
1696 icoord
+= aBorderPadding
.IStart(frameWM
);
1697 // Reposition ruby segments in a ruby container
1698 for (RubySegmentEnumerator
e(static_cast<nsRubyFrame
*>(aFrame
)); !e
.AtEnd();
1700 nsRubyBaseContainerFrame
* rbc
= e
.GetBaseContainer();
1701 AutoRubyTextContainerArray
textContainers(rbc
);
1703 nscoord segmentISize
= RepositionFrame(
1704 rbc
, isLTR
, icoord
, aContinuationStates
, frameWM
, false, frameSize
);
1705 for (nsRubyTextContainerFrame
* rtc
: textContainers
) {
1706 nscoord isize
= RepositionFrame(rtc
, isLTR
, icoord
, aContinuationStates
,
1707 frameWM
, false, frameSize
);
1708 segmentISize
= std::max(segmentISize
, isize
);
1710 icoord
+= segmentISize
;
1712 icoord
+= aBorderPadding
.IEnd(frameWM
);
1713 } else if (frameType
== LayoutFrameType::RubyBaseContainer
) {
1714 // Reposition ruby columns in a ruby segment
1715 auto rbc
= static_cast<nsRubyBaseContainerFrame
*>(aFrame
);
1716 AutoRubyTextContainerArray
textContainers(rbc
);
1718 for (RubyColumnEnumerator
e(rbc
, textContainers
); !e
.AtEnd(); e
.Next()) {
1720 e
.GetColumn(column
);
1722 nscoord columnISize
=
1723 RepositionFrame(column
.mBaseFrame
, isLTR
, icoord
, aContinuationStates
,
1724 frameWM
, false, frameSize
);
1725 for (nsRubyTextFrame
* rt
: column
.mTextFrames
) {
1726 nscoord isize
= RepositionFrame(rt
, isLTR
, icoord
, aContinuationStates
,
1727 frameWM
, false, frameSize
);
1728 columnISize
= std::max(columnISize
, isize
);
1730 icoord
+= columnISize
;
1733 if (frameType
== LayoutFrameType::RubyBase
||
1734 frameType
== LayoutFrameType::RubyText
) {
1735 RepositionRubyContentFrame(aFrame
, frameWM
, aBorderPadding
);
1737 // Note that, ruby text container is not present in all conditions
1738 // above. It is intended, because the children of rtc are reordered
1739 // with the children of ruby base container simultaneously. We only
1740 // need to return its isize here, as it should not be changed.
1741 icoord
+= aFrame
->ISize(aContainerWM
);
1747 nscoord
nsBidiPresUtils::RepositionFrame(
1748 nsIFrame
* aFrame
, bool aIsEvenLevel
, nscoord aStartOrEnd
,
1749 nsContinuationStates
* aContinuationStates
, WritingMode aContainerWM
,
1750 bool aContainerReverseDir
, const nsSize
& aContainerSize
) {
1752 aContainerWM
.IsVertical() ? aContainerSize
.height
: aContainerSize
.width
;
1753 NS_ASSERTION(lineSize
!= NS_UNCONSTRAINEDSIZE
,
1754 "Unconstrained inline line size in bidi frame reordering");
1755 if (!aFrame
) return 0;
1757 bool isFirst
, isLast
;
1758 WritingMode frameWM
= aFrame
->GetWritingMode();
1759 IsFirstOrLast(aFrame
, aContinuationStates
,
1760 aContainerWM
.IsBidiLTR() == frameWM
.IsBidiLTR(),
1761 isFirst
/* out */, isLast
/* out */);
1763 // We only need the margin if the frame is first or last in its own
1764 // writing mode, but we're traversing the frames in the order of the
1765 // container's writing mode. To get the right values, we set start and
1766 // end margins on a logical margin in the frame's writing mode, and
1767 // then convert the margin to the container's writing mode to set the
1770 // This method is called from nsBlockFrame::PlaceLine via the call to
1771 // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
1772 // have been reflowed, which is required for GetUsedMargin/Border/Padding
1773 nscoord frameISize
= aFrame
->ISize();
1774 LogicalMargin frameMargin
= aFrame
->GetLogicalUsedMargin(frameWM
);
1775 LogicalMargin borderPadding
= aFrame
->GetLogicalUsedBorderAndPadding(frameWM
);
1776 // Since the visual order of frame could be different from the continuation
1777 // order, we need to remove any inline border/padding [that is already applied
1778 // based on continuation order] and then add it back based on the visual order
1779 // (i.e. isFirst/isLast) to get the correct isize for the current frame.
1780 // We don't need to do that for 'box-decoration-break:clone' because then all
1781 // continuations have border/padding/margin applied.
1782 if (aFrame
->StyleBorder()->mBoxDecorationBreak
==
1783 StyleBoxDecorationBreak::Slice
) {
1784 // First remove the border/padding that was applied based on logical order.
1785 if (!aFrame
->GetPrevContinuation()) {
1786 frameISize
-= borderPadding
.IStart(frameWM
);
1788 if (!aFrame
->GetNextContinuation()) {
1789 frameISize
-= borderPadding
.IEnd(frameWM
);
1791 // Set margin/border/padding based on visual order.
1793 frameMargin
.IStart(frameWM
) = 0;
1794 borderPadding
.IStart(frameWM
) = 0;
1797 frameMargin
.IEnd(frameWM
) = 0;
1798 borderPadding
.IEnd(frameWM
) = 0;
1800 // Add the border/padding which is now based on visual order.
1801 frameISize
+= borderPadding
.IStartEnd(frameWM
);
1805 if (IsBidiLeaf(aFrame
)) {
1807 frameWM
.IsOrthogonalTo(aContainerWM
) ? aFrame
->BSize() : frameISize
;
1808 } else if (RubyUtils::IsRubyBox(aFrame
->Type())) {
1809 icoord
+= RepositionRubyFrame(aFrame
, aContinuationStates
, aContainerWM
,
1812 bool reverseDir
= aIsEvenLevel
!= frameWM
.IsBidiLTR();
1813 icoord
+= reverseDir
? borderPadding
.IEnd(frameWM
)
1814 : borderPadding
.IStart(frameWM
);
1815 LogicalSize
logicalSize(frameWM
, frameISize
, aFrame
->BSize());
1816 nsSize frameSize
= logicalSize
.GetPhysicalSize(frameWM
);
1817 // Reposition the child frames
1818 for (nsIFrame
* f
: aFrame
->PrincipalChildList()) {
1819 icoord
+= RepositionFrame(f
, aIsEvenLevel
, icoord
, aContinuationStates
,
1820 frameWM
, reverseDir
, frameSize
);
1822 icoord
+= reverseDir
? borderPadding
.IStart(frameWM
)
1823 : borderPadding
.IEnd(frameWM
);
1826 // In the following variables, if aContainerReverseDir is true, i.e.
1827 // the container is positioning its children in reverse of its logical
1828 // direction, the "StartOrEnd" refers to the distance from the frame
1829 // to the inline end edge of the container, elsewise, it refers to the
1830 // distance to the inline start edge.
1831 const LogicalMargin margin
= frameMargin
.ConvertTo(aContainerWM
, frameWM
);
1832 nscoord marginStartOrEnd
= aContainerReverseDir
? margin
.IEnd(aContainerWM
)
1833 : margin
.IStart(aContainerWM
);
1834 nscoord frameStartOrEnd
= aStartOrEnd
+ marginStartOrEnd
;
1836 LogicalRect rect
= aFrame
->GetLogicalRect(aContainerWM
, aContainerSize
);
1837 rect
.ISize(aContainerWM
) = icoord
;
1838 rect
.IStart(aContainerWM
) = aContainerReverseDir
1839 ? lineSize
- frameStartOrEnd
- icoord
1841 aFrame
->SetRect(aContainerWM
, rect
, aContainerSize
);
1843 return icoord
+ margin
.IStartEnd(aContainerWM
);
1846 void nsBidiPresUtils::InitContinuationStates(
1847 nsIFrame
* aFrame
, nsContinuationStates
* aContinuationStates
) {
1848 aContinuationStates
->Insert(aFrame
);
1849 if (!IsBidiLeaf(aFrame
)) {
1850 // Continue for child frames
1851 for (nsIFrame
* frame
: aFrame
->PrincipalChildList()) {
1852 InitContinuationStates(frame
, aContinuationStates
);
1858 nscoord
nsBidiPresUtils::RepositionInlineFrames(BidiLineData
* aBld
,
1859 WritingMode aLineWM
,
1860 const nsSize
& aContainerSize
,
1862 nscoord start
= aStart
;
1864 int32_t count
= aBld
->mVisualFrames
.Length();
1866 nsContinuationStates continuationStates
;
1868 // Initialize continuation states to (nullptr, 0) for
1869 // each frame on the line.
1870 for (index
= 0; index
< count
; index
++) {
1871 InitContinuationStates(aBld
->VisualFrameAt(index
), &continuationStates
);
1874 // Reposition frames in visual order
1875 int32_t step
, limit
;
1876 if (aLineWM
.IsBidiLTR()) {
1885 for (; index
!= limit
; index
+= step
) {
1886 frame
= aBld
->VisualFrameAt(index
);
1887 start
+= RepositionFrame(
1888 frame
, !(aBld
->mLevels
[aBld
->mIndexMap
[index
]].IsRTL()), start
,
1889 &continuationStates
, aLineWM
, false, aContainerSize
);
1894 bool nsBidiPresUtils::CheckLineOrder(nsIFrame
* aFirstFrameOnLine
,
1895 int32_t aNumFramesOnLine
,
1896 nsIFrame
** aFirstVisual
,
1897 nsIFrame
** aLastVisual
) {
1898 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1899 int32_t count
= bld
.FrameCount();
1902 *aFirstVisual
= bld
.VisualFrameAt(0);
1905 *aLastVisual
= bld
.VisualFrameAt(count
- 1);
1908 return bld
.mIsReordered
;
1911 nsIFrame
* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame
* aFrame
,
1912 nsIFrame
* aFirstFrameOnLine
,
1913 int32_t aNumFramesOnLine
) {
1914 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1916 int32_t count
= bld
.mVisualFrames
.Length();
1918 if (aFrame
== nullptr && count
) return bld
.VisualFrameAt(0);
1920 for (int32_t i
= 0; i
< count
- 1; i
++) {
1921 if (bld
.VisualFrameAt(i
) == aFrame
) {
1922 return bld
.VisualFrameAt(i
+ 1);
1929 nsIFrame
* nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame
* aFrame
,
1930 nsIFrame
* aFirstFrameOnLine
,
1931 int32_t aNumFramesOnLine
) {
1932 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1934 int32_t count
= bld
.mVisualFrames
.Length();
1936 if (aFrame
== nullptr && count
) return bld
.VisualFrameAt(count
- 1);
1938 for (int32_t i
= 1; i
< count
; i
++) {
1939 if (bld
.VisualFrameAt(i
) == aFrame
) {
1940 return bld
.VisualFrameAt(i
- 1);
1947 inline void nsBidiPresUtils::EnsureBidiContinuation(
1948 nsIFrame
* aFrame
, const nsLineList::iterator aLine
, nsIFrame
** aNewFrame
,
1949 int32_t aStart
, int32_t aEnd
) {
1950 MOZ_ASSERT(aNewFrame
, "null OUT ptr");
1951 MOZ_ASSERT(aFrame
, "aFrame is null");
1953 aFrame
->AdjustOffsetsForBidi(aStart
, aEnd
);
1954 CreateContinuation(aFrame
, aLine
, aNewFrame
, false);
1957 void nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData
* aBpd
,
1959 int32_t aFirstIndex
,
1960 int32_t aLastIndex
) {
1961 FrameBidiData bidiData
= aFrame
->GetBidiData();
1962 bidiData
.precedingControl
= kBidiLevelNone
;
1963 for (int32_t index
= aFirstIndex
+ 1; index
<= aLastIndex
; index
++) {
1964 nsIFrame
* frame
= aBpd
->FrameAt(index
);
1965 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1966 // Make the frame and its continuation ancestors fluid,
1967 // so they can be reused or deleted by normal reflow code
1968 frame
->SetProperty(nsIFrame::BidiDataProperty(), bidiData
);
1969 frame
->AddStateBits(NS_FRAME_IS_BIDI
);
1970 while (frame
&& IsBidiSplittable(frame
)) {
1971 nsIFrame
* prev
= frame
->GetPrevContinuation();
1973 MakeContinuationFluid(prev
, frame
);
1974 frame
= frame
->GetParent();
1982 // Make sure that the last continuation we made fluid does not itself have a
1983 // fluid continuation (this can happen when re-resolving after dynamic changes
1985 nsIFrame
* lastFrame
= aBpd
->FrameAt(aLastIndex
);
1986 MakeContinuationsNonFluidUpParentChain(lastFrame
, lastFrame
->GetNextInFlow());
1989 nsresult
nsBidiPresUtils::FormatUnicodeText(nsPresContext
* aPresContext
,
1991 int32_t& aTextLength
,
1992 intl::BidiClass aBidiClass
) {
1993 nsresult rv
= NS_OK
;
1995 // adjusted for correct numeral shaping
1996 uint32_t bidiOptions
= aPresContext
->GetBidi();
1997 switch (GET_BIDI_OPTION_NUMERAL(bidiOptions
)) {
1998 case IBMBIDI_NUMERAL_HINDI
:
1999 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2002 case IBMBIDI_NUMERAL_ARABIC
:
2003 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2006 case IBMBIDI_NUMERAL_PERSIAN
:
2007 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_PERSIAN
);
2010 case IBMBIDI_NUMERAL_REGULAR
:
2012 switch (aBidiClass
) {
2013 case intl::BidiClass::EuropeanNumber
:
2014 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2017 case intl::BidiClass::ArabicNumber
:
2018 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2026 case IBMBIDI_NUMERAL_HINDICONTEXT
:
2027 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions
) ==
2028 IBMBIDI_TEXTDIRECTION_RTL
) &&
2029 (IS_ARABIC_DIGIT(aText
[0]))) ||
2030 (intl::BidiClass::ArabicNumber
== aBidiClass
)) {
2031 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2032 } else if (intl::BidiClass::EuropeanNumber
== aBidiClass
) {
2033 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2037 case IBMBIDI_NUMERAL_PERSIANCONTEXT
:
2038 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions
) ==
2039 IBMBIDI_TEXTDIRECTION_RTL
) &&
2040 (IS_ARABIC_DIGIT(aText
[0]))) ||
2041 (intl::BidiClass::ArabicNumber
== aBidiClass
)) {
2042 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_PERSIAN
);
2043 } else if (intl::BidiClass::EuropeanNumber
== aBidiClass
) {
2044 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2048 case IBMBIDI_NUMERAL_NOMINAL
:
2053 StripBidiControlCharacters(aText
, aTextLength
);
2057 void nsBidiPresUtils::StripBidiControlCharacters(char16_t
* aText
,
2058 int32_t& aTextLength
) {
2059 if ((nullptr == aText
) || (aTextLength
< 1)) {
2063 int32_t stripLen
= 0;
2065 for (int32_t i
= 0; i
< aTextLength
; i
++) {
2066 // XXX: This silently ignores surrogate characters.
2067 // As of Unicode 4.0, all Bidi control characters are within the BMP.
2068 if (IsBidiControl((uint32_t)aText
[i
])) {
2071 aText
[i
- stripLen
] = aText
[i
];
2074 aTextLength
-= stripLen
;
2077 void nsBidiPresUtils::CalculateBidiClass(
2078 intl::Bidi
* aBidiEngine
, const char16_t
* aText
, int32_t& aOffset
,
2079 int32_t aBidiClassLimit
, int32_t& aRunLimit
, int32_t& aRunLength
,
2080 int32_t& aRunCount
, intl::BidiClass
& aBidiClass
,
2081 intl::BidiClass
& aPrevBidiClass
) {
2082 bool strongTypeFound
= false;
2084 intl::BidiClass bidiClass
;
2086 aBidiClass
= intl::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
= intl::BidiClass::RightToLeft
;
2097 } else if (IS_ARABIC_ALPHABETIC(ch
)) {
2098 bidiClass
= intl::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 ((intl::BidiClass::RightToLeftArabic
== aPrevBidiClass
||
2122 intl::BidiClass::ArabicNumber
== aPrevBidiClass
) &&
2123 intl::BidiClass::EuropeanNumber
== bidiClass
) {
2124 bidiClass
= intl::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 mozilla::intl::Bidi
* aBidiEngine
) {
2145 NS_ASSERTION((aPosResolve
== nullptr) != (aPosResolveCount
> 0),
2146 "Incorrect aPosResolve / aPosResolveCount arguments");
2148 nsAutoString
textBuffer(aText
, aLength
);
2149 textBuffer
.ReplaceChar(kSeparators
, kSpace
);
2150 const char16_t
* text
= textBuffer
.get();
2152 if (aBidiEngine
->SetParagraph(Span(text
, aLength
), aBaseLevel
).isErr()) {
2153 return NS_ERROR_FAILURE
;
2156 auto result
= aBidiEngine
->CountRuns();
2157 if (result
.isErr()) {
2158 return NS_ERROR_FAILURE
;
2160 int32_t runCount
= result
.unwrap();
2162 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2163 aPosResolve
[nPosResolve
].visualIndex
= kNotFound
;
2164 aPosResolve
[nPosResolve
].visualLeftTwips
= kNotFound
;
2165 aPosResolve
[nPosResolve
].visualWidth
= kNotFound
;
2168 // For single-char string, use a simplified path as it cannot have multiple
2169 // direction or bidi-class runs.
2170 if (runCount
== 1 &&
2172 (aLength
== 2 && NS_IS_SURROGATE_PAIR(aText
[0], aText
[1])))) {
2173 ProcessOneChar(aText
, aLength
, aBaseLevel
, aPresContext
, aprocessor
, aMode
,
2174 aPosResolve
, aPosResolveCount
, aWidth
, aBidiEngine
);
2178 nscoord xOffset
= 0;
2179 nscoord width
, xEndRun
= 0;
2180 nscoord totalWidth
= 0;
2181 int32_t i
, start
, limit
, length
;
2182 uint32_t visualStart
= 0;
2183 intl::BidiClass bidiClass
;
2184 intl::BidiClass prevClass
= intl::BidiClass::LeftToRight
;
2186 for (i
= 0; i
< runCount
; i
++) {
2187 mozilla::intl::BidiDirection dir
=
2188 aBidiEngine
->GetVisualRun(i
, &start
, &length
);
2190 BidiEmbeddingLevel level
;
2191 aBidiEngine
->GetLogicalRun(start
, &limit
, &level
);
2193 dir
= level
.Direction();
2194 int32_t subRunLength
= limit
- start
;
2195 int32_t lineOffset
= start
;
2196 int32_t typeLimit
= std::min(limit
, AssertedCast
<int32_t>(aLength
));
2197 int32_t subRunCount
= 1;
2198 int32_t subRunLimit
= typeLimit
;
2201 * If |level| is even, i.e. the direction of the run is left-to-right, we
2202 * render the subruns from left to right and increment the x-coordinate
2203 * |xOffset| by the width of each subrun after rendering.
2205 * If |level| is odd, i.e. the direction of the run is right-to-left, we
2206 * render the subruns from right to left. We begin by incrementing |xOffset|
2207 * by the width of the whole run, and then decrement it by the width of each
2208 * subrun before rendering. After rendering all the subruns, we restore the
2209 * x-coordinate of the end of the run for the start of the next run.
2212 if (dir
== intl::BidiDirection::RTL
) {
2213 aprocessor
.SetText(text
+ start
, subRunLength
, intl::BidiDirection::RTL
);
2214 width
= aprocessor
.GetWidth();
2219 while (subRunCount
> 0) {
2220 // CalculateBidiClass can increment subRunCount if the run
2221 // contains mixed character types
2222 CalculateBidiClass(aBidiEngine
, text
, lineOffset
, typeLimit
, subRunLimit
,
2223 subRunLength
, subRunCount
, bidiClass
, prevClass
);
2225 nsAutoString runVisualText
;
2226 runVisualText
.Assign(text
+ 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
== mozilla::intl::BidiDirection::RTL
) {
2238 if (aMode
== MODE_DRAW
) {
2239 aprocessor
.DrawText(xOffset
, width
);
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
== mozilla::intl::BidiDirection::RTL
) {
2306 // One day, son, this could all be replaced with
2307 // mPresContext->GetBidiEngine().GetVisualIndex() ...
2308 posResolve
->visualIndex
=
2310 (subRunLength
- (posResolve
->logicalIndex
+ 1 - start
));
2311 // Skipping to the "left part".
2312 visualLeftPart
= text
+ 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
= text
+ 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
== intl::BidiDirection::LTR
) {
2340 subRunLimit
= typeLimit
;
2341 subRunLength
= typeLimit
- lineOffset
;
2343 if (dir
== intl::BidiDirection::RTL
) {
2347 visualStart
+= length
;
2351 *aWidth
= totalWidth
;
2356 void nsBidiPresUtils::ProcessOneChar(const char16_t
* aText
, size_t aLength
,
2357 BidiEmbeddingLevel aBaseLevel
,
2358 nsPresContext
* aPresContext
,
2359 BidiProcessor
& aprocessor
, Mode aMode
,
2360 nsBidiPositionResolve
* aPosResolve
,
2361 int32_t aPosResolveCount
, nscoord
* aWidth
,
2362 mozilla::intl::Bidi
* aBidiEngine
) {
2364 int32_t start
= 0, limit
;
2365 intl::BidiClass bidiClass
;
2366 intl::BidiClass prevClass
= intl::BidiClass::LeftToRight
;
2368 BidiEmbeddingLevel level
;
2369 aBidiEngine
->GetLogicalRun(0, &limit
, &level
);
2370 MOZ_ASSERT(limit
== int32_t(aLength
), "cannot have multiple bidi runs");
2372 int32_t subRunLength
= aLength
;
2373 int32_t subRunCount
= 1;
2374 CalculateBidiClass(aBidiEngine
, aText
, start
, aLength
, limit
, subRunLength
,
2375 subRunCount
, bidiClass
, prevClass
);
2376 MOZ_ASSERT(subRunCount
== 1, "cannot split single-character run");
2378 nsAutoString runVisualText
;
2379 runVisualText
.Assign(aText
, aLength
);
2381 FormatUnicodeText(aPresContext
, runVisualText
.BeginWriting(), subRunLength
,
2385 mozilla::intl::BidiDirection dir
= level
.Direction();
2386 aprocessor
.SetText(runVisualText
.get(), aLength
, dir
);
2387 width
= aprocessor
.GetWidth();
2389 if (aMode
== MODE_DRAW
) {
2390 aprocessor
.DrawText(0, width
);
2393 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2394 nsBidiPositionResolve
* posResolve
= &aPosResolve
[nPosResolve
];
2395 if (posResolve
->visualLeftTwips
!= kNotFound
) {
2398 if (0 <= posResolve
->logicalIndex
&&
2399 subRunLength
> posResolve
->logicalIndex
) {
2400 posResolve
->visualIndex
= 0;
2401 posResolve
->visualLeftTwips
= 0;
2402 posResolve
->visualWidth
= width
;
2411 class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final
2412 : public nsBidiPresUtils::BidiProcessor
{
2414 typedef mozilla::gfx::DrawTarget DrawTarget
;
2416 nsIRenderingContextBidiProcessor(gfxContext
* aCtx
,
2417 DrawTarget
* aTextRunConstructionDrawTarget
,
2418 nsFontMetrics
* aFontMetrics
,
2421 mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget
),
2422 mFontMetrics(aFontMetrics
),
2427 ~nsIRenderingContextBidiProcessor() { mFontMetrics
->SetTextRunRTL(false); }
2429 virtual void SetText(const char16_t
* aText
, int32_t aLength
,
2430 intl::BidiDirection aDirection
) override
{
2431 mFontMetrics
->SetTextRunRTL(aDirection
== intl::BidiDirection::RTL
);
2436 virtual nscoord
GetWidth() override
{
2437 return nsLayoutUtils::AppUnitWidthOfString(mText
, mLength
, *mFontMetrics
,
2438 mTextRunConstructionDrawTarget
);
2441 virtual void DrawText(nscoord aIOffset
, nscoord
) override
{
2443 if (mFontMetrics
->GetVertical()) {
2448 mFontMetrics
->DrawString(mText
, mLength
, pt
.x
, pt
.y
, mCtx
,
2449 mTextRunConstructionDrawTarget
);
2454 DrawTarget
* mTextRunConstructionDrawTarget
;
2455 nsFontMetrics
* mFontMetrics
;
2457 const char16_t
* mText
;
2461 nsresult
nsBidiPresUtils::ProcessTextForRenderingContext(
2462 const char16_t
* aText
, int32_t aLength
, BidiEmbeddingLevel aBaseLevel
,
2463 nsPresContext
* aPresContext
, gfxContext
& aRenderingContext
,
2464 DrawTarget
* aTextRunConstructionDrawTarget
, nsFontMetrics
& aFontMetrics
,
2465 Mode aMode
, nscoord aX
, nscoord aY
, nsBidiPositionResolve
* aPosResolve
,
2466 int32_t aPosResolveCount
, nscoord
* aWidth
) {
2467 nsIRenderingContextBidiProcessor
processor(&aRenderingContext
,
2468 aTextRunConstructionDrawTarget
,
2469 &aFontMetrics
, nsPoint(aX
, aY
));
2470 return ProcessText(aText
, aLength
, aBaseLevel
, aPresContext
, processor
, aMode
,
2471 aPosResolve
, aPosResolveCount
, aWidth
,
2472 &aPresContext
->GetBidiEngine());
2476 BidiEmbeddingLevel
nsBidiPresUtils::BidiLevelFromStyle(
2477 ComputedStyle
* aComputedStyle
) {
2478 if (aComputedStyle
->StyleTextReset()->mUnicodeBidi
==
2479 StyleUnicodeBidi::Plaintext
) {
2480 return BidiEmbeddingLevel::DefaultLTR();
2483 if (aComputedStyle
->StyleVisibility()->mDirection
== StyleDirection::Rtl
) {
2484 return BidiEmbeddingLevel::RTL();
2487 return BidiEmbeddingLevel::LTR();