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 kNextLine
= 0x0085;
51 static const char16_t kZWSP
= 0x200B;
52 static const char16_t kLineSeparator
= 0x2028;
53 static const char16_t kParagraphSeparator
= 0x2029;
54 static const char16_t kObjectSubstitute
= 0xFFFC;
55 static const char16_t kLRE
= 0x202A;
56 static const char16_t kRLE
= 0x202B;
57 static const char16_t kLRO
= 0x202D;
58 static const char16_t kRLO
= 0x202E;
59 static const char16_t kPDF
= 0x202C;
60 static const char16_t kLRI
= 0x2066;
61 static const char16_t kRLI
= 0x2067;
62 static const char16_t kFSI
= 0x2068;
63 static const char16_t kPDI
= 0x2069;
64 // All characters with Bidi type Segment Separator or Block Separator.
65 // This should be kept in sync with the table in ReplaceSeparators.
66 static const char16_t kSeparators
[] = {
67 char16_t('\t'), char16_t('\r'), char16_t('\n'), char16_t(0xb),
68 char16_t(0x1c), char16_t(0x1d), char16_t(0x1e), char16_t(0x1f),
69 kNextLine
, kParagraphSeparator
, char16_t(0)};
71 #define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1)
73 // This exists just to be a type; the value doesn't matter.
74 enum class BidiControlFrameType
{ Value
};
76 static bool IsIsolateControl(char16_t aChar
) {
77 return aChar
== kLRI
|| aChar
== kRLI
|| aChar
== kFSI
;
80 // Given a ComputedStyle, return any bidi control character necessary to
81 // implement style properties that override directionality (i.e. if it has
82 // unicode-bidi:bidi-override, or text-orientation:upright in vertical
83 // writing mode) when applying the bidi algorithm.
85 // Returns 0 if no override control character is implied by this style.
86 static char16_t
GetBidiOverride(ComputedStyle
* aComputedStyle
) {
87 const nsStyleVisibility
* vis
= aComputedStyle
->StyleVisibility();
88 if ((vis
->mWritingMode
== StyleWritingModeProperty::VerticalRl
||
89 vis
->mWritingMode
== StyleWritingModeProperty::VerticalLr
) &&
90 vis
->mTextOrientation
== StyleTextOrientation::Upright
) {
93 const nsStyleTextReset
* text
= aComputedStyle
->StyleTextReset();
94 if (text
->mUnicodeBidi
== StyleUnicodeBidi::BidiOverride
||
95 text
->mUnicodeBidi
== StyleUnicodeBidi::IsolateOverride
) {
96 return StyleDirection::Rtl
== vis
->mDirection
? kRLO
: kLRO
;
101 // Given a ComputedStyle, return any bidi control character necessary to
102 // implement style properties that affect bidi resolution (i.e. if it
103 // has unicode-bidiembed, isolate, or plaintext) when applying the bidi
106 // Returns 0 if no control character is implied by the style.
108 // Note that GetBidiOverride and GetBidiControl need to be separate
109 // because in the case of unicode-bidi:isolate-override we need both
111 static char16_t
GetBidiControl(ComputedStyle
* aComputedStyle
) {
112 const nsStyleVisibility
* vis
= aComputedStyle
->StyleVisibility();
113 const nsStyleTextReset
* text
= aComputedStyle
->StyleTextReset();
114 switch (text
->mUnicodeBidi
) {
115 case StyleUnicodeBidi::Embed
:
116 return StyleDirection::Rtl
== vis
->mDirection
? kRLE
: kLRE
;
117 case StyleUnicodeBidi::Isolate
:
118 // <bdi> element already has its directionality set from content so
119 // we never need to return kFSI.
120 return StyleDirection::Rtl
== vis
->mDirection
? kRLI
: kLRI
;
121 case StyleUnicodeBidi::IsolateOverride
:
122 case StyleUnicodeBidi::Plaintext
:
124 case StyleUnicodeBidi::Normal
:
125 case StyleUnicodeBidi::BidiOverride
:
133 static inline bool AreContinuationsInOrder(nsIFrame
* aFrame1
,
135 nsIFrame
* f
= aFrame1
;
137 f
= f
->GetNextContinuation();
138 } while (f
&& f
!= aFrame2
);
143 struct MOZ_STACK_CLASS BidiParagraphData
{
145 FrameInfo(nsIFrame
* aFrame
, nsBlockInFlowLineIterator
& aLineIter
)
147 mBlockContainer(aLineIter
.GetContainer()),
148 mInOverflow(aLineIter
.GetInOverflow()) {}
150 explicit FrameInfo(BidiControlFrameType aValue
)
151 : mFrame(NS_BIDI_CONTROL_FRAME
),
152 mBlockContainer(nullptr),
153 mInOverflow(false) {}
156 : mFrame(nullptr), mBlockContainer(nullptr), mInOverflow(false) {}
160 // The block containing mFrame (i.e., which continuation).
161 nsBlockFrame
* mBlockContainer
;
163 // true if mFrame is in mBlockContainer's overflow lines, false if
168 nsAutoString mBuffer
;
169 AutoTArray
<char16_t
, 16> mEmbeddingStack
;
170 AutoTArray
<FrameInfo
, 16> mLogicalFrames
;
171 nsTHashMap
<nsPtrHashKey
<const nsIContent
>, int32_t> mContentToFrameIndex
;
172 // Cached presentation context for the frames we're processing.
173 nsPresContext
* mPresContext
;
176 BidiEmbeddingLevel mParaLevel
;
177 nsIContent
* mPrevContent
;
180 * This class is designed to manage the process of mapping a frame to
181 * the line that it's in, when we know that (a) the frames we ask it
182 * about are always in the block's lines and (b) each successive frame
183 * we ask it about is the same as or after (in depth-first search
184 * order) the previous.
186 * Since we move through the lines at a different pace in Traverse and
187 * ResolveParagraph, we use one of these for each.
189 * The state of the mapping is also different between TraverseFrames
190 * and ResolveParagraph since since resolving can call functions
191 * (EnsureBidiContinuation or SplitInlineAncestors) that can create
192 * new frames and thus break lines.
194 * The TraverseFrames iterator is only used in some edge cases.
196 struct FastLineIterator
{
197 FastLineIterator() : mPrevFrame(nullptr), mNextLineStart(nullptr) {}
199 // These iterators *and* mPrevFrame track the line list that we're
202 // mPrevFrame, if non-null, should be either the frame we're currently
203 // handling (in ResolveParagraph or TraverseFrames, depending on the
204 // iterator) or a frame before it, and is also guaranteed to either be in
205 // mCurrentLine or have been in mCurrentLine until recently.
207 // In case the splitting causes block frames to break lines, however, we
208 // also track the first frame of the next line. If that changes, it means
209 // we've broken lines and we have to invalidate mPrevFrame.
210 nsBlockInFlowLineIterator mLineIterator
;
211 nsIFrame
* mPrevFrame
;
212 nsIFrame
* mNextLineStart
;
214 nsLineList::iterator
GetLine() { return mLineIterator
.GetLine(); }
216 static bool IsFrameInCurrentLine(nsBlockInFlowLineIterator
* aLineIter
,
217 nsIFrame
* aPrevFrame
, nsIFrame
* aFrame
) {
218 MOZ_ASSERT(!aPrevFrame
|| aLineIter
->GetLine()->Contains(aPrevFrame
),
219 "aPrevFrame must be in aLineIter's current line");
220 nsIFrame
* endFrame
= aLineIter
->IsLastLineInList()
222 : aLineIter
->GetLine().next()->mFirstChild
;
223 nsIFrame
* startFrame
=
224 aPrevFrame
? aPrevFrame
: aLineIter
->GetLine()->mFirstChild
;
225 for (nsIFrame
* frame
= startFrame
; frame
&& frame
!= endFrame
;
226 frame
= frame
->GetNextSibling()) {
227 if (frame
== aFrame
) return true;
232 static nsIFrame
* FirstChildOfNextLine(
233 nsBlockInFlowLineIterator
& aIterator
) {
234 const nsLineList::iterator line
= aIterator
.GetLine();
235 const nsLineList::iterator lineEnd
= aIterator
.End();
236 MOZ_ASSERT(line
!= lineEnd
, "iterator should start off valid");
237 const nsLineList::iterator nextLine
= line
.next();
239 return nextLine
!= lineEnd
? nextLine
->mFirstChild
: nullptr;
242 // Advance line iterator to the line containing aFrame, assuming
243 // that aFrame is already in the line list our iterator is iterating
245 void AdvanceToFrame(nsIFrame
* aFrame
) {
246 if (mPrevFrame
&& FirstChildOfNextLine(mLineIterator
) != mNextLineStart
) {
247 // Something has caused a line to split. We need to invalidate
248 // mPrevFrame since it may now be in a *later* line, though it may
249 // still be in this line, so we need to start searching for it from
250 // the start of this line.
251 mPrevFrame
= nullptr;
253 nsIFrame
* child
= aFrame
;
254 nsIFrame
* parent
= nsLayoutUtils::GetParentOrPlaceholderFor(child
);
255 while (parent
&& !parent
->IsBlockFrameOrSubclass()) {
257 parent
= nsLayoutUtils::GetParentOrPlaceholderFor(child
);
259 MOZ_ASSERT(parent
, "aFrame is not a descendent of a block frame");
260 while (!IsFrameInCurrentLine(&mLineIterator
, mPrevFrame
, child
)) {
264 mLineIterator
.Next();
265 MOZ_ASSERT(hasNext
, "Can't find frame in lines!");
266 mPrevFrame
= nullptr;
269 mNextLineStart
= FirstChildOfNextLine(mLineIterator
);
272 // Advance line iterator to the line containing aFrame, which may
273 // require moving forward into overflow lines or into a later
274 // continuation (or both).
275 void AdvanceToLinesAndFrame(const FrameInfo
& aFrameInfo
) {
276 if (mLineIterator
.GetContainer() != aFrameInfo
.mBlockContainer
||
277 mLineIterator
.GetInOverflow() != aFrameInfo
.mInOverflow
) {
279 mLineIterator
.GetContainer() == aFrameInfo
.mBlockContainer
280 ? (!mLineIterator
.GetInOverflow() && aFrameInfo
.mInOverflow
)
281 : (!mLineIterator
.GetContainer() ||
282 AreContinuationsInOrder(mLineIterator
.GetContainer(),
283 aFrameInfo
.mBlockContainer
)),
284 "must move forwards");
285 nsBlockFrame
* block
= aFrameInfo
.mBlockContainer
;
286 nsLineList::iterator lines
=
287 aFrameInfo
.mInOverflow
? block
->GetOverflowLines()->mLines
.begin()
288 : block
->LinesBegin();
290 nsBlockInFlowLineIterator(block
, lines
, aFrameInfo
.mInOverflow
);
291 mPrevFrame
= nullptr;
293 AdvanceToFrame(aFrameInfo
.mFrame
);
297 FastLineIterator mCurrentTraverseLine
, mCurrentResolveLine
;
300 // Only used for NOISY debug output.
301 // Matches the current TraverseFrames state, not the ResolveParagraph
303 nsBlockFrame
* mCurrentBlock
;
306 explicit BidiParagraphData(nsBlockFrame
* aBlockFrame
)
307 : mPresContext(aBlockFrame
->PresContext()),
308 mIsVisual(mPresContext
->IsVisualMode()),
309 mRequiresBidi(false),
310 mParaLevel(nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame
->Style())),
311 mPrevContent(nullptr)
314 mCurrentBlock(aBlockFrame
)
317 if (mParaLevel
> 0) {
318 mRequiresBidi
= true;
323 * Drill up in content to detect whether this is an element that needs to
324 * be rendered with logical order even on visual pages.
326 * We always use logical order on form controls, firstly so that text
327 * entry will be in logical order, but also because visual pages were
328 * written with the assumption that even if the browser had no support
329 * for right-to-left text rendering, it would use native widgets with
330 * bidi support to display form controls.
332 * We also use logical order in XUL elements, since we expect that if a
333 * XUL element appears in a visual page, it will be generated by an XBL
334 * binding and contain localized text which will be in logical order.
336 for (nsIContent
* content
= aBlockFrame
->GetContent(); content
;
337 content
= content
->GetParent()) {
338 if (content
->IsXULElement() || content
->IsHTMLFormControlElement()) {
347 if (mPresContext
->BidiEngine().SetParagraph(mBuffer
, mParaLevel
).isErr()) {
348 return NS_ERROR_FAILURE
;
354 * mParaLevel can be BidiDirection::LTR as well as
355 * BidiDirection::LTR or BidiDirection::RTL.
356 * GetParagraphEmbeddingLevel() returns the actual (resolved) paragraph level
357 * which is always either BidiDirection::LTR or
360 BidiEmbeddingLevel
GetParagraphEmbeddingLevel() {
361 BidiEmbeddingLevel paraLevel
= mParaLevel
;
362 if (paraLevel
== BidiEmbeddingLevel::DefaultLTR() ||
363 paraLevel
== BidiEmbeddingLevel::DefaultRTL()) {
364 paraLevel
= mPresContext
->BidiEngine().GetParagraphEmbeddingLevel();
369 BidiEngine::ParagraphDirection
GetParagraphDirection() {
370 return mPresContext
->BidiEngine().GetParagraphDirection();
373 nsresult
CountRuns(int32_t* runCount
) {
374 auto result
= mPresContext
->BidiEngine().CountRuns();
375 if (result
.isErr()) {
376 return NS_ERROR_FAILURE
;
378 *runCount
= result
.unwrap();
382 void GetLogicalRun(int32_t aLogicalStart
, int32_t* aLogicalLimit
,
383 BidiEmbeddingLevel
* aLevel
) {
384 mPresContext
->BidiEngine().GetLogicalRun(aLogicalStart
, aLogicalLimit
,
387 *aLevel
= GetParagraphEmbeddingLevel();
392 mLogicalFrames
.Clear();
393 mContentToFrameIndex
.Clear();
394 mBuffer
.SetLength(0);
395 mPrevContent
= nullptr;
396 for (uint32_t i
= 0; i
< mEmbeddingStack
.Length(); ++i
) {
397 mBuffer
.Append(mEmbeddingStack
[i
]);
398 mLogicalFrames
.AppendElement(FrameInfo(BidiControlFrameType::Value
));
402 void AppendFrame(nsIFrame
* aFrame
, FastLineIterator
& aLineIter
,
403 nsIContent
* aContent
= nullptr) {
405 mContentToFrameIndex
.InsertOrUpdate(aContent
, FrameCount());
408 // We don't actually need to advance aLineIter to aFrame, since all we use
409 // from it is the block and is-overflow state, which are correct already.
410 mLogicalFrames
.AppendElement(FrameInfo(aFrame
, aLineIter
.mLineIterator
));
413 void AdvanceAndAppendFrame(nsIFrame
** aFrame
, FastLineIterator
& aLineIter
,
414 nsIFrame
** aNextSibling
) {
415 nsIFrame
* frame
= *aFrame
;
416 nsIFrame
* nextSibling
= *aNextSibling
;
418 frame
= frame
->GetNextContinuation();
420 AppendFrame(frame
, aLineIter
, nullptr);
423 * If we have already overshot the saved next-sibling while
424 * scanning the frame's continuations, advance it.
426 if (frame
== nextSibling
) {
427 nextSibling
= frame
->GetNextSibling();
432 *aNextSibling
= nextSibling
;
435 int32_t GetLastFrameForContent(nsIContent
* aContent
) {
436 return mContentToFrameIndex
.Get(aContent
);
439 int32_t FrameCount() { return mLogicalFrames
.Length(); }
441 int32_t BufferLength() { return mBuffer
.Length(); }
443 nsIFrame
* FrameAt(int32_t aIndex
) { return mLogicalFrames
[aIndex
].mFrame
; }
445 const FrameInfo
& FrameInfoAt(int32_t aIndex
) {
446 return mLogicalFrames
[aIndex
];
449 void AppendUnichar(char16_t aCh
) { mBuffer
.Append(aCh
); }
451 void AppendString(const nsDependentSubstring
& aString
) {
452 mBuffer
.Append(aString
);
455 void AppendControlChar(char16_t aCh
) {
456 mLogicalFrames
.AppendElement(FrameInfo(BidiControlFrameType::Value
));
460 void PushBidiControl(char16_t aCh
) {
461 AppendControlChar(aCh
);
462 mEmbeddingStack
.AppendElement(aCh
);
465 void AppendPopChar(char16_t aCh
) {
466 AppendControlChar(IsIsolateControl(aCh
) ? kPDI
: kPDF
);
469 void PopBidiControl(char16_t aCh
) {
470 MOZ_ASSERT(mEmbeddingStack
.Length(), "embedding/override underflow");
471 MOZ_ASSERT(aCh
== mEmbeddingStack
.LastElement());
473 mEmbeddingStack
.RemoveLastElement();
476 void ClearBidiControls() {
477 for (char16_t c
: Reversed(mEmbeddingStack
)) {
483 class MOZ_STACK_CLASS BidiLineData
{
485 BidiLineData(nsIFrame
* aFirstFrameOnLine
, int32_t aNumFramesOnLine
) {
486 // Initialize the logically-ordered array of frames using the top-level
487 // frames of a single line
488 auto appendFrame
= [&](nsIFrame
* frame
, BidiEmbeddingLevel level
) {
489 mLogicalFrames
.AppendElement(frame
);
490 mLevels
.AppendElement(level
);
491 mIndexMap
.AppendElement(0);
494 for (nsIFrame
* frame
= aFirstFrameOnLine
; frame
&& aNumFramesOnLine
--;
495 frame
= frame
->GetNextSibling()) {
496 FrameBidiData bidiData
= nsBidiPresUtils::GetFrameBidiData(frame
);
497 if (bidiData
.precedingControl
!= kBidiLevelNone
) {
498 appendFrame(NS_BIDI_CONTROL_FRAME
, bidiData
.precedingControl
);
500 appendFrame(frame
, bidiData
.embeddingLevel
);
504 BidiEngine::ReorderVisual(mLevels
.Elements(), mLevels
.Length(),
505 mIndexMap
.Elements());
507 // Collect the frames in visual order, omitting virtual controls
508 // and noting whether frames are reordered.
509 for (uint32_t i
= 0; i
< mIndexMap
.Length(); i
++) {
510 nsIFrame
* frame
= mLogicalFrames
[mIndexMap
[i
]];
511 if (frame
== NS_BIDI_CONTROL_FRAME
) {
514 mVisualFrameIndex
.AppendElement(mIndexMap
[i
]);
515 if (int32_t(i
) != mIndexMap
[i
]) {
521 uint32_t LogicalFrameCount() const { return mLogicalFrames
.Length(); }
522 uint32_t VisualFrameCount() const { return mVisualFrameIndex
.Length(); }
524 nsIFrame
* LogicalFrameAt(uint32_t aIndex
) const {
525 return mLogicalFrames
[aIndex
];
528 nsIFrame
* VisualFrameAt(uint32_t aIndex
) const {
529 return mLogicalFrames
[mVisualFrameIndex
[aIndex
]];
532 std::pair
<nsIFrame
*, BidiEmbeddingLevel
> VisualFrameAndLevelAt(
533 uint32_t aIndex
) const {
534 int32_t index
= mVisualFrameIndex
[aIndex
];
535 return std::pair(mLogicalFrames
[index
], mLevels
[index
]);
538 bool IsReordered() const { return mIsReordered
; }
540 void InitContinuationStates(nsContinuationStates
* aContinuationStates
) const {
541 for (auto* frame
: mLogicalFrames
) {
542 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
543 nsBidiPresUtils::InitContinuationStates(frame
, aContinuationStates
);
549 AutoTArray
<nsIFrame
*, 16> mLogicalFrames
;
550 AutoTArray
<int32_t, 16> mVisualFrameIndex
;
551 AutoTArray
<int32_t, 16> mIndexMap
;
552 AutoTArray
<BidiEmbeddingLevel
, 16> mLevels
;
553 bool mIsReordered
= false;
558 void MOZ_EXPORT
DumpBidiLine(BidiLineData
* aData
, bool aVisualOrder
) {
559 auto dump
= [](nsIFrame
* frame
) {
560 if (frame
== NS_BIDI_CONTROL_FRAME
) {
561 fprintf_stderr(stderr
, "(Bidi control frame)\n");
568 for (uint32_t i
= 0; i
< aData
->VisualFrameCount(); i
++) {
569 dump(aData
->VisualFrameAt(i
));
572 for (uint32_t i
= 0; i
< aData
->LogicalFrameCount(); i
++) {
573 dump(aData
->LogicalFrameAt(i
));
580 /* Some helper methods for Resolve() */
582 // Should this frame be split between text runs?
583 static bool IsBidiSplittable(nsIFrame
* aFrame
) {
585 // Bidi inline containers should be split, unless they're line frames.
586 LayoutFrameType frameType
= aFrame
->Type();
587 return (aFrame
->IsBidiInlineContainer() &&
588 frameType
!= LayoutFrameType::Line
) ||
589 frameType
== LayoutFrameType::Text
;
592 // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
593 static bool IsBidiLeaf(const nsIFrame
* aFrame
) {
594 nsIFrame
* kid
= aFrame
->PrincipalChildList().FirstChild();
596 if (aFrame
->IsBidiInlineContainer() ||
597 RubyUtils::IsRubyBox(aFrame
->Type())) {
605 * Create non-fluid continuations for the ancestors of a given frame all the way
606 * up the frame tree until we hit a non-splittable frame (a line or a block).
608 * @param aParent the first parent frame to be split
609 * @param aFrame the child frames after this frame are reparented to the
610 * newly-created continuation of aParent.
611 * If aFrame is null, all the children of aParent are reparented.
613 static void SplitInlineAncestors(nsContainerFrame
* aParent
,
614 nsLineList::iterator aLine
, nsIFrame
* aFrame
) {
615 PresShell
* presShell
= aParent
->PresShell();
616 nsIFrame
* frame
= aFrame
;
617 nsContainerFrame
* parent
= aParent
;
618 nsContainerFrame
* newParent
;
620 while (IsBidiSplittable(parent
)) {
621 nsContainerFrame
* grandparent
= parent
->GetParent();
622 NS_ASSERTION(grandparent
,
623 "Couldn't get parent's parent in "
624 "nsBidiPresUtils::SplitInlineAncestors");
626 // Split the child list after |frame|, unless it is the last child.
627 if (!frame
|| frame
->GetNextSibling()) {
628 newParent
= static_cast<nsContainerFrame
*>(
629 presShell
->FrameConstructor()->CreateContinuingFrame(
630 parent
, grandparent
, false));
632 nsFrameList tail
= parent
->StealFramesAfter(frame
);
634 // Reparent views as necessary
635 nsContainerFrame::ReparentFrameViewList(tail
, parent
, newParent
);
637 // The parent's continuation adopts the siblings after the split.
638 MOZ_ASSERT(!newParent
->IsBlockFrameOrSubclass(),
639 "blocks should not be IsBidiSplittable");
640 newParent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, nullptr,
641 nullptr, std::move(tail
));
643 // While passing &aLine to InsertFrames for a non-block isn't harmful
644 // because it's a no-op, it doesn't really make sense. However, the
645 // MOZ_ASSERT() we need to guarantee that it's safe only works if the
646 // parent is actually the block.
647 const nsLineList::iterator
* parentLine
;
648 if (grandparent
->IsBlockFrameOrSubclass()) {
649 MOZ_ASSERT(aLine
->Contains(parent
));
652 parentLine
= nullptr;
655 // The list name FrameChildListID::NoReflowPrincipal would indicate we
657 grandparent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, parent
,
658 parentLine
, nsFrameList(newParent
, newParent
));
662 parent
= grandparent
;
666 static void MakeContinuationFluid(nsIFrame
* aFrame
, nsIFrame
* aNext
) {
667 NS_ASSERTION(!aFrame
->GetNextInFlow() || aFrame
->GetNextInFlow() == aNext
,
668 "next-in-flow is not next continuation!");
669 aFrame
->SetNextInFlow(aNext
);
671 NS_ASSERTION(!aNext
->GetPrevInFlow() || aNext
->GetPrevInFlow() == aFrame
,
672 "prev-in-flow is not prev continuation!");
673 aNext
->SetPrevInFlow(aFrame
);
676 static void MakeContinuationsNonFluidUpParentChain(nsIFrame
* aFrame
,
681 for (frame
= aFrame
, next
= aNext
;
682 frame
&& next
&& next
!= frame
&& next
== frame
->GetNextInFlow() &&
683 IsBidiSplittable(frame
);
684 frame
= frame
->GetParent(), next
= next
->GetParent()) {
685 frame
->SetNextContinuation(next
);
686 next
->SetPrevContinuation(frame
);
690 // If aFrame is the last child of its parent, convert bidi continuations to
691 // fluid continuations for all of its inline ancestors.
692 // If it isn't the last child, make sure that its continuation is fluid.
693 static void JoinInlineAncestors(nsIFrame
* aFrame
) {
694 nsIFrame
* frame
= aFrame
;
695 while (frame
&& IsBidiSplittable(frame
)) {
696 nsIFrame
* next
= frame
->GetNextContinuation();
698 MakeContinuationFluid(frame
, next
);
700 // Join the parent only as long as we're its last child.
701 if (frame
->GetNextSibling()) break;
702 frame
= frame
->GetParent();
706 static void CreateContinuation(nsIFrame
* aFrame
,
707 const nsLineList::iterator aLine
,
708 nsIFrame
** aNewFrame
, bool aIsFluid
) {
709 MOZ_ASSERT(aNewFrame
, "null OUT ptr");
710 MOZ_ASSERT(aFrame
, "null ptr");
712 *aNewFrame
= nullptr;
714 nsPresContext
* presContext
= aFrame
->PresContext();
715 PresShell
* presShell
= presContext
->PresShell();
716 NS_ASSERTION(presShell
,
717 "PresShell must be set on PresContext before calling "
718 "nsBidiPresUtils::CreateContinuation");
720 nsContainerFrame
* parent
= aFrame
->GetParent();
723 "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
725 // While passing &aLine to InsertFrames for a non-block isn't harmful
726 // because it's a no-op, it doesn't really make sense. However, the
727 // MOZ_ASSERT() we need to guarantee that it's safe only works if the
728 // parent is actually the block.
729 const nsLineList::iterator
* parentLine
;
730 if (parent
->IsBlockFrameOrSubclass()) {
731 MOZ_ASSERT(aLine
->Contains(aFrame
));
734 parentLine
= nullptr;
737 // Have to special case floating first letter frames because the continuation
738 // doesn't go in the first letter frame. The continuation goes with the rest
739 // of the text that the first letter frame was made out of.
740 if (parent
->IsLetterFrame() && parent
->IsFloating()) {
741 nsFirstLetterFrame
* letterFrame
= do_QueryFrame(parent
);
742 letterFrame
->CreateContinuationForFloatingParent(aFrame
, aNewFrame
,
747 *aNewFrame
= presShell
->FrameConstructor()->CreateContinuingFrame(
748 aFrame
, parent
, aIsFluid
);
750 // The list name FrameChildListID::NoReflowPrincipal would indicate we don't
752 // XXXbz this needs higher-level framelist love
753 parent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, aFrame
, parentLine
,
754 nsFrameList(*aNewFrame
, *aNewFrame
));
757 // Split inline ancestor frames
758 SplitInlineAncestors(parent
, aLine
, aFrame
);
763 * Overview of the implementation of Resolve():
765 * Walk through the descendants of aBlockFrame and build:
766 * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
767 * * mBuffer: an nsString containing a representation of
768 * the content of the frames.
769 * In the case of text frames, this is the actual text context of the
770 * frames, but some other elements are represented in a symbolic form which
771 * will make the Unicode Bidi Algorithm give the correct results.
772 * Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo>
773 * elements are represented by the corresponding Unicode control characters.
774 * <br> elements are represented by U+2028 LINE SEPARATOR
775 * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
778 * Then pass mBuffer to the Bidi engine for resolving of embedding levels
779 * by nsBidi::SetPara() and division into directional runs by
780 * nsBidi::CountRuns().
782 * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
783 * correlate them with the frames indexed in mLogicalFrames, setting the
784 * baseLevel and embeddingLevel properties according to the results returned
785 * by the Bidi engine.
787 * The rendering layer requires each text frame to contain text in only one
788 * direction, so we may need to call EnsureBidiContinuation() to split frames.
789 * We may also need to call RemoveBidiContinuation() to convert frames created
790 * by EnsureBidiContinuation() in previous reflows into fluid continuations.
792 nsresult
nsBidiPresUtils::Resolve(nsBlockFrame
* aBlockFrame
) {
793 BidiParagraphData
bpd(aBlockFrame
);
795 // Handle bidi-override being set on the block itself before calling
797 // No need to call GetBidiControl as well, because isolate and embed
798 // values of unicode-bidi property are redundant on block elements.
799 // unicode-bidi:plaintext on a block element is handled by block frame
800 // via using nsIFrame::GetWritingMode(nsIFrame*).
801 char16_t ch
= GetBidiOverride(aBlockFrame
->Style());
803 bpd
.PushBidiControl(ch
);
804 bpd
.mRequiresBidi
= true;
806 // If there are no unicode-bidi properties and no RTL characters in the
807 // block's content, then it is pure LTR and we can skip the rest of bidi
809 nsIContent
* currContent
= nullptr;
810 for (nsBlockFrame
* block
= aBlockFrame
; block
;
811 block
= static_cast<nsBlockFrame
*>(block
->GetNextContinuation())) {
812 block
->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
813 if (!bpd
.mRequiresBidi
&&
814 ChildListMayRequireBidi(block
->PrincipalChildList().FirstChild(),
816 bpd
.mRequiresBidi
= true;
818 if (!bpd
.mRequiresBidi
) {
819 nsBlockFrame::FrameLines
* overflowLines
= block
->GetOverflowLines();
821 if (ChildListMayRequireBidi(overflowLines
->mFrames
.FirstChild(),
823 bpd
.mRequiresBidi
= true;
828 if (!bpd
.mRequiresBidi
) {
833 for (nsBlockFrame
* block
= aBlockFrame
; block
;
834 block
= static_cast<nsBlockFrame
*>(block
->GetNextContinuation())) {
836 bpd
.mCurrentBlock
= block
;
838 block
->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
839 bpd
.mCurrentTraverseLine
.mLineIterator
=
840 nsBlockInFlowLineIterator(block
, block
->LinesBegin());
841 bpd
.mCurrentTraverseLine
.mPrevFrame
= nullptr;
842 TraverseFrames(block
->PrincipalChildList().FirstChild(), &bpd
);
843 nsBlockFrame::FrameLines
* overflowLines
= block
->GetOverflowLines();
845 bpd
.mCurrentTraverseLine
.mLineIterator
=
846 nsBlockInFlowLineIterator(block
, overflowLines
->mLines
.begin(), true);
847 bpd
.mCurrentTraverseLine
.mPrevFrame
= nullptr;
848 TraverseFrames(overflowLines
->mFrames
.FirstChild(), &bpd
);
853 bpd
.PopBidiControl(ch
);
856 return ResolveParagraph(&bpd
);
859 // In ResolveParagraph, we previously used ReplaceChar(kSeparators, kSpace)
860 // to convert separators to spaces, but this hard-coded implementation is
861 // substantially faster than the general-purpose ReplaceChar function.
862 // This must be kept in sync with the definition of kSeparators.
863 static inline void ReplaceSeparators(nsString
& aText
, size_t aStartIndex
= 0) {
864 for (char16_t
* cp
= aText
.BeginWriting() + aStartIndex
;
865 cp
< aText
.EndWriting(); cp
++) {
866 if (MOZ_UNLIKELY(*cp
< char16_t(' '))) {
867 static constexpr char16_t SeparatorToSpace
[32] = {
868 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ' ', ' ',
869 ' ', 0x0c, ' ', 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
870 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, ' ', ' ', ' ', ' ',
872 *cp
= SeparatorToSpace
[*cp
];
873 } else if (MOZ_UNLIKELY(*cp
== kNextLine
|| *cp
== kParagraphSeparator
)) {
879 nsresult
nsBidiPresUtils::ResolveParagraph(BidiParagraphData
* aBpd
) {
880 if (aBpd
->BufferLength() < 1) {
884 ReplaceSeparators(aBpd
->mBuffer
);
888 nsresult rv
= aBpd
->SetPara();
889 NS_ENSURE_SUCCESS(rv
, rv
);
891 BidiEmbeddingLevel embeddingLevel
= aBpd
->GetParagraphEmbeddingLevel();
893 rv
= aBpd
->CountRuns(&runCount
);
894 NS_ENSURE_SUCCESS(rv
, rv
);
896 int32_t runLength
= 0; // the length of the current run of text
897 int32_t logicalLimit
= 0; // the end of the current run + 1
899 int32_t fragmentLength
= 0; // the length of the current text frame
900 int32_t frameIndex
= -1; // index to the frames in mLogicalFrames
901 int32_t frameCount
= aBpd
->FrameCount();
902 int32_t contentOffset
= 0; // offset of current frame in its content node
903 bool isTextFrame
= false;
904 nsIFrame
* frame
= nullptr;
905 BidiParagraphData::FrameInfo frameInfo
;
906 nsIContent
* content
= nullptr;
907 int32_t contentTextLength
= 0;
912 "Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, "
914 (void*)aBpd
->mCurrentBlock
, NS_ConvertUTF16toUTF8(aBpd
->mBuffer
).get(),
915 frameCount
, runCount
);
916 # ifdef REALLY_NOISY_BIDI
917 printf(" block frame tree=:\n");
918 aBpd
->mCurrentBlock
->List(stdout
);
923 if (runCount
== 1 && frameCount
== 1 &&
924 aBpd
->GetParagraphDirection() == BidiEngine::ParagraphDirection::LTR
&&
925 aBpd
->GetParagraphEmbeddingLevel() == 0) {
926 // We have a single left-to-right frame in a left-to-right paragraph,
927 // without bidi isolation from the surrounding text.
928 // Make sure that the embedding level and base level frame properties aren't
929 // set (because if they are this frame used to have some other direction,
930 // so we can't do this optimization), and we're done.
931 nsIFrame
* frame
= aBpd
->FrameAt(0);
932 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
933 FrameBidiData bidiData
= frame
->GetBidiData();
934 if (!bidiData
.embeddingLevel
&& !bidiData
.baseLevel
) {
937 printf("early return for single direction frame %p\n", (void*)frame
);
940 frame
->AddStateBits(NS_FRAME_IS_BIDI
);
946 BidiParagraphData::FrameInfo lastRealFrame
;
947 BidiEmbeddingLevel lastEmbeddingLevel
= kBidiLevelNone
;
948 BidiEmbeddingLevel precedingControl
= kBidiLevelNone
;
950 auto storeBidiDataToFrame
= [&]() {
951 FrameBidiData bidiData
;
952 bidiData
.embeddingLevel
= embeddingLevel
;
953 bidiData
.baseLevel
= aBpd
->GetParagraphEmbeddingLevel();
954 // If a control character doesn't have a lower embedding level than
955 // both the preceding and the following frame, it isn't something
956 // needed for getting the correct result. This optimization should
957 // remove almost all of embeds and overrides, and some of isolates.
958 if (precedingControl
>= embeddingLevel
||
959 precedingControl
>= lastEmbeddingLevel
) {
960 bidiData
.precedingControl
= kBidiLevelNone
;
962 bidiData
.precedingControl
= precedingControl
;
964 precedingControl
= kBidiLevelNone
;
965 lastEmbeddingLevel
= embeddingLevel
;
966 frame
->SetProperty(nsIFrame::BidiDataProperty(), bidiData
);
970 if (fragmentLength
<= 0) {
971 // Get the next frame from mLogicalFrames
972 if (++frameIndex
>= frameCount
) {
975 frameInfo
= aBpd
->FrameInfoAt(frameIndex
);
976 frame
= frameInfo
.mFrame
;
977 if (frame
== NS_BIDI_CONTROL_FRAME
|| !frame
->IsTextFrame()) {
979 * Any non-text frame corresponds to a single character in the text
980 * buffer (a bidi control character, LINE SEPARATOR, or OBJECT
986 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(frameInfo
);
987 content
= frame
->GetContent();
992 contentTextLength
= content
->TextLength();
993 auto [start
, end
] = frame
->GetOffsets();
994 NS_ASSERTION(!(contentTextLength
< end
- start
),
995 "Frame offsets don't fit in content");
996 fragmentLength
= std::min(contentTextLength
, end
- start
);
997 contentOffset
= start
;
1000 } // if (fragmentLength <= 0)
1002 if (runLength
<= 0) {
1003 // Get the next run of text from the Bidi engine
1004 if (++numRun
>= runCount
) {
1005 // We've run out of runs of text; but don't forget to store bidi data
1006 // to the frame before breaking out of the loop (bug 1426042).
1007 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1008 storeBidiDataToFrame();
1010 frame
->AdjustOffsetsForBidi(contentOffset
,
1011 contentOffset
+ fragmentLength
);
1016 int32_t lineOffset
= logicalLimit
;
1017 aBpd
->GetLogicalRun(lineOffset
, &logicalLimit
, &embeddingLevel
);
1018 runLength
= logicalLimit
- lineOffset
;
1019 } // if (runLength <= 0)
1021 if (frame
== NS_BIDI_CONTROL_FRAME
) {
1022 // In theory, we only need to do this for isolates. However, it is
1023 // easier to do this for all here because we do not maintain the
1024 // index to get corresponding character from buffer. Since we do
1025 // have proper embedding level for all those characters, including
1026 // them wouldn't affect the final result.
1027 precedingControl
= std::min(precedingControl
, embeddingLevel
);
1029 storeBidiDataToFrame();
1031 if (contentTextLength
== 0) {
1032 // Set the base level and embedding level of the current run even
1033 // on an empty frame. Otherwise frame reordering will not be correct.
1034 frame
->AdjustOffsetsForBidi(0, 0);
1035 // Nothing more to do for an empty frame, except update
1036 // lastRealFrame like we do below.
1037 lastRealFrame
= frameInfo
;
1040 nsLineList::iterator currentLine
= aBpd
->mCurrentResolveLine
.GetLine();
1041 if ((runLength
> 0) && (runLength
< fragmentLength
)) {
1043 * The text in this frame continues beyond the end of this directional
1044 * run. Create a non-fluid continuation frame for the next directional
1047 currentLine
->MarkDirty();
1049 int32_t runEnd
= contentOffset
+ runLength
;
1050 EnsureBidiContinuation(frame
, currentLine
, &nextBidi
, contentOffset
,
1052 nextBidi
->AdjustOffsetsForBidi(runEnd
,
1053 contentOffset
+ fragmentLength
);
1055 frameInfo
.mFrame
= frame
;
1056 contentOffset
= runEnd
;
1058 aBpd
->mCurrentResolveLine
.AdvanceToFrame(frame
);
1059 } // if (runLength < fragmentLength)
1061 if (contentOffset
+ fragmentLength
== contentTextLength
) {
1063 * We have finished all the text in this content node. Convert any
1064 * further non-fluid continuations to fluid continuations and
1065 * advance frameIndex to the last frame in the content node
1067 int32_t newIndex
= aBpd
->GetLastFrameForContent(content
);
1068 if (newIndex
> frameIndex
) {
1069 currentLine
->MarkDirty();
1070 RemoveBidiContinuation(aBpd
, frame
, frameIndex
, newIndex
);
1071 frameIndex
= newIndex
;
1072 frameInfo
= aBpd
->FrameInfoAt(frameIndex
);
1073 frame
= frameInfo
.mFrame
;
1075 } else if (fragmentLength
> 0 && runLength
> fragmentLength
) {
1077 * There is more text that belongs to this directional run in the
1078 * next text frame: make sure it is a fluid continuation of the
1079 * current frame. Do not advance frameIndex, because the next frame
1080 * may contain multi-directional text and need to be split
1082 int32_t newIndex
= frameIndex
;
1084 } while (++newIndex
< frameCount
&&
1085 aBpd
->FrameAt(newIndex
) == NS_BIDI_CONTROL_FRAME
);
1086 if (newIndex
< frameCount
) {
1087 currentLine
->MarkDirty();
1088 RemoveBidiContinuation(aBpd
, frame
, frameIndex
, newIndex
);
1090 } else if (runLength
== fragmentLength
) {
1092 * If the directional run ends at the end of the frame, make sure
1093 * that any continuation is non-fluid, and do the same up the
1096 nsIFrame
* next
= frame
->GetNextInFlow();
1098 currentLine
->MarkDirty();
1099 MakeContinuationsNonFluidUpParentChain(frame
, next
);
1102 frame
->AdjustOffsetsForBidi(contentOffset
,
1103 contentOffset
+ fragmentLength
);
1106 } // not bidi control frame
1107 int32_t temp
= runLength
;
1108 runLength
-= fragmentLength
;
1109 fragmentLength
-= temp
;
1111 // Record last real frame so that we can do splitting properly even
1112 // if a run ends after a virtual bidi control frame.
1113 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1114 lastRealFrame
= frameInfo
;
1116 if (lastRealFrame
.mFrame
&& fragmentLength
<= 0) {
1117 // If the frame is at the end of a run, and this is not the end of our
1118 // paragraph, split all ancestor inlines that need splitting.
1119 // To determine whether we're at the end of the run, we check that we've
1120 // finished processing the current run, and that the current frame
1121 // doesn't have a fluid continuation (it could have a fluid continuation
1122 // of zero length, so testing runLength alone is not sufficient).
1123 if (runLength
<= 0 && !lastRealFrame
.mFrame
->GetNextInFlow()) {
1124 if (numRun
+ 1 < runCount
) {
1125 nsIFrame
* child
= lastRealFrame
.mFrame
;
1126 nsContainerFrame
* parent
= child
->GetParent();
1127 // As long as we're on the last sibling, the parent doesn't have to
1129 // However, if the parent has a fluid continuation, we do have to make
1130 // it non-fluid. This can happen e.g. when we have a first-letter
1131 // frame and the end of the first-letter coincides with the end of a
1133 while (parent
&& IsBidiSplittable(parent
) &&
1134 !child
->GetNextSibling()) {
1135 nsIFrame
* next
= parent
->GetNextInFlow();
1137 parent
->SetNextContinuation(next
);
1138 next
->SetPrevContinuation(parent
);
1141 parent
= child
->GetParent();
1143 if (parent
&& IsBidiSplittable(parent
)) {
1144 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(lastRealFrame
);
1145 SplitInlineAncestors(parent
, aBpd
->mCurrentResolveLine
.GetLine(),
1148 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(lastRealFrame
);
1151 } else if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1152 // We're not at an end of a run. If |frame| is the last child of its
1153 // parent, and its ancestors happen to have bidi continuations, convert
1154 // them into fluid continuations.
1155 JoinInlineAncestors(frame
);
1161 # ifdef REALLY_NOISY_BIDI
1162 printf("---\nAfter Resolve(), frameTree =:\n");
1163 aBpd
->mCurrentBlock
->List(stdout
);
1171 void nsBidiPresUtils::TraverseFrames(nsIFrame
* aCurrentFrame
,
1172 BidiParagraphData
* aBpd
) {
1173 if (!aCurrentFrame
) return;
1176 nsBlockFrame
* initialLineContainer
=
1177 aBpd
->mCurrentTraverseLine
.mLineIterator
.GetContainer();
1180 nsIFrame
* childFrame
= aCurrentFrame
;
1183 * It's important to get the next sibling and next continuation *before*
1184 * handling the frame: If we encounter a forced paragraph break and call
1185 * ResolveParagraph within this loop, doing GetNextSibling and
1186 * GetNextContinuation after that could return a bidi continuation that had
1187 * just been split from the original childFrame and we would process it
1190 nsIFrame
* nextSibling
= childFrame
->GetNextSibling();
1192 // If the real frame for a placeholder is a first letter frame, we need to
1193 // drill down into it and include its contents in Bidi resolution.
1194 // If not, we just use the placeholder.
1195 nsIFrame
* frame
= childFrame
;
1196 if (childFrame
->IsPlaceholderFrame()) {
1197 nsIFrame
* realFrame
=
1198 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame
);
1199 if (realFrame
->IsLetterFrame()) {
1204 auto DifferentBidiValues
= [](ComputedStyle
* aSC1
, nsIFrame
* aFrame2
) {
1205 ComputedStyle
* sc2
= aFrame2
->Style();
1206 return GetBidiControl(aSC1
) != GetBidiControl(sc2
) ||
1207 GetBidiOverride(aSC1
) != GetBidiOverride(sc2
);
1210 ComputedStyle
* sc
= frame
->Style();
1211 nsIFrame
* nextContinuation
= frame
->GetNextContinuation();
1212 nsIFrame
* prevContinuation
= frame
->GetPrevContinuation();
1214 !nextContinuation
|| DifferentBidiValues(sc
, nextContinuation
);
1216 !prevContinuation
|| DifferentBidiValues(sc
, prevContinuation
);
1218 char16_t controlChar
= 0;
1219 char16_t overrideChar
= 0;
1220 LayoutFrameType frameType
= frame
->Type();
1221 if (frame
->IsBidiInlineContainer() || RubyUtils::IsRubyBox(frameType
)) {
1222 if (!frame
->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
1223 nsContainerFrame
* c
= static_cast<nsContainerFrame
*>(frame
);
1224 MOZ_ASSERT(c
== do_QueryFrame(frame
),
1225 "eBidiInlineContainer and ruby frame must be"
1226 " a nsContainerFrame subclass");
1227 c
->DrainSelfOverflowList();
1230 controlChar
= GetBidiControl(sc
);
1231 overrideChar
= GetBidiOverride(sc
);
1233 // Add dummy frame pointers representing bidi control codes before
1234 // the first frames of elements specifying override, isolation, or
1237 if (controlChar
!= 0) {
1238 aBpd
->PushBidiControl(controlChar
);
1240 if (overrideChar
!= 0) {
1241 aBpd
->PushBidiControl(overrideChar
);
1246 if (IsBidiLeaf(frame
)) {
1247 /* Bidi leaf frame: add the frame to the mLogicalFrames array,
1248 * and add its index to the mContentToFrameIndex hashtable. This
1249 * will be used in RemoveBidiContinuation() to identify the last
1250 * frame in the array with a given content.
1252 nsIContent
* content
= frame
->GetContent();
1253 aBpd
->AppendFrame(frame
, aBpd
->mCurrentTraverseLine
, content
);
1255 // Append the content of the frame to the paragraph buffer
1256 if (LayoutFrameType::Text
== frameType
) {
1257 if (content
!= aBpd
->mPrevContent
) {
1258 aBpd
->mPrevContent
= content
;
1259 if (!frame
->StyleText()->NewlineIsSignificant(
1260 static_cast<nsTextFrame
*>(frame
))) {
1261 content
->GetAsText()->AppendTextTo(aBpd
->mBuffer
);
1264 * For preformatted text we have to do bidi resolution on each line
1268 content
->GetAsText()->AppendTextTo(text
);
1273 auto [start
, end
] = frame
->GetOffsets();
1274 int32_t endLine
= text
.FindChar('\n', start
);
1275 if (endLine
== -1) {
1277 * If there is no newline in the text content, just save the
1278 * text from this frame and its continuations, and do bidi
1281 aBpd
->AppendString(Substring(text
, start
));
1282 while (frame
&& nextSibling
) {
1283 aBpd
->AdvanceAndAppendFrame(
1284 &frame
, aBpd
->mCurrentTraverseLine
, &nextSibling
);
1290 * If there is a newline in the frame, break the frame after the
1291 * newline, do bidi resolution and repeat until the last sibling
1296 * If the frame ends before the new line, save the text and move
1297 * into the next continuation
1300 Substring(text
, start
, std::min(end
, endLine
) - start
));
1301 while (end
< endLine
&& nextSibling
) {
1302 aBpd
->AdvanceAndAppendFrame(&frame
, aBpd
->mCurrentTraverseLine
,
1304 NS_ASSERTION(frame
, "Premature end of continuation chain");
1305 std::tie(start
, end
) = frame
->GetOffsets();
1307 Substring(text
, start
, std::min(end
, endLine
) - start
));
1310 if (end
< endLine
) {
1311 aBpd
->mPrevContent
= nullptr;
1315 bool createdContinuation
= false;
1316 if (uint32_t(endLine
) < text
.Length()) {
1318 * Timing is everything here: if the frame already has a bidi
1319 * continuation, we need to make the continuation fluid *before*
1320 * resetting the length of the current frame. Otherwise
1321 * nsTextFrame::SetLength won't set the continuation frame's
1322 * text offsets correctly.
1324 * On the other hand, if the frame doesn't have a continuation,
1325 * we need to create one *after* resetting the length, or
1326 * CreateContinuingFrame will complain that there is no more
1327 * content for the continuation.
1329 next
= frame
->GetNextInFlow();
1331 // If the frame already has a bidi continuation, make it fluid
1332 next
= frame
->GetNextContinuation();
1334 MakeContinuationFluid(frame
, next
);
1335 JoinInlineAncestors(frame
);
1339 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
1340 textFrame
->SetLength(endLine
- start
, nullptr);
1342 // If it weren't for CreateContinuation needing this to
1343 // be current, we could restructure the marking dirty
1344 // below to use mCurrentResolveLine and eliminate
1345 // mCurrentTraverseLine entirely.
1346 aBpd
->mCurrentTraverseLine
.AdvanceToFrame(frame
);
1349 // If the frame has no next in flow, create one.
1351 frame
, aBpd
->mCurrentTraverseLine
.GetLine(), &next
, true);
1352 createdContinuation
= true;
1354 // Mark the line before the newline as dirty.
1355 aBpd
->mCurrentTraverseLine
.GetLine()->MarkDirty();
1357 ResolveParagraphWithinBlock(aBpd
);
1359 if (!nextSibling
&& !createdContinuation
) {
1364 aBpd
->AppendFrame(frame
, aBpd
->mCurrentTraverseLine
);
1365 // Mark the line after the newline as dirty.
1366 aBpd
->mCurrentTraverseLine
.AdvanceToFrame(frame
);
1367 aBpd
->mCurrentTraverseLine
.GetLine()->MarkDirty();
1371 * If we have already overshot the saved next-sibling while
1372 * scanning the frame's continuations, advance it.
1374 if (frame
&& frame
== nextSibling
) {
1375 nextSibling
= frame
->GetNextSibling();
1381 } else if (LayoutFrameType::Br
== frameType
) {
1382 // break frame -- append line separator
1383 aBpd
->AppendUnichar(kLineSeparator
);
1384 ResolveParagraphWithinBlock(aBpd
);
1386 // other frame type -- see the Unicode Bidi Algorithm:
1387 // "...inline objects (such as graphics) are treated as if they are ...
1389 // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
1390 // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1
1391 aBpd
->AppendUnichar(
1392 content
->IsHTMLElement(nsGkAtoms::wbr
) ? kZWSP
: kObjectSubstitute
);
1393 if (!frame
->IsInlineOutside()) {
1394 // if it is not inline, end the paragraph
1395 ResolveParagraphWithinBlock(aBpd
);
1399 // For a non-leaf frame, recurse into TraverseFrames
1400 nsIFrame
* kid
= frame
->PrincipalChildList().FirstChild();
1401 MOZ_ASSERT(!frame
->GetChildList(FrameChildListID::Overflow
).FirstChild(),
1402 "should have drained the overflow list above");
1404 TraverseFrames(kid
, aBpd
);
1408 // If the element is attributed by dir, indicate direction pop (add PDF
1411 // Add a dummy frame pointer representing a bidi control code after the
1412 // last frame of an element specifying embedding or override
1413 if (overrideChar
!= 0) {
1414 aBpd
->PopBidiControl(overrideChar
);
1416 if (controlChar
!= 0) {
1417 aBpd
->PopBidiControl(controlChar
);
1420 childFrame
= nextSibling
;
1421 } while (childFrame
);
1423 MOZ_ASSERT(initialLineContainer
==
1424 aBpd
->mCurrentTraverseLine
.mLineIterator
.GetContainer());
1427 bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame
* aFirstChild
,
1428 nsIContent
** aCurrContent
) {
1429 MOZ_ASSERT(!aFirstChild
|| !aFirstChild
->GetPrevSibling(),
1430 "Expecting to traverse from the start of a child list");
1432 for (nsIFrame
* childFrame
= aFirstChild
; childFrame
;
1433 childFrame
= childFrame
->GetNextSibling()) {
1434 nsIFrame
* frame
= childFrame
;
1436 // If the real frame for a placeholder is a first-letter frame, we need to
1437 // consider its contents for potential Bidi resolution.
1438 if (childFrame
->IsPlaceholderFrame()) {
1439 nsIFrame
* realFrame
=
1440 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame
);
1441 if (realFrame
->IsLetterFrame()) {
1446 // If unicode-bidi properties are present, we should do bidi resolution.
1447 ComputedStyle
* sc
= frame
->Style();
1448 if (GetBidiControl(sc
) || GetBidiOverride(sc
)) {
1452 if (IsBidiLeaf(frame
)) {
1453 if (frame
->IsTextFrame()) {
1454 // If the frame already has a BidiDataProperty, we know we need to
1455 // perform bidi resolution (even if no bidi content is NOW present --
1456 // we might need to remove the property set by a previous reflow, if
1457 // content has changed; see bug 1366623).
1458 if (frame
->HasProperty(nsIFrame::BidiDataProperty())) {
1462 // Check whether the text frame has any RTL characters; if so, bidi
1463 // resolution will be needed.
1464 dom::Text
* content
= frame
->GetContent()->AsText();
1465 if (content
!= *aCurrContent
) {
1466 *aCurrContent
= content
;
1467 const nsTextFragment
* txt
= &content
->TextFragment();
1469 HasRTLChars(Span(txt
->Get2b(), txt
->GetLength()))) {
1474 } else if (ChildListMayRequireBidi(frame
->PrincipalChildList().FirstChild(),
1483 void nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData
* aBpd
) {
1484 aBpd
->ClearBidiControls();
1485 ResolveParagraph(aBpd
);
1490 nscoord
nsBidiPresUtils::ReorderFrames(nsIFrame
* aFirstFrameOnLine
,
1491 int32_t aNumFramesOnLine
,
1492 WritingMode aLineWM
,
1493 const nsSize
& aContainerSize
,
1495 nsSize
containerSize(aContainerSize
);
1497 // If this line consists of a line frame, reorder the line frame's children.
1498 if (aFirstFrameOnLine
->IsLineFrame()) {
1499 // The line frame is positioned at the start-edge, so use its size
1500 // as the container size.
1501 containerSize
= aFirstFrameOnLine
->GetSize();
1503 aFirstFrameOnLine
= aFirstFrameOnLine
->PrincipalChildList().FirstChild();
1504 if (!aFirstFrameOnLine
) {
1507 // All children of the line frame are on the first line. Setting
1508 // aNumFramesOnLine to -1 makes InitLogicalArrayFromLine look at all of
1510 aNumFramesOnLine
= -1;
1511 // As the line frame itself has been adjusted at its inline-start position
1512 // by the caller, we do not want to apply this to its children.
1516 // No need to bidi-reorder the line if there's only a single frame.
1517 if (aNumFramesOnLine
== 1) {
1518 auto bidiData
= nsBidiPresUtils::GetFrameBidiData(aFirstFrameOnLine
);
1519 nsContinuationStates continuationStates
;
1520 InitContinuationStates(aFirstFrameOnLine
, &continuationStates
);
1521 return aStart
+ RepositionFrame(aFirstFrameOnLine
,
1522 bidiData
.embeddingLevel
.IsLTR(), aStart
,
1523 &continuationStates
, aLineWM
, false,
1527 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1528 return RepositionInlineFrames(bld
, aLineWM
, containerSize
, aStart
);
1531 nsIFrame
* nsBidiPresUtils::GetFirstLeaf(nsIFrame
* aFrame
) {
1532 nsIFrame
* firstLeaf
= aFrame
;
1533 while (!IsBidiLeaf(firstLeaf
)) {
1534 nsIFrame
* firstChild
= firstLeaf
->PrincipalChildList().FirstChild();
1535 nsIFrame
* realFrame
= nsPlaceholderFrame::GetRealFrameFor(firstChild
);
1536 firstLeaf
= (realFrame
->IsLetterFrame()) ? realFrame
: firstChild
;
1541 FrameBidiData
nsBidiPresUtils::GetFrameBidiData(nsIFrame
* aFrame
) {
1542 return GetFirstLeaf(aFrame
)->GetBidiData();
1545 BidiEmbeddingLevel
nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame
* aFrame
) {
1546 return GetFirstLeaf(aFrame
)->GetEmbeddingLevel();
1549 BidiEmbeddingLevel
nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame
* aFrame
) {
1550 const nsIFrame
* firstLeaf
= aFrame
;
1551 while (!IsBidiLeaf(firstLeaf
)) {
1552 firstLeaf
= firstLeaf
->PrincipalChildList().FirstChild();
1554 return firstLeaf
->GetBaseLevel();
1557 void nsBidiPresUtils::IsFirstOrLast(nsIFrame
* aFrame
,
1558 nsContinuationStates
* aContinuationStates
,
1559 bool aSpanDirMatchesLineDir
,
1560 bool& aIsFirst
/* out */,
1561 bool& aIsLast
/* out */) {
1563 * Since we lay out frames in the line's direction, visiting a frame with
1564 * 'mFirstVisualFrame == nullptr', means it's the first appearance of one
1565 * of its continuation chain frames on the line.
1566 * To determine if it's the last visual frame of its continuation chain on
1567 * the line or not, we count the number of frames of the chain on the line,
1568 * and then reduce it when we lay out a frame of the chain. If this value
1569 * becomes 1 it means that it's the last visual frame of its continuation
1570 * chain on this line.
1573 bool firstInLineOrder
, lastInLineOrder
;
1574 nsFrameContinuationState
* frameState
= aContinuationStates
->Get(aFrame
);
1575 nsFrameContinuationState
* firstFrameState
;
1577 if (!frameState
->mFirstVisualFrame
) {
1578 // aFrame is the first visual frame of its continuation chain
1579 nsFrameContinuationState
* contState
;
1582 frameState
->mFrameCount
= 1;
1583 frameState
->mFirstVisualFrame
= aFrame
;
1586 * Traverse continuation chain of aFrame in both backward and forward
1587 * directions while the frames are on this line. Count the frames and
1588 * set their mFirstVisualFrame to aFrame.
1590 // Traverse continuation chain backward
1591 for (frame
= aFrame
->GetPrevContinuation();
1592 frame
&& (contState
= aContinuationStates
->Get(frame
));
1593 frame
= frame
->GetPrevContinuation()) {
1594 frameState
->mFrameCount
++;
1595 contState
->mFirstVisualFrame
= aFrame
;
1597 frameState
->mHasContOnPrevLines
= (frame
!= nullptr);
1599 // Traverse continuation chain forward
1600 for (frame
= aFrame
->GetNextContinuation();
1601 frame
&& (contState
= aContinuationStates
->Get(frame
));
1602 frame
= frame
->GetNextContinuation()) {
1603 frameState
->mFrameCount
++;
1604 contState
->mFirstVisualFrame
= aFrame
;
1606 frameState
->mHasContOnNextLines
= (frame
!= nullptr);
1608 firstInLineOrder
= true;
1609 firstFrameState
= frameState
;
1611 // aFrame is not the first visual frame of its continuation chain
1612 firstInLineOrder
= false;
1613 firstFrameState
= aContinuationStates
->Get(frameState
->mFirstVisualFrame
);
1616 lastInLineOrder
= (firstFrameState
->mFrameCount
== 1);
1618 if (aSpanDirMatchesLineDir
) {
1619 aIsFirst
= firstInLineOrder
;
1620 aIsLast
= lastInLineOrder
;
1622 aIsFirst
= lastInLineOrder
;
1623 aIsLast
= firstInLineOrder
;
1626 if (frameState
->mHasContOnPrevLines
) {
1629 if (firstFrameState
->mHasContOnNextLines
) {
1633 if ((aIsFirst
|| aIsLast
) &&
1634 aFrame
->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT
)) {
1635 // For ib splits, don't treat anything except the last part as
1636 // endmost or anything except the first part as startmost.
1637 // As an optimization, only get the first continuation once.
1638 nsIFrame
* firstContinuation
= aFrame
->FirstContinuation();
1639 if (firstContinuation
->FrameIsNonLastInIBSplit()) {
1640 // We are not endmost
1643 if (firstContinuation
->FrameIsNonFirstInIBSplit()) {
1644 // We are not startmost
1649 // Reduce number of remaining frames of the continuation chain on the line.
1650 firstFrameState
->mFrameCount
--;
1652 nsInlineFrame
* testFrame
= do_QueryFrame(aFrame
);
1655 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET
);
1658 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST
);
1660 aFrame
->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST
);
1664 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST
);
1666 aFrame
->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST
);
1672 void nsBidiPresUtils::RepositionRubyContentFrame(
1673 nsIFrame
* aFrame
, WritingMode aFrameWM
,
1674 const LogicalMargin
& aBorderPadding
) {
1675 const nsFrameList
& childList
= aFrame
->PrincipalChildList();
1676 if (childList
.IsEmpty()) {
1680 // Reorder the children.
1682 ReorderFrames(childList
.FirstChild(), childList
.GetLength(), aFrameWM
,
1683 aFrame
->GetSize(), aBorderPadding
.IStart(aFrameWM
));
1684 isize
+= aBorderPadding
.IEnd(aFrameWM
);
1686 if (aFrame
->StyleText()->mRubyAlign
== StyleRubyAlign::Start
) {
1689 nscoord residualISize
= aFrame
->ISize(aFrameWM
) - isize
;
1690 if (residualISize
<= 0) {
1694 // When ruby-align is not "start", if the content does not fill this
1695 // frame, we need to center the children.
1696 const nsSize dummyContainerSize
;
1697 for (nsIFrame
* child
: childList
) {
1698 LogicalRect rect
= child
->GetLogicalRect(aFrameWM
, dummyContainerSize
);
1699 rect
.IStart(aFrameWM
) += residualISize
/ 2;
1700 child
->SetRect(aFrameWM
, rect
, dummyContainerSize
);
1705 nscoord
nsBidiPresUtils::RepositionRubyFrame(
1706 nsIFrame
* aFrame
, nsContinuationStates
* aContinuationStates
,
1707 const WritingMode aContainerWM
, const LogicalMargin
& aBorderPadding
) {
1708 LayoutFrameType frameType
= aFrame
->Type();
1709 MOZ_ASSERT(RubyUtils::IsRubyBox(frameType
));
1712 WritingMode frameWM
= aFrame
->GetWritingMode();
1713 bool isLTR
= frameWM
.IsBidiLTR();
1714 nsSize frameSize
= aFrame
->GetSize();
1715 if (frameType
== LayoutFrameType::Ruby
) {
1716 icoord
+= aBorderPadding
.IStart(frameWM
);
1717 // Reposition ruby segments in a ruby container
1718 for (RubySegmentEnumerator
e(static_cast<nsRubyFrame
*>(aFrame
)); !e
.AtEnd();
1720 nsRubyBaseContainerFrame
* rbc
= e
.GetBaseContainer();
1721 AutoRubyTextContainerArray
textContainers(rbc
);
1723 nscoord segmentISize
= RepositionFrame(
1724 rbc
, isLTR
, icoord
, aContinuationStates
, frameWM
, false, frameSize
);
1725 for (nsRubyTextContainerFrame
* rtc
: textContainers
) {
1726 nscoord isize
= RepositionFrame(rtc
, isLTR
, icoord
, aContinuationStates
,
1727 frameWM
, false, frameSize
);
1728 segmentISize
= std::max(segmentISize
, isize
);
1730 icoord
+= segmentISize
;
1732 icoord
+= aBorderPadding
.IEnd(frameWM
);
1733 } else if (frameType
== LayoutFrameType::RubyBaseContainer
) {
1734 // Reposition ruby columns in a ruby segment
1735 auto rbc
= static_cast<nsRubyBaseContainerFrame
*>(aFrame
);
1736 AutoRubyTextContainerArray
textContainers(rbc
);
1738 for (RubyColumnEnumerator
e(rbc
, textContainers
); !e
.AtEnd(); e
.Next()) {
1740 e
.GetColumn(column
);
1742 nscoord columnISize
=
1743 RepositionFrame(column
.mBaseFrame
, isLTR
, icoord
, aContinuationStates
,
1744 frameWM
, false, frameSize
);
1745 for (nsRubyTextFrame
* rt
: column
.mTextFrames
) {
1746 nscoord isize
= RepositionFrame(rt
, isLTR
, icoord
, aContinuationStates
,
1747 frameWM
, false, frameSize
);
1748 columnISize
= std::max(columnISize
, isize
);
1750 icoord
+= columnISize
;
1753 if (frameType
== LayoutFrameType::RubyBase
||
1754 frameType
== LayoutFrameType::RubyText
) {
1755 RepositionRubyContentFrame(aFrame
, frameWM
, aBorderPadding
);
1757 // Note that, ruby text container is not present in all conditions
1758 // above. It is intended, because the children of rtc are reordered
1759 // with the children of ruby base container simultaneously. We only
1760 // need to return its isize here, as it should not be changed.
1761 icoord
+= aFrame
->ISize(aContainerWM
);
1767 nscoord
nsBidiPresUtils::RepositionFrame(
1768 nsIFrame
* aFrame
, bool aIsEvenLevel
, nscoord aStartOrEnd
,
1769 nsContinuationStates
* aContinuationStates
, WritingMode aContainerWM
,
1770 bool aContainerReverseDir
, const nsSize
& aContainerSize
) {
1772 aContainerWM
.IsVertical() ? aContainerSize
.height
: aContainerSize
.width
;
1773 NS_ASSERTION(lineSize
!= NS_UNCONSTRAINEDSIZE
,
1774 "Unconstrained inline line size in bidi frame reordering");
1775 if (!aFrame
) return 0;
1777 bool isFirst
, isLast
;
1778 WritingMode frameWM
= aFrame
->GetWritingMode();
1779 IsFirstOrLast(aFrame
, aContinuationStates
,
1780 aContainerWM
.IsBidiLTR() == frameWM
.IsBidiLTR(),
1781 isFirst
/* out */, isLast
/* out */);
1783 // We only need the margin if the frame is first or last in its own
1784 // writing mode, but we're traversing the frames in the order of the
1785 // container's writing mode. To get the right values, we set start and
1786 // end margins on a logical margin in the frame's writing mode, and
1787 // then convert the margin to the container's writing mode to set the
1790 // This method is called from nsBlockFrame::PlaceLine via the call to
1791 // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
1792 // have been reflowed, which is required for GetUsedMargin/Border/Padding
1793 nscoord frameISize
= aFrame
->ISize();
1794 LogicalMargin frameMargin
= aFrame
->GetLogicalUsedMargin(frameWM
);
1795 LogicalMargin borderPadding
= aFrame
->GetLogicalUsedBorderAndPadding(frameWM
);
1796 // Since the visual order of frame could be different from the continuation
1797 // order, we need to remove any inline border/padding [that is already applied
1798 // based on continuation order] and then add it back based on the visual order
1799 // (i.e. isFirst/isLast) to get the correct isize for the current frame.
1800 // We don't need to do that for 'box-decoration-break:clone' because then all
1801 // continuations have border/padding/margin applied.
1802 if (aFrame
->StyleBorder()->mBoxDecorationBreak
==
1803 StyleBoxDecorationBreak::Slice
) {
1804 // First remove the border/padding that was applied based on logical order.
1805 if (!aFrame
->GetPrevContinuation()) {
1806 frameISize
-= borderPadding
.IStart(frameWM
);
1808 if (!aFrame
->GetNextContinuation()) {
1809 frameISize
-= borderPadding
.IEnd(frameWM
);
1811 // Set margin/border/padding based on visual order.
1813 frameMargin
.IStart(frameWM
) = 0;
1814 borderPadding
.IStart(frameWM
) = 0;
1817 frameMargin
.IEnd(frameWM
) = 0;
1818 borderPadding
.IEnd(frameWM
) = 0;
1820 // Add the border/padding which is now based on visual order.
1821 frameISize
+= borderPadding
.IStartEnd(frameWM
);
1825 if (IsBidiLeaf(aFrame
)) {
1827 frameWM
.IsOrthogonalTo(aContainerWM
) ? aFrame
->BSize() : frameISize
;
1828 } else if (RubyUtils::IsRubyBox(aFrame
->Type())) {
1829 icoord
+= RepositionRubyFrame(aFrame
, aContinuationStates
, aContainerWM
,
1832 bool reverseDir
= aIsEvenLevel
!= frameWM
.IsBidiLTR();
1833 icoord
+= reverseDir
? borderPadding
.IEnd(frameWM
)
1834 : borderPadding
.IStart(frameWM
);
1835 LogicalSize
logicalSize(frameWM
, frameISize
, aFrame
->BSize());
1836 nsSize frameSize
= logicalSize
.GetPhysicalSize(frameWM
);
1837 // Reposition the child frames
1838 for (nsIFrame
* f
: aFrame
->PrincipalChildList()) {
1839 icoord
+= RepositionFrame(f
, aIsEvenLevel
, icoord
, aContinuationStates
,
1840 frameWM
, reverseDir
, frameSize
);
1842 icoord
+= reverseDir
? borderPadding
.IStart(frameWM
)
1843 : borderPadding
.IEnd(frameWM
);
1846 // In the following variables, if aContainerReverseDir is true, i.e.
1847 // the container is positioning its children in reverse of its logical
1848 // direction, the "StartOrEnd" refers to the distance from the frame
1849 // to the inline end edge of the container, elsewise, it refers to the
1850 // distance to the inline start edge.
1851 const LogicalMargin margin
= frameMargin
.ConvertTo(aContainerWM
, frameWM
);
1852 nscoord marginStartOrEnd
= aContainerReverseDir
? margin
.IEnd(aContainerWM
)
1853 : margin
.IStart(aContainerWM
);
1854 nscoord frameStartOrEnd
= aStartOrEnd
+ marginStartOrEnd
;
1856 LogicalRect rect
= aFrame
->GetLogicalRect(aContainerWM
, aContainerSize
);
1857 rect
.ISize(aContainerWM
) = icoord
;
1858 rect
.IStart(aContainerWM
) = aContainerReverseDir
1859 ? lineSize
- frameStartOrEnd
- icoord
1861 aFrame
->SetRect(aContainerWM
, rect
, aContainerSize
);
1863 return icoord
+ margin
.IStartEnd(aContainerWM
);
1866 void nsBidiPresUtils::InitContinuationStates(
1867 nsIFrame
* aFrame
, nsContinuationStates
* aContinuationStates
) {
1868 aContinuationStates
->Insert(aFrame
);
1869 if (!IsBidiLeaf(aFrame
)) {
1870 // Continue for child frames
1871 for (nsIFrame
* frame
: aFrame
->PrincipalChildList()) {
1872 InitContinuationStates(frame
, aContinuationStates
);
1878 nscoord
nsBidiPresUtils::RepositionInlineFrames(const BidiLineData
& aBld
,
1879 WritingMode aLineWM
,
1880 const nsSize
& aContainerSize
,
1882 nsContinuationStates continuationStates
;
1883 aBld
.InitContinuationStates(&continuationStates
);
1885 if (aLineWM
.IsBidiLTR()) {
1886 for (auto index
: IntegerRange(aBld
.VisualFrameCount())) {
1887 auto [frame
, level
] = aBld
.VisualFrameAndLevelAt(index
);
1889 RepositionFrame(frame
, level
.IsLTR(), aStart
, &continuationStates
,
1890 aLineWM
, false, aContainerSize
);
1893 for (auto index
: Reversed(IntegerRange(aBld
.VisualFrameCount()))) {
1894 auto [frame
, level
] = aBld
.VisualFrameAndLevelAt(index
);
1896 RepositionFrame(frame
, level
.IsLTR(), aStart
, &continuationStates
,
1897 aLineWM
, false, aContainerSize
);
1904 bool nsBidiPresUtils::CheckLineOrder(nsIFrame
* aFirstFrameOnLine
,
1905 int32_t aNumFramesOnLine
,
1906 nsIFrame
** aFirstVisual
,
1907 nsIFrame
** aLastVisual
) {
1908 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1911 *aFirstVisual
= bld
.VisualFrameAt(0);
1914 *aLastVisual
= bld
.VisualFrameAt(bld
.VisualFrameCount() - 1);
1917 return bld
.IsReordered();
1920 nsIFrame
* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame
* aFrame
,
1921 nsIFrame
* aFirstFrameOnLine
,
1922 int32_t aNumFramesOnLine
) {
1923 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1925 int32_t count
= bld
.VisualFrameCount();
1927 if (!aFrame
&& count
) {
1928 return bld
.VisualFrameAt(0);
1931 for (int32_t i
= 0; i
< count
- 1; i
++) {
1932 if (bld
.VisualFrameAt(i
) == aFrame
) {
1933 return bld
.VisualFrameAt(i
+ 1);
1940 nsIFrame
* nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame
* aFrame
,
1941 nsIFrame
* aFirstFrameOnLine
,
1942 int32_t aNumFramesOnLine
) {
1943 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1945 int32_t count
= bld
.VisualFrameCount();
1947 if (!aFrame
&& count
) {
1948 return bld
.VisualFrameAt(count
- 1);
1951 for (int32_t i
= 1; i
< count
; i
++) {
1952 if (bld
.VisualFrameAt(i
) == aFrame
) {
1953 return bld
.VisualFrameAt(i
- 1);
1960 inline void nsBidiPresUtils::EnsureBidiContinuation(
1961 nsIFrame
* aFrame
, const nsLineList::iterator aLine
, nsIFrame
** aNewFrame
,
1962 int32_t aStart
, int32_t aEnd
) {
1963 MOZ_ASSERT(aNewFrame
, "null OUT ptr");
1964 MOZ_ASSERT(aFrame
, "aFrame is null");
1966 aFrame
->AdjustOffsetsForBidi(aStart
, aEnd
);
1967 CreateContinuation(aFrame
, aLine
, aNewFrame
, false);
1970 void nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData
* aBpd
,
1972 int32_t aFirstIndex
,
1973 int32_t aLastIndex
) {
1974 FrameBidiData bidiData
= aFrame
->GetBidiData();
1975 bidiData
.precedingControl
= kBidiLevelNone
;
1976 for (int32_t index
= aFirstIndex
+ 1; index
<= aLastIndex
; index
++) {
1977 nsIFrame
* frame
= aBpd
->FrameAt(index
);
1978 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1979 // Make the frame and its continuation ancestors fluid,
1980 // so they can be reused or deleted by normal reflow code
1981 frame
->SetProperty(nsIFrame::BidiDataProperty(), bidiData
);
1982 frame
->AddStateBits(NS_FRAME_IS_BIDI
);
1983 while (frame
&& IsBidiSplittable(frame
)) {
1984 nsIFrame
* prev
= frame
->GetPrevContinuation();
1986 MakeContinuationFluid(prev
, frame
);
1987 frame
= frame
->GetParent();
1995 // Make sure that the last continuation we made fluid does not itself have a
1996 // fluid continuation (this can happen when re-resolving after dynamic changes
1998 nsIFrame
* lastFrame
= aBpd
->FrameAt(aLastIndex
);
1999 MakeContinuationsNonFluidUpParentChain(lastFrame
, lastFrame
->GetNextInFlow());
2002 nsresult
nsBidiPresUtils::FormatUnicodeText(nsPresContext
* aPresContext
,
2004 int32_t& aTextLength
,
2005 BidiClass aBidiClass
) {
2006 nsresult rv
= NS_OK
;
2008 // adjusted for correct numeral shaping
2009 uint32_t bidiOptions
= aPresContext
->GetBidi();
2010 switch (GET_BIDI_OPTION_NUMERAL(bidiOptions
)) {
2011 case IBMBIDI_NUMERAL_HINDI
:
2012 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2015 case IBMBIDI_NUMERAL_ARABIC
:
2016 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2019 case IBMBIDI_NUMERAL_PERSIAN
:
2020 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_PERSIAN
);
2023 case IBMBIDI_NUMERAL_REGULAR
:
2025 switch (aBidiClass
) {
2026 case BidiClass::EuropeanNumber
:
2027 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2030 case BidiClass::ArabicNumber
:
2031 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2039 case IBMBIDI_NUMERAL_HINDICONTEXT
:
2040 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions
) ==
2041 IBMBIDI_TEXTDIRECTION_RTL
) &&
2042 (IS_ARABIC_DIGIT(aText
[0]))) ||
2043 (BidiClass::ArabicNumber
== aBidiClass
)) {
2044 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2045 } else if (BidiClass::EuropeanNumber
== aBidiClass
) {
2046 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2050 case IBMBIDI_NUMERAL_PERSIANCONTEXT
:
2051 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions
) ==
2052 IBMBIDI_TEXTDIRECTION_RTL
) &&
2053 (IS_ARABIC_DIGIT(aText
[0]))) ||
2054 (BidiClass::ArabicNumber
== aBidiClass
)) {
2055 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_PERSIAN
);
2056 } else if (BidiClass::EuropeanNumber
== aBidiClass
) {
2057 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2061 case IBMBIDI_NUMERAL_NOMINAL
:
2066 StripBidiControlCharacters(aText
, aTextLength
);
2070 void nsBidiPresUtils::StripBidiControlCharacters(char16_t
* aText
,
2071 int32_t& aTextLength
) {
2072 if ((nullptr == aText
) || (aTextLength
< 1)) {
2076 int32_t stripLen
= 0;
2078 for (int32_t i
= 0; i
< aTextLength
; i
++) {
2079 // XXX: This silently ignores surrogate characters.
2080 // As of Unicode 4.0, all Bidi control characters are within the BMP.
2081 if (IsBidiControl((uint32_t)aText
[i
])) {
2084 aText
[i
- stripLen
] = aText
[i
];
2087 aTextLength
-= stripLen
;
2090 void nsBidiPresUtils::CalculateBidiClass(
2091 const char16_t
* aText
, int32_t& aOffset
, int32_t aBidiClassLimit
,
2092 int32_t& aRunLimit
, int32_t& aRunLength
, int32_t& aRunCount
,
2093 BidiClass
& aBidiClass
, BidiClass
& aPrevBidiClass
) {
2094 bool strongTypeFound
= false;
2096 BidiClass bidiClass
;
2098 aBidiClass
= BidiClass::OtherNeutral
;
2101 for (offset
= aOffset
; offset
< aBidiClassLimit
; offset
+= charLen
) {
2102 // Make sure we give RTL chartype to all characters that would be classified
2103 // as Right-To-Left by a bidi platform.
2104 // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
2106 uint32_t ch
= aText
[offset
];
2107 if (IS_HEBREW_CHAR(ch
)) {
2108 bidiClass
= BidiClass::RightToLeft
;
2109 } else if (IS_ARABIC_ALPHABETIC(ch
)) {
2110 bidiClass
= BidiClass::RightToLeftArabic
;
2112 if (offset
+ 1 < aBidiClassLimit
&&
2113 NS_IS_SURROGATE_PAIR(ch
, aText
[offset
+ 1])) {
2114 ch
= SURROGATE_TO_UCS4(ch
, aText
[offset
+ 1]);
2117 bidiClass
= intl::UnicodeProperties::GetBidiClass(ch
);
2120 if (!BIDICLASS_IS_WEAK(bidiClass
)) {
2121 if (strongTypeFound
&& (bidiClass
!= aPrevBidiClass
) &&
2122 (BIDICLASS_IS_RTL(bidiClass
) || BIDICLASS_IS_RTL(aPrevBidiClass
))) {
2123 // Stop at this point to ensure uni-directionality of the text
2124 // (from platform's point of view).
2125 // Also, don't mix Arabic and Hebrew content (since platform may
2126 // provide BIDI support to one of them only).
2127 aRunLength
= offset
- aOffset
;
2133 if ((BidiClass::RightToLeftArabic
== aPrevBidiClass
||
2134 BidiClass::ArabicNumber
== aPrevBidiClass
) &&
2135 BidiClass::EuropeanNumber
== bidiClass
) {
2136 bidiClass
= BidiClass::ArabicNumber
;
2139 // Set PrevBidiClass to the last strong type in this frame
2140 // (for correct numeric shaping)
2141 aPrevBidiClass
= bidiClass
;
2143 strongTypeFound
= true;
2144 aBidiClass
= bidiClass
;
2150 nsresult
nsBidiPresUtils::ProcessText(const char16_t
* aText
, size_t aLength
,
2151 BidiEmbeddingLevel aBaseLevel
,
2152 nsPresContext
* aPresContext
,
2153 BidiProcessor
& aprocessor
, Mode aMode
,
2154 nsBidiPositionResolve
* aPosResolve
,
2155 int32_t aPosResolveCount
, nscoord
* aWidth
,
2156 BidiEngine
& aBidiEngine
) {
2157 MOZ_ASSERT((aPosResolve
== nullptr) != (aPosResolveCount
> 0),
2158 "Incorrect aPosResolve / aPosResolveCount arguments");
2160 // Caller should have already replaced any separators in the original text
2161 // with <space> characters.
2162 MOZ_ASSERT(nsDependentSubstring(aText
, aLength
).FindCharInSet(kSeparators
) ==
2165 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2166 aPosResolve
[nPosResolve
].visualIndex
= kNotFound
;
2167 aPosResolve
[nPosResolve
].visualLeftTwips
= kNotFound
;
2168 aPosResolve
[nPosResolve
].visualWidth
= kNotFound
;
2171 // For a single-char string, or a string that is purely LTR, use a simplified
2172 // path as it cannot have multiple direction or bidi-class runs.
2174 (aLength
== 2 && NS_IS_SURROGATE_PAIR(aText
[0], aText
[1])) ||
2175 (aBaseLevel
.Direction() == BidiDirection::LTR
&&
2176 !encoding_mem_is_utf16_bidi(aText
, aLength
))) {
2177 ProcessSimpleRun(aText
, aLength
, aBaseLevel
, aPresContext
, aprocessor
,
2178 aMode
, aPosResolve
, aPosResolveCount
, aWidth
);
2182 if (aBidiEngine
.SetParagraph(Span(aText
, aLength
), aBaseLevel
).isErr()) {
2183 return NS_ERROR_FAILURE
;
2186 auto result
= aBidiEngine
.CountRuns();
2187 if (result
.isErr()) {
2188 return NS_ERROR_FAILURE
;
2190 int32_t runCount
= result
.unwrap();
2192 nscoord xOffset
= 0;
2193 nscoord width
, xEndRun
= 0;
2194 nscoord totalWidth
= 0;
2195 int32_t i
, start
, limit
, length
;
2196 uint32_t visualStart
= 0;
2197 BidiClass bidiClass
;
2198 BidiClass prevClass
= BidiClass::LeftToRight
;
2200 for (i
= 0; i
< runCount
; i
++) {
2201 aBidiEngine
.GetVisualRun(i
, &start
, &length
);
2203 BidiEmbeddingLevel level
;
2204 aBidiEngine
.GetLogicalRun(start
, &limit
, &level
);
2206 BidiDirection dir
= level
.Direction();
2207 int32_t subRunLength
= limit
- start
;
2208 int32_t lineOffset
= start
;
2209 int32_t typeLimit
= std::min(limit
, AssertedCast
<int32_t>(aLength
));
2210 int32_t subRunCount
= 1;
2211 int32_t subRunLimit
= typeLimit
;
2214 * If |level| is even, i.e. the direction of the run is left-to-right, we
2215 * render the subruns from left to right and increment the x-coordinate
2216 * |xOffset| by the width of each subrun after rendering.
2218 * If |level| is odd, i.e. the direction of the run is right-to-left, we
2219 * render the subruns from right to left. We begin by incrementing |xOffset|
2220 * by the width of the whole run, and then decrement it by the width of each
2221 * subrun before rendering. After rendering all the subruns, we restore the
2222 * x-coordinate of the end of the run for the start of the next run.
2225 if (dir
== BidiDirection::RTL
) {
2226 aprocessor
.SetText(aText
+ start
, subRunLength
, BidiDirection::RTL
);
2227 width
= aprocessor
.GetWidth();
2232 while (subRunCount
> 0) {
2233 // CalculateBidiClass can increment subRunCount if the run
2234 // contains mixed character types
2235 CalculateBidiClass(aText
, lineOffset
, typeLimit
, subRunLimit
,
2236 subRunLength
, subRunCount
, bidiClass
, prevClass
);
2238 nsAutoString
runVisualText(aText
+ start
, subRunLength
);
2240 FormatUnicodeText(aPresContext
, runVisualText
.BeginWriting(),
2241 subRunLength
, bidiClass
);
2244 aprocessor
.SetText(runVisualText
.get(), subRunLength
, dir
);
2245 width
= aprocessor
.GetWidth();
2246 totalWidth
+= width
;
2247 if (dir
== BidiDirection::RTL
) {
2250 if (aMode
== MODE_DRAW
) {
2251 aprocessor
.DrawText(xOffset
);
2255 * The caller may request to calculate the visual position of one
2256 * or more characters.
2258 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2259 nsBidiPositionResolve
* posResolve
= &aPosResolve
[nPosResolve
];
2261 * Did we already resolve this position's visual metric? If so, skip.
2263 if (posResolve
->visualLeftTwips
!= kNotFound
) continue;
2266 * First find out if the logical position is within this run.
2268 if (start
<= posResolve
->logicalIndex
&&
2269 start
+ subRunLength
> posResolve
->logicalIndex
) {
2271 * If this run is only one character long, we have an easy case:
2272 * the visual position is the x-coord of the start of the run
2273 * less the x-coord of the start of the whole text.
2275 if (subRunLength
== 1) {
2276 posResolve
->visualIndex
= visualStart
;
2277 posResolve
->visualLeftTwips
= xOffset
;
2278 posResolve
->visualWidth
= width
;
2281 * Otherwise, we need to measure the width of the run's part
2282 * which is to the visual left of the index.
2283 * In other words, the run is broken in two, around the logical index,
2284 * and we measure the part which is visually left.
2285 * If the run is right-to-left, this part will span from after the
2286 * index up to the end of the run; if it is left-to-right, this part
2287 * will span from the start of the run up to (and inclduing) the
2288 * character before the index.
2292 * Here is a description of how the width of the current character
2293 * (posResolve->visualWidth) is calculated:
2295 * LTR (current char: "P"):
2296 * S A M P L E (logical index: 3, visual index: 3)
2297 * ^ (visualLeftPart)
2298 * ^ (visualRightSide)
2299 * visualLeftLength == 3
2301 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2302 * ^^ (posResolve->visualWidth)
2304 * RTL (current char: "M"):
2305 * E L P M A S (logical index: 2, visual index: 3)
2306 * ^ (visualLeftPart)
2307 * ^ (visualRightSide)
2308 * visualLeftLength == 3
2310 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2311 * ^^ (posResolve->visualWidth)
2314 // The position in the text where this run's "left part" begins.
2315 const char16_t
* visualLeftPart
;
2316 const char16_t
* visualRightSide
;
2317 if (dir
== BidiDirection::RTL
) {
2318 // One day, son, this could all be replaced with
2319 // mPresContext->BidiEngine().GetVisualIndex() ...
2320 posResolve
->visualIndex
=
2322 (subRunLength
- (posResolve
->logicalIndex
+ 1 - start
));
2323 // Skipping to the "left part".
2324 visualLeftPart
= aText
+ posResolve
->logicalIndex
+ 1;
2325 // Skipping to the right side of the current character
2326 visualRightSide
= visualLeftPart
- 1;
2328 posResolve
->visualIndex
=
2329 visualStart
+ (posResolve
->logicalIndex
- start
);
2330 // Skipping to the "left part".
2331 visualLeftPart
= aText
+ start
;
2332 // In LTR mode this is the same as visualLeftPart
2333 visualRightSide
= visualLeftPart
;
2335 // The delta between the start of the run and the left part's end.
2336 int32_t visualLeftLength
= posResolve
->visualIndex
- visualStart
;
2337 aprocessor
.SetText(visualLeftPart
, visualLeftLength
, dir
);
2338 subWidth
= aprocessor
.GetWidth();
2339 aprocessor
.SetText(visualRightSide
, visualLeftLength
+ 1, dir
);
2340 posResolve
->visualLeftTwips
= xOffset
+ subWidth
;
2341 posResolve
->visualWidth
= aprocessor
.GetWidth() - subWidth
;
2346 if (dir
== BidiDirection::LTR
) {
2352 subRunLimit
= typeLimit
;
2353 subRunLength
= typeLimit
- lineOffset
;
2355 if (dir
== BidiDirection::RTL
) {
2359 visualStart
+= length
;
2363 *aWidth
= totalWidth
;
2368 // This is called either for a single character (one code unit, or a surrogate
2369 // pair), or for a run that is known to be purely LTR.
2370 void nsBidiPresUtils::ProcessSimpleRun(const char16_t
* aText
, size_t aLength
,
2371 BidiEmbeddingLevel aBaseLevel
,
2372 nsPresContext
* aPresContext
,
2373 BidiProcessor
& aprocessor
, Mode aMode
,
2374 nsBidiPositionResolve
* aPosResolve
,
2375 int32_t aPosResolveCount
,
2383 // Get bidi class from the first (or only) character.
2384 uint32_t ch
= aText
[0];
2385 if (aLength
> 1 && NS_IS_HIGH_SURROGATE(ch
) &&
2386 NS_IS_LOW_SURROGATE(aText
[1])) {
2387 ch
= SURROGATE_TO_UCS4(aText
[0], aText
[1]);
2389 BidiClass bidiClass
= intl::UnicodeProperties::GetBidiClass(ch
);
2391 nsAutoString
runVisualText(aText
, aLength
);
2392 int32_t length
= aLength
;
2394 FormatUnicodeText(aPresContext
, runVisualText
.BeginWriting(), length
,
2398 BidiDirection dir
= bidiClass
== BidiClass::RightToLeft
||
2399 bidiClass
== BidiClass::RightToLeftArabic
2400 ? BidiDirection::RTL
2401 : BidiDirection::LTR
;
2402 aprocessor
.SetText(runVisualText
.get(), length
, dir
);
2404 if (aMode
== MODE_DRAW
) {
2405 aprocessor
.DrawText(0);
2408 if (!aWidth
&& !aPosResolve
) {
2412 nscoord width
= aprocessor
.GetWidth();
2414 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2415 nsBidiPositionResolve
* posResolve
= &aPosResolve
[nPosResolve
];
2416 if (posResolve
->visualLeftTwips
!= kNotFound
) {
2419 if (0 <= posResolve
->logicalIndex
&& length
> posResolve
->logicalIndex
) {
2420 posResolve
->visualIndex
= 0;
2421 posResolve
->visualLeftTwips
= 0;
2422 posResolve
->visualWidth
= width
;
2431 class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final
2432 : public nsBidiPresUtils::BidiProcessor
{
2434 typedef gfx::DrawTarget DrawTarget
;
2436 nsIRenderingContextBidiProcessor(gfxContext
* aCtx
,
2437 DrawTarget
* aTextRunConstructionDrawTarget
,
2438 nsFontMetrics
* aFontMetrics
,
2441 mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget
),
2442 mFontMetrics(aFontMetrics
),
2447 ~nsIRenderingContextBidiProcessor() { mFontMetrics
->SetTextRunRTL(false); }
2449 virtual void SetText(const char16_t
* aText
, int32_t aLength
,
2450 BidiDirection aDirection
) override
{
2451 mFontMetrics
->SetTextRunRTL(aDirection
== BidiDirection::RTL
);
2456 virtual nscoord
GetWidth() override
{
2457 return nsLayoutUtils::AppUnitWidthOfString(mText
, mLength
, *mFontMetrics
,
2458 mTextRunConstructionDrawTarget
);
2461 virtual void DrawText(nscoord aIOffset
) override
{
2463 if (mFontMetrics
->GetVertical()) {
2468 mFontMetrics
->DrawString(mText
, mLength
, pt
.x
, pt
.y
, mCtx
,
2469 mTextRunConstructionDrawTarget
);
2474 DrawTarget
* mTextRunConstructionDrawTarget
;
2475 nsFontMetrics
* mFontMetrics
;
2477 const char16_t
* mText
;
2481 nsresult
nsBidiPresUtils::ProcessTextForRenderingContext(
2482 const char16_t
* aText
, int32_t aLength
, BidiEmbeddingLevel aBaseLevel
,
2483 nsPresContext
* aPresContext
, gfxContext
& aRenderingContext
,
2484 DrawTarget
* aTextRunConstructionDrawTarget
, nsFontMetrics
& aFontMetrics
,
2485 Mode aMode
, nscoord aX
, nscoord aY
, nsBidiPositionResolve
* aPosResolve
,
2486 int32_t aPosResolveCount
, nscoord
* aWidth
) {
2487 nsIRenderingContextBidiProcessor
processor(&aRenderingContext
,
2488 aTextRunConstructionDrawTarget
,
2489 &aFontMetrics
, nsPoint(aX
, aY
));
2490 nsDependentSubstring
text(aText
, aLength
);
2491 auto separatorIndex
= text
.FindCharInSet(kSeparators
);
2492 if (separatorIndex
== kNotFound
) {
2493 return ProcessText(text
.BeginReading(), text
.Length(), aBaseLevel
,
2494 aPresContext
, processor
, aMode
, aPosResolve
,
2495 aPosResolveCount
, aWidth
, aPresContext
->BidiEngine());
2498 // We need to replace any block or segment separators with space for bidi
2499 // processing, so make a local copy.
2500 nsAutoString
localText(text
);
2501 ReplaceSeparators(localText
, separatorIndex
);
2502 return ProcessText(localText
.BeginReading(), localText
.Length(), aBaseLevel
,
2503 aPresContext
, processor
, aMode
, aPosResolve
,
2504 aPosResolveCount
, aWidth
, aPresContext
->BidiEngine());
2508 BidiEmbeddingLevel
nsBidiPresUtils::BidiLevelFromStyle(
2509 ComputedStyle
* aComputedStyle
) {
2510 if (aComputedStyle
->StyleTextReset()->mUnicodeBidi
==
2511 StyleUnicodeBidi::Plaintext
) {
2512 return BidiEmbeddingLevel::DefaultLTR();
2515 if (aComputedStyle
->StyleVisibility()->mDirection
== StyleDirection::Rtl
) {
2516 return BidiEmbeddingLevel::RTL();
2519 return BidiEmbeddingLevel::LTR();