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 kZWSP
= 0x200B;
51 static const char16_t kLineSeparator
= 0x2028;
52 static const char16_t kObjectSubstitute
= 0xFFFC;
53 static const char16_t kLRE
= 0x202A;
54 static const char16_t kRLE
= 0x202B;
55 static const char16_t kLRO
= 0x202D;
56 static const char16_t kRLO
= 0x202E;
57 static const char16_t kPDF
= 0x202C;
58 static const char16_t kLRI
= 0x2066;
59 static const char16_t kRLI
= 0x2067;
60 static const char16_t kFSI
= 0x2068;
61 static const char16_t kPDI
= 0x2069;
62 // All characters with Bidi type Segment Separator or Block Separator.
63 // This should be kept in sync with the table in ReplaceSeparators.
64 static const char16_t kSeparators
[] = {
65 char16_t('\t'), char16_t('\r'), char16_t('\n'), char16_t(0xb),
66 char16_t(0x1c), char16_t(0x1d), char16_t(0x1e), char16_t(0x1f),
67 char16_t(0x85), char16_t(0x2029), char16_t(0)};
69 #define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1)
71 // This exists just to be a type; the value doesn't matter.
72 enum class BidiControlFrameType
{ Value
};
74 static bool IsIsolateControl(char16_t aChar
) {
75 return aChar
== kLRI
|| aChar
== kRLI
|| aChar
== kFSI
;
78 // Given a ComputedStyle, return any bidi control character necessary to
79 // implement style properties that override directionality (i.e. if it has
80 // unicode-bidi:bidi-override, or text-orientation:upright in vertical
81 // writing mode) when applying the bidi algorithm.
83 // Returns 0 if no override control character is implied by this style.
84 static char16_t
GetBidiOverride(ComputedStyle
* aComputedStyle
) {
85 const nsStyleVisibility
* vis
= aComputedStyle
->StyleVisibility();
86 if ((vis
->mWritingMode
== StyleWritingModeProperty::VerticalRl
||
87 vis
->mWritingMode
== StyleWritingModeProperty::VerticalLr
) &&
88 vis
->mTextOrientation
== StyleTextOrientation::Upright
) {
91 const nsStyleTextReset
* text
= aComputedStyle
->StyleTextReset();
92 if (text
->mUnicodeBidi
== StyleUnicodeBidi::BidiOverride
||
93 text
->mUnicodeBidi
== StyleUnicodeBidi::IsolateOverride
) {
94 return StyleDirection::Rtl
== vis
->mDirection
? kRLO
: kLRO
;
99 // Given a ComputedStyle, return any bidi control character necessary to
100 // implement style properties that affect bidi resolution (i.e. if it
101 // has unicode-bidiembed, isolate, or plaintext) when applying the bidi
104 // Returns 0 if no control character is implied by the style.
106 // Note that GetBidiOverride and GetBidiControl need to be separate
107 // because in the case of unicode-bidi:isolate-override we need both
109 static char16_t
GetBidiControl(ComputedStyle
* aComputedStyle
) {
110 const nsStyleVisibility
* vis
= aComputedStyle
->StyleVisibility();
111 const nsStyleTextReset
* text
= aComputedStyle
->StyleTextReset();
112 switch (text
->mUnicodeBidi
) {
113 case StyleUnicodeBidi::Embed
:
114 return StyleDirection::Rtl
== vis
->mDirection
? kRLE
: kLRE
;
115 case StyleUnicodeBidi::Isolate
:
116 // <bdi> element already has its directionality set from content so
117 // we never need to return kFSI.
118 return StyleDirection::Rtl
== vis
->mDirection
? kRLI
: kLRI
;
119 case StyleUnicodeBidi::IsolateOverride
:
120 case StyleUnicodeBidi::Plaintext
:
122 case StyleUnicodeBidi::Normal
:
123 case StyleUnicodeBidi::BidiOverride
:
131 static inline bool AreContinuationsInOrder(nsIFrame
* aFrame1
,
133 nsIFrame
* f
= aFrame1
;
135 f
= f
->GetNextContinuation();
136 } while (f
&& f
!= aFrame2
);
141 struct MOZ_STACK_CLASS BidiParagraphData
{
143 FrameInfo(nsIFrame
* aFrame
, nsBlockInFlowLineIterator
& aLineIter
)
145 mBlockContainer(aLineIter
.GetContainer()),
146 mInOverflow(aLineIter
.GetInOverflow()) {}
148 explicit FrameInfo(BidiControlFrameType aValue
)
149 : mFrame(NS_BIDI_CONTROL_FRAME
),
150 mBlockContainer(nullptr),
151 mInOverflow(false) {}
154 : mFrame(nullptr), mBlockContainer(nullptr), mInOverflow(false) {}
158 // The block containing mFrame (i.e., which continuation).
159 nsBlockFrame
* mBlockContainer
;
161 // true if mFrame is in mBlockContainer's overflow lines, false if
166 nsAutoString mBuffer
;
167 AutoTArray
<char16_t
, 16> mEmbeddingStack
;
168 AutoTArray
<FrameInfo
, 16> mLogicalFrames
;
169 nsTHashMap
<nsPtrHashKey
<const nsIContent
>, int32_t> mContentToFrameIndex
;
170 // Cached presentation context for the frames we're processing.
171 nsPresContext
* mPresContext
;
174 BidiEmbeddingLevel mParaLevel
;
175 nsIContent
* mPrevContent
;
178 * This class is designed to manage the process of mapping a frame to
179 * the line that it's in, when we know that (a) the frames we ask it
180 * about are always in the block's lines and (b) each successive frame
181 * we ask it about is the same as or after (in depth-first search
182 * order) the previous.
184 * Since we move through the lines at a different pace in Traverse and
185 * ResolveParagraph, we use one of these for each.
187 * The state of the mapping is also different between TraverseFrames
188 * and ResolveParagraph since since resolving can call functions
189 * (EnsureBidiContinuation or SplitInlineAncestors) that can create
190 * new frames and thus break lines.
192 * The TraverseFrames iterator is only used in some edge cases.
194 struct FastLineIterator
{
195 FastLineIterator() : mPrevFrame(nullptr), mNextLineStart(nullptr) {}
197 // These iterators *and* mPrevFrame track the line list that we're
200 // mPrevFrame, if non-null, should be either the frame we're currently
201 // handling (in ResolveParagraph or TraverseFrames, depending on the
202 // iterator) or a frame before it, and is also guaranteed to either be in
203 // mCurrentLine or have been in mCurrentLine until recently.
205 // In case the splitting causes block frames to break lines, however, we
206 // also track the first frame of the next line. If that changes, it means
207 // we've broken lines and we have to invalidate mPrevFrame.
208 nsBlockInFlowLineIterator mLineIterator
;
209 nsIFrame
* mPrevFrame
;
210 nsIFrame
* mNextLineStart
;
212 nsLineList::iterator
GetLine() { return mLineIterator
.GetLine(); }
214 static bool IsFrameInCurrentLine(nsBlockInFlowLineIterator
* aLineIter
,
215 nsIFrame
* aPrevFrame
, nsIFrame
* aFrame
) {
216 MOZ_ASSERT(!aPrevFrame
|| aLineIter
->GetLine()->Contains(aPrevFrame
),
217 "aPrevFrame must be in aLineIter's current line");
218 nsIFrame
* endFrame
= aLineIter
->IsLastLineInList()
220 : aLineIter
->GetLine().next()->mFirstChild
;
221 nsIFrame
* startFrame
=
222 aPrevFrame
? aPrevFrame
: aLineIter
->GetLine()->mFirstChild
;
223 for (nsIFrame
* frame
= startFrame
; frame
&& frame
!= endFrame
;
224 frame
= frame
->GetNextSibling()) {
225 if (frame
== aFrame
) return true;
230 static nsIFrame
* FirstChildOfNextLine(
231 nsBlockInFlowLineIterator
& aIterator
) {
232 const nsLineList::iterator line
= aIterator
.GetLine();
233 const nsLineList::iterator lineEnd
= aIterator
.End();
234 MOZ_ASSERT(line
!= lineEnd
, "iterator should start off valid");
235 const nsLineList::iterator nextLine
= line
.next();
237 return nextLine
!= lineEnd
? nextLine
->mFirstChild
: nullptr;
240 // Advance line iterator to the line containing aFrame, assuming
241 // that aFrame is already in the line list our iterator is iterating
243 void AdvanceToFrame(nsIFrame
* aFrame
) {
244 if (mPrevFrame
&& FirstChildOfNextLine(mLineIterator
) != mNextLineStart
) {
245 // Something has caused a line to split. We need to invalidate
246 // mPrevFrame since it may now be in a *later* line, though it may
247 // still be in this line, so we need to start searching for it from
248 // the start of this line.
249 mPrevFrame
= nullptr;
251 nsIFrame
* child
= aFrame
;
252 nsIFrame
* parent
= nsLayoutUtils::GetParentOrPlaceholderFor(child
);
253 while (parent
&& !parent
->IsBlockFrameOrSubclass()) {
255 parent
= nsLayoutUtils::GetParentOrPlaceholderFor(child
);
257 MOZ_ASSERT(parent
, "aFrame is not a descendent of a block frame");
258 while (!IsFrameInCurrentLine(&mLineIterator
, mPrevFrame
, child
)) {
262 mLineIterator
.Next();
263 MOZ_ASSERT(hasNext
, "Can't find frame in lines!");
264 mPrevFrame
= nullptr;
267 mNextLineStart
= FirstChildOfNextLine(mLineIterator
);
270 // Advance line iterator to the line containing aFrame, which may
271 // require moving forward into overflow lines or into a later
272 // continuation (or both).
273 void AdvanceToLinesAndFrame(const FrameInfo
& aFrameInfo
) {
274 if (mLineIterator
.GetContainer() != aFrameInfo
.mBlockContainer
||
275 mLineIterator
.GetInOverflow() != aFrameInfo
.mInOverflow
) {
277 mLineIterator
.GetContainer() == aFrameInfo
.mBlockContainer
278 ? (!mLineIterator
.GetInOverflow() && aFrameInfo
.mInOverflow
)
279 : (!mLineIterator
.GetContainer() ||
280 AreContinuationsInOrder(mLineIterator
.GetContainer(),
281 aFrameInfo
.mBlockContainer
)),
282 "must move forwards");
283 nsBlockFrame
* block
= aFrameInfo
.mBlockContainer
;
284 nsLineList::iterator lines
=
285 aFrameInfo
.mInOverflow
? block
->GetOverflowLines()->mLines
.begin()
286 : block
->LinesBegin();
288 nsBlockInFlowLineIterator(block
, lines
, aFrameInfo
.mInOverflow
);
289 mPrevFrame
= nullptr;
291 AdvanceToFrame(aFrameInfo
.mFrame
);
295 FastLineIterator mCurrentTraverseLine
, mCurrentResolveLine
;
298 // Only used for NOISY debug output.
299 // Matches the current TraverseFrames state, not the ResolveParagraph
301 nsBlockFrame
* mCurrentBlock
;
304 explicit BidiParagraphData(nsBlockFrame
* aBlockFrame
)
305 : mPresContext(aBlockFrame
->PresContext()),
306 mIsVisual(mPresContext
->IsVisualMode()),
307 mRequiresBidi(false),
308 mParaLevel(nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame
->Style())),
309 mPrevContent(nullptr)
312 mCurrentBlock(aBlockFrame
)
315 if (mParaLevel
> 0) {
316 mRequiresBidi
= true;
321 * Drill up in content to detect whether this is an element that needs to
322 * be rendered with logical order even on visual pages.
324 * We always use logical order on form controls, firstly so that text
325 * entry will be in logical order, but also because visual pages were
326 * written with the assumption that even if the browser had no support
327 * for right-to-left text rendering, it would use native widgets with
328 * bidi support to display form controls.
330 * We also use logical order in XUL elements, since we expect that if a
331 * XUL element appears in a visual page, it will be generated by an XBL
332 * binding and contain localized text which will be in logical order.
334 for (nsIContent
* content
= aBlockFrame
->GetContent(); content
;
335 content
= content
->GetParent()) {
336 if (content
->IsXULElement() || content
->IsHTMLFormControlElement()) {
345 if (mPresContext
->BidiEngine().SetParagraph(mBuffer
, mParaLevel
).isErr()) {
346 return NS_ERROR_FAILURE
;
352 * mParaLevel can be BidiDirection::LTR as well as
353 * BidiDirection::LTR or BidiDirection::RTL.
354 * GetParagraphEmbeddingLevel() returns the actual (resolved) paragraph level
355 * which is always either BidiDirection::LTR or
358 BidiEmbeddingLevel
GetParagraphEmbeddingLevel() {
359 BidiEmbeddingLevel paraLevel
= mParaLevel
;
360 if (paraLevel
== BidiEmbeddingLevel::DefaultLTR() ||
361 paraLevel
== BidiEmbeddingLevel::DefaultRTL()) {
362 paraLevel
= mPresContext
->BidiEngine().GetParagraphEmbeddingLevel();
367 BidiEngine::ParagraphDirection
GetParagraphDirection() {
368 return mPresContext
->BidiEngine().GetParagraphDirection();
371 nsresult
CountRuns(int32_t* runCount
) {
372 auto result
= mPresContext
->BidiEngine().CountRuns();
373 if (result
.isErr()) {
374 return NS_ERROR_FAILURE
;
376 *runCount
= result
.unwrap();
380 void GetLogicalRun(int32_t aLogicalStart
, int32_t* aLogicalLimit
,
381 BidiEmbeddingLevel
* aLevel
) {
382 mPresContext
->BidiEngine().GetLogicalRun(aLogicalStart
, aLogicalLimit
,
385 *aLevel
= GetParagraphEmbeddingLevel();
390 mLogicalFrames
.Clear();
391 mContentToFrameIndex
.Clear();
392 mBuffer
.SetLength(0);
393 mPrevContent
= nullptr;
394 for (uint32_t i
= 0; i
< mEmbeddingStack
.Length(); ++i
) {
395 mBuffer
.Append(mEmbeddingStack
[i
]);
396 mLogicalFrames
.AppendElement(FrameInfo(BidiControlFrameType::Value
));
400 void AppendFrame(nsIFrame
* aFrame
, FastLineIterator
& aLineIter
,
401 nsIContent
* aContent
= nullptr) {
403 mContentToFrameIndex
.InsertOrUpdate(aContent
, FrameCount());
406 // We don't actually need to advance aLineIter to aFrame, since all we use
407 // from it is the block and is-overflow state, which are correct already.
408 mLogicalFrames
.AppendElement(FrameInfo(aFrame
, aLineIter
.mLineIterator
));
411 void AdvanceAndAppendFrame(nsIFrame
** aFrame
, FastLineIterator
& aLineIter
,
412 nsIFrame
** aNextSibling
) {
413 nsIFrame
* frame
= *aFrame
;
414 nsIFrame
* nextSibling
= *aNextSibling
;
416 frame
= frame
->GetNextContinuation();
418 AppendFrame(frame
, aLineIter
, nullptr);
421 * If we have already overshot the saved next-sibling while
422 * scanning the frame's continuations, advance it.
424 if (frame
== nextSibling
) {
425 nextSibling
= frame
->GetNextSibling();
430 *aNextSibling
= nextSibling
;
433 int32_t GetLastFrameForContent(nsIContent
* aContent
) {
434 return mContentToFrameIndex
.Get(aContent
);
437 int32_t FrameCount() { return mLogicalFrames
.Length(); }
439 int32_t BufferLength() { return mBuffer
.Length(); }
441 nsIFrame
* FrameAt(int32_t aIndex
) { return mLogicalFrames
[aIndex
].mFrame
; }
443 const FrameInfo
& FrameInfoAt(int32_t aIndex
) {
444 return mLogicalFrames
[aIndex
];
447 void AppendUnichar(char16_t aCh
) { mBuffer
.Append(aCh
); }
449 void AppendString(const nsDependentSubstring
& aString
) {
450 mBuffer
.Append(aString
);
453 void AppendControlChar(char16_t aCh
) {
454 mLogicalFrames
.AppendElement(FrameInfo(BidiControlFrameType::Value
));
458 void PushBidiControl(char16_t aCh
) {
459 AppendControlChar(aCh
);
460 mEmbeddingStack
.AppendElement(aCh
);
463 void AppendPopChar(char16_t aCh
) {
464 AppendControlChar(IsIsolateControl(aCh
) ? kPDI
: kPDF
);
467 void PopBidiControl(char16_t aCh
) {
468 MOZ_ASSERT(mEmbeddingStack
.Length(), "embedding/override underflow");
469 MOZ_ASSERT(aCh
== mEmbeddingStack
.LastElement());
471 mEmbeddingStack
.RemoveLastElement();
474 void ClearBidiControls() {
475 for (char16_t c
: Reversed(mEmbeddingStack
)) {
481 class MOZ_STACK_CLASS BidiLineData
{
483 BidiLineData(nsIFrame
* aFirstFrameOnLine
, int32_t aNumFramesOnLine
) {
484 // Initialize the logically-ordered array of frames using the top-level
485 // frames of a single line
486 auto appendFrame
= [&](nsIFrame
* frame
, BidiEmbeddingLevel level
) {
487 mLogicalFrames
.AppendElement(frame
);
488 mLevels
.AppendElement(level
);
489 mIndexMap
.AppendElement(0);
492 for (nsIFrame
* frame
= aFirstFrameOnLine
; frame
&& aNumFramesOnLine
--;
493 frame
= frame
->GetNextSibling()) {
494 FrameBidiData bidiData
= nsBidiPresUtils::GetFrameBidiData(frame
);
495 if (bidiData
.precedingControl
!= kBidiLevelNone
) {
496 appendFrame(NS_BIDI_CONTROL_FRAME
, bidiData
.precedingControl
);
498 appendFrame(frame
, bidiData
.embeddingLevel
);
502 BidiEngine::ReorderVisual(mLevels
.Elements(), mLevels
.Length(),
503 mIndexMap
.Elements());
505 // Collect the frames in visual order, omitting virtual controls
506 // and noting whether frames are reordered.
507 for (uint32_t i
= 0; i
< mIndexMap
.Length(); i
++) {
508 nsIFrame
* frame
= mLogicalFrames
[mIndexMap
[i
]];
509 if (frame
== NS_BIDI_CONTROL_FRAME
) {
512 mVisualFrameIndex
.AppendElement(mIndexMap
[i
]);
513 if (int32_t(i
) != mIndexMap
[i
]) {
519 uint32_t LogicalFrameCount() const { return mLogicalFrames
.Length(); }
520 uint32_t VisualFrameCount() const { return mVisualFrameIndex
.Length(); }
522 nsIFrame
* LogicalFrameAt(uint32_t aIndex
) const {
523 return mLogicalFrames
[aIndex
];
526 nsIFrame
* VisualFrameAt(uint32_t aIndex
) const {
527 return mLogicalFrames
[mVisualFrameIndex
[aIndex
]];
530 std::pair
<nsIFrame
*, BidiEmbeddingLevel
> VisualFrameAndLevelAt(
531 uint32_t aIndex
) const {
532 int32_t index
= mVisualFrameIndex
[aIndex
];
533 return std::pair(mLogicalFrames
[index
], mLevels
[index
]);
536 bool IsReordered() const { return mIsReordered
; }
538 void InitContinuationStates(nsContinuationStates
* aContinuationStates
) const {
539 for (auto* frame
: mLogicalFrames
) {
540 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
541 nsBidiPresUtils::InitContinuationStates(frame
, aContinuationStates
);
547 AutoTArray
<nsIFrame
*, 16> mLogicalFrames
;
548 AutoTArray
<int32_t, 16> mVisualFrameIndex
;
549 AutoTArray
<int32_t, 16> mIndexMap
;
550 AutoTArray
<BidiEmbeddingLevel
, 16> mLevels
;
551 bool mIsReordered
= false;
556 void MOZ_EXPORT
DumpBidiLine(BidiLineData
* aData
, bool aVisualOrder
) {
557 auto dump
= [](nsIFrame
* frame
) {
558 if (frame
== NS_BIDI_CONTROL_FRAME
) {
559 fprintf_stderr(stderr
, "(Bidi control frame)\n");
566 for (uint32_t i
= 0; i
< aData
->VisualFrameCount(); i
++) {
567 dump(aData
->VisualFrameAt(i
));
570 for (uint32_t i
= 0; i
< aData
->LogicalFrameCount(); i
++) {
571 dump(aData
->LogicalFrameAt(i
));
578 /* Some helper methods for Resolve() */
580 // Should this frame be split between text runs?
581 static bool IsBidiSplittable(nsIFrame
* aFrame
) {
583 // Bidi inline containers should be split, unless they're line frames.
584 LayoutFrameType frameType
= aFrame
->Type();
585 return (aFrame
->IsBidiInlineContainer() &&
586 frameType
!= LayoutFrameType::Line
) ||
587 frameType
== LayoutFrameType::Text
;
590 // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
591 static bool IsBidiLeaf(const nsIFrame
* aFrame
) {
592 nsIFrame
* kid
= aFrame
->PrincipalChildList().FirstChild();
594 if (aFrame
->IsBidiInlineContainer() ||
595 RubyUtils::IsRubyBox(aFrame
->Type())) {
603 * Create non-fluid continuations for the ancestors of a given frame all the way
604 * up the frame tree until we hit a non-splittable frame (a line or a block).
606 * @param aParent the first parent frame to be split
607 * @param aFrame the child frames after this frame are reparented to the
608 * newly-created continuation of aParent.
609 * If aFrame is null, all the children of aParent are reparented.
611 static void SplitInlineAncestors(nsContainerFrame
* aParent
,
612 nsLineList::iterator aLine
, nsIFrame
* aFrame
) {
613 PresShell
* presShell
= aParent
->PresShell();
614 nsIFrame
* frame
= aFrame
;
615 nsContainerFrame
* parent
= aParent
;
616 nsContainerFrame
* newParent
;
618 while (IsBidiSplittable(parent
)) {
619 nsContainerFrame
* grandparent
= parent
->GetParent();
620 NS_ASSERTION(grandparent
,
621 "Couldn't get parent's parent in "
622 "nsBidiPresUtils::SplitInlineAncestors");
624 // Split the child list after |frame|, unless it is the last child.
625 if (!frame
|| frame
->GetNextSibling()) {
626 newParent
= static_cast<nsContainerFrame
*>(
627 presShell
->FrameConstructor()->CreateContinuingFrame(
628 parent
, grandparent
, false));
630 nsFrameList tail
= parent
->StealFramesAfter(frame
);
632 // Reparent views as necessary
633 nsContainerFrame::ReparentFrameViewList(tail
, parent
, newParent
);
635 // The parent's continuation adopts the siblings after the split.
636 MOZ_ASSERT(!newParent
->IsBlockFrameOrSubclass(),
637 "blocks should not be IsBidiSplittable");
638 newParent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, nullptr,
639 nullptr, std::move(tail
));
641 // While passing &aLine to InsertFrames for a non-block isn't harmful
642 // because it's a no-op, it doesn't really make sense. However, the
643 // MOZ_ASSERT() we need to guarantee that it's safe only works if the
644 // parent is actually the block.
645 const nsLineList::iterator
* parentLine
;
646 if (grandparent
->IsBlockFrameOrSubclass()) {
647 MOZ_ASSERT(aLine
->Contains(parent
));
650 parentLine
= nullptr;
653 // The list name FrameChildListID::NoReflowPrincipal would indicate we
655 grandparent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, parent
,
656 parentLine
, nsFrameList(newParent
, newParent
));
660 parent
= grandparent
;
664 static void MakeContinuationFluid(nsIFrame
* aFrame
, nsIFrame
* aNext
) {
665 NS_ASSERTION(!aFrame
->GetNextInFlow() || aFrame
->GetNextInFlow() == aNext
,
666 "next-in-flow is not next continuation!");
667 aFrame
->SetNextInFlow(aNext
);
669 NS_ASSERTION(!aNext
->GetPrevInFlow() || aNext
->GetPrevInFlow() == aFrame
,
670 "prev-in-flow is not prev continuation!");
671 aNext
->SetPrevInFlow(aFrame
);
674 static void MakeContinuationsNonFluidUpParentChain(nsIFrame
* aFrame
,
679 for (frame
= aFrame
, next
= aNext
;
680 frame
&& next
&& next
!= frame
&& next
== frame
->GetNextInFlow() &&
681 IsBidiSplittable(frame
);
682 frame
= frame
->GetParent(), next
= next
->GetParent()) {
683 frame
->SetNextContinuation(next
);
684 next
->SetPrevContinuation(frame
);
688 // If aFrame is the last child of its parent, convert bidi continuations to
689 // fluid continuations for all of its inline ancestors.
690 // If it isn't the last child, make sure that its continuation is fluid.
691 static void JoinInlineAncestors(nsIFrame
* aFrame
) {
692 nsIFrame
* frame
= aFrame
;
693 while (frame
&& IsBidiSplittable(frame
)) {
694 nsIFrame
* next
= frame
->GetNextContinuation();
696 MakeContinuationFluid(frame
, next
);
698 // Join the parent only as long as we're its last child.
699 if (frame
->GetNextSibling()) break;
700 frame
= frame
->GetParent();
704 static void CreateContinuation(nsIFrame
* aFrame
,
705 const nsLineList::iterator aLine
,
706 nsIFrame
** aNewFrame
, bool aIsFluid
) {
707 MOZ_ASSERT(aNewFrame
, "null OUT ptr");
708 MOZ_ASSERT(aFrame
, "null ptr");
710 *aNewFrame
= nullptr;
712 nsPresContext
* presContext
= aFrame
->PresContext();
713 PresShell
* presShell
= presContext
->PresShell();
714 NS_ASSERTION(presShell
,
715 "PresShell must be set on PresContext before calling "
716 "nsBidiPresUtils::CreateContinuation");
718 nsContainerFrame
* parent
= aFrame
->GetParent();
721 "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
723 // While passing &aLine to InsertFrames for a non-block isn't harmful
724 // because it's a no-op, it doesn't really make sense. However, the
725 // MOZ_ASSERT() we need to guarantee that it's safe only works if the
726 // parent is actually the block.
727 const nsLineList::iterator
* parentLine
;
728 if (parent
->IsBlockFrameOrSubclass()) {
729 MOZ_ASSERT(aLine
->Contains(aFrame
));
732 parentLine
= nullptr;
735 // Have to special case floating first letter frames because the continuation
736 // doesn't go in the first letter frame. The continuation goes with the rest
737 // of the text that the first letter frame was made out of.
738 if (parent
->IsLetterFrame() && parent
->IsFloating()) {
739 nsFirstLetterFrame
* letterFrame
= do_QueryFrame(parent
);
740 letterFrame
->CreateContinuationForFloatingParent(aFrame
, aNewFrame
,
745 *aNewFrame
= presShell
->FrameConstructor()->CreateContinuingFrame(
746 aFrame
, parent
, aIsFluid
);
748 // The list name FrameChildListID::NoReflowPrincipal would indicate we don't
750 // XXXbz this needs higher-level framelist love
751 parent
->InsertFrames(FrameChildListID::NoReflowPrincipal
, aFrame
, parentLine
,
752 nsFrameList(*aNewFrame
, *aNewFrame
));
755 // Split inline ancestor frames
756 SplitInlineAncestors(parent
, aLine
, aFrame
);
761 * Overview of the implementation of Resolve():
763 * Walk through the descendants of aBlockFrame and build:
764 * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
765 * * mBuffer: an nsString containing a representation of
766 * the content of the frames.
767 * In the case of text frames, this is the actual text context of the
768 * frames, but some other elements are represented in a symbolic form which
769 * will make the Unicode Bidi Algorithm give the correct results.
770 * Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo>
771 * elements are represented by the corresponding Unicode control characters.
772 * <br> elements are represented by U+2028 LINE SEPARATOR
773 * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
776 * Then pass mBuffer to the Bidi engine for resolving of embedding levels
777 * by nsBidi::SetPara() and division into directional runs by
778 * nsBidi::CountRuns().
780 * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
781 * correlate them with the frames indexed in mLogicalFrames, setting the
782 * baseLevel and embeddingLevel properties according to the results returned
783 * by the Bidi engine.
785 * The rendering layer requires each text frame to contain text in only one
786 * direction, so we may need to call EnsureBidiContinuation() to split frames.
787 * We may also need to call RemoveBidiContinuation() to convert frames created
788 * by EnsureBidiContinuation() in previous reflows into fluid continuations.
790 nsresult
nsBidiPresUtils::Resolve(nsBlockFrame
* aBlockFrame
) {
791 BidiParagraphData
bpd(aBlockFrame
);
793 // Handle bidi-override being set on the block itself before calling
795 // No need to call GetBidiControl as well, because isolate and embed
796 // values of unicode-bidi property are redundant on block elements.
797 // unicode-bidi:plaintext on a block element is handled by block frame
798 // via using nsIFrame::GetWritingMode(nsIFrame*).
799 char16_t ch
= GetBidiOverride(aBlockFrame
->Style());
801 bpd
.PushBidiControl(ch
);
802 bpd
.mRequiresBidi
= true;
804 // If there are no unicode-bidi properties and no RTL characters in the
805 // block's content, then it is pure LTR and we can skip the rest of bidi
807 nsIContent
* currContent
= nullptr;
808 for (nsBlockFrame
* block
= aBlockFrame
; block
;
809 block
= static_cast<nsBlockFrame
*>(block
->GetNextContinuation())) {
810 block
->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
811 if (!bpd
.mRequiresBidi
&&
812 ChildListMayRequireBidi(block
->PrincipalChildList().FirstChild(),
814 bpd
.mRequiresBidi
= true;
816 if (!bpd
.mRequiresBidi
) {
817 nsBlockFrame::FrameLines
* overflowLines
= block
->GetOverflowLines();
819 if (ChildListMayRequireBidi(overflowLines
->mFrames
.FirstChild(),
821 bpd
.mRequiresBidi
= true;
826 if (!bpd
.mRequiresBidi
) {
831 for (nsBlockFrame
* block
= aBlockFrame
; block
;
832 block
= static_cast<nsBlockFrame
*>(block
->GetNextContinuation())) {
834 bpd
.mCurrentBlock
= block
;
836 block
->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
837 bpd
.mCurrentTraverseLine
.mLineIterator
=
838 nsBlockInFlowLineIterator(block
, block
->LinesBegin());
839 bpd
.mCurrentTraverseLine
.mPrevFrame
= nullptr;
840 TraverseFrames(block
->PrincipalChildList().FirstChild(), &bpd
);
841 nsBlockFrame::FrameLines
* overflowLines
= block
->GetOverflowLines();
843 bpd
.mCurrentTraverseLine
.mLineIterator
=
844 nsBlockInFlowLineIterator(block
, overflowLines
->mLines
.begin(), true);
845 bpd
.mCurrentTraverseLine
.mPrevFrame
= nullptr;
846 TraverseFrames(overflowLines
->mFrames
.FirstChild(), &bpd
);
851 bpd
.PopBidiControl(ch
);
854 return ResolveParagraph(&bpd
);
857 // In ResolveParagraph, we previously used ReplaceChar(kSeparators, kSpace)
858 // to convert separators to spaces, but this hard-coded implementation is
859 // substantially faster than the general-purpose ReplaceChar function.
860 // This must be kept in sync with the definition of kSeparators.
861 static inline void ReplaceSeparators(nsString
& aText
, size_t aStartIndex
= 0) {
862 for (char16_t
* cp
= aText
.BeginWriting() + aStartIndex
;
863 cp
< aText
.EndWriting(); cp
++) {
864 if (MOZ_UNLIKELY(*cp
< char16_t(' '))) {
865 static constexpr char16_t SeparatorToSpace
[32] = {
866 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ' ', ' ',
867 ' ', 0x0c, ' ', 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
868 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, ' ', ' ', ' ', ' ',
870 *cp
= SeparatorToSpace
[*cp
];
871 } else if (MOZ_UNLIKELY(*cp
== 0x0085 || *cp
== 0x2026)) {
877 nsresult
nsBidiPresUtils::ResolveParagraph(BidiParagraphData
* aBpd
) {
878 if (aBpd
->BufferLength() < 1) {
882 ReplaceSeparators(aBpd
->mBuffer
);
886 nsresult rv
= aBpd
->SetPara();
887 NS_ENSURE_SUCCESS(rv
, rv
);
889 BidiEmbeddingLevel embeddingLevel
= aBpd
->GetParagraphEmbeddingLevel();
891 rv
= aBpd
->CountRuns(&runCount
);
892 NS_ENSURE_SUCCESS(rv
, rv
);
894 int32_t runLength
= 0; // the length of the current run of text
895 int32_t logicalLimit
= 0; // the end of the current run + 1
897 int32_t fragmentLength
= 0; // the length of the current text frame
898 int32_t frameIndex
= -1; // index to the frames in mLogicalFrames
899 int32_t frameCount
= aBpd
->FrameCount();
900 int32_t contentOffset
= 0; // offset of current frame in its content node
901 bool isTextFrame
= false;
902 nsIFrame
* frame
= nullptr;
903 BidiParagraphData::FrameInfo frameInfo
;
904 nsIContent
* content
= nullptr;
905 int32_t contentTextLength
= 0;
910 "Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, "
912 (void*)aBpd
->mCurrentBlock
, NS_ConvertUTF16toUTF8(aBpd
->mBuffer
).get(),
913 frameCount
, runCount
);
914 # ifdef REALLY_NOISY_BIDI
915 printf(" block frame tree=:\n");
916 aBpd
->mCurrentBlock
->List(stdout
);
921 if (runCount
== 1 && frameCount
== 1 &&
922 aBpd
->GetParagraphDirection() == BidiEngine::ParagraphDirection::LTR
&&
923 aBpd
->GetParagraphEmbeddingLevel() == 0) {
924 // We have a single left-to-right frame in a left-to-right paragraph,
925 // without bidi isolation from the surrounding text.
926 // Make sure that the embedding level and base level frame properties aren't
927 // set (because if they are this frame used to have some other direction,
928 // so we can't do this optimization), and we're done.
929 nsIFrame
* frame
= aBpd
->FrameAt(0);
930 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
931 FrameBidiData bidiData
= frame
->GetBidiData();
932 if (!bidiData
.embeddingLevel
&& !bidiData
.baseLevel
) {
935 printf("early return for single direction frame %p\n", (void*)frame
);
938 frame
->AddStateBits(NS_FRAME_IS_BIDI
);
944 BidiParagraphData::FrameInfo lastRealFrame
;
945 BidiEmbeddingLevel lastEmbeddingLevel
= kBidiLevelNone
;
946 BidiEmbeddingLevel precedingControl
= kBidiLevelNone
;
948 auto storeBidiDataToFrame
= [&]() {
949 FrameBidiData bidiData
;
950 bidiData
.embeddingLevel
= embeddingLevel
;
951 bidiData
.baseLevel
= aBpd
->GetParagraphEmbeddingLevel();
952 // If a control character doesn't have a lower embedding level than
953 // both the preceding and the following frame, it isn't something
954 // needed for getting the correct result. This optimization should
955 // remove almost all of embeds and overrides, and some of isolates.
956 if (precedingControl
>= embeddingLevel
||
957 precedingControl
>= lastEmbeddingLevel
) {
958 bidiData
.precedingControl
= kBidiLevelNone
;
960 bidiData
.precedingControl
= precedingControl
;
962 precedingControl
= kBidiLevelNone
;
963 lastEmbeddingLevel
= embeddingLevel
;
964 frame
->SetProperty(nsIFrame::BidiDataProperty(), bidiData
);
968 if (fragmentLength
<= 0) {
969 // Get the next frame from mLogicalFrames
970 if (++frameIndex
>= frameCount
) {
973 frameInfo
= aBpd
->FrameInfoAt(frameIndex
);
974 frame
= frameInfo
.mFrame
;
975 if (frame
== NS_BIDI_CONTROL_FRAME
|| !frame
->IsTextFrame()) {
977 * Any non-text frame corresponds to a single character in the text
978 * buffer (a bidi control character, LINE SEPARATOR, or OBJECT
984 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(frameInfo
);
985 content
= frame
->GetContent();
990 contentTextLength
= content
->TextLength();
991 auto [start
, end
] = frame
->GetOffsets();
992 NS_ASSERTION(!(contentTextLength
< end
- start
),
993 "Frame offsets don't fit in content");
994 fragmentLength
= std::min(contentTextLength
, end
- start
);
995 contentOffset
= start
;
998 } // if (fragmentLength <= 0)
1000 if (runLength
<= 0) {
1001 // Get the next run of text from the Bidi engine
1002 if (++numRun
>= runCount
) {
1003 // We've run out of runs of text; but don't forget to store bidi data
1004 // to the frame before breaking out of the loop (bug 1426042).
1005 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1006 storeBidiDataToFrame();
1008 frame
->AdjustOffsetsForBidi(contentOffset
,
1009 contentOffset
+ fragmentLength
);
1014 int32_t lineOffset
= logicalLimit
;
1015 aBpd
->GetLogicalRun(lineOffset
, &logicalLimit
, &embeddingLevel
);
1016 runLength
= logicalLimit
- lineOffset
;
1017 } // if (runLength <= 0)
1019 if (frame
== NS_BIDI_CONTROL_FRAME
) {
1020 // In theory, we only need to do this for isolates. However, it is
1021 // easier to do this for all here because we do not maintain the
1022 // index to get corresponding character from buffer. Since we do
1023 // have proper embedding level for all those characters, including
1024 // them wouldn't affect the final result.
1025 precedingControl
= std::min(precedingControl
, embeddingLevel
);
1027 storeBidiDataToFrame();
1029 if (contentTextLength
== 0) {
1030 // Set the base level and embedding level of the current run even
1031 // on an empty frame. Otherwise frame reordering will not be correct.
1032 frame
->AdjustOffsetsForBidi(0, 0);
1033 // Nothing more to do for an empty frame, except update
1034 // lastRealFrame like we do below.
1035 lastRealFrame
= frameInfo
;
1038 nsLineList::iterator currentLine
= aBpd
->mCurrentResolveLine
.GetLine();
1039 if ((runLength
> 0) && (runLength
< fragmentLength
)) {
1041 * The text in this frame continues beyond the end of this directional
1042 * run. Create a non-fluid continuation frame for the next directional
1045 currentLine
->MarkDirty();
1047 int32_t runEnd
= contentOffset
+ runLength
;
1048 EnsureBidiContinuation(frame
, currentLine
, &nextBidi
, contentOffset
,
1050 nextBidi
->AdjustOffsetsForBidi(runEnd
,
1051 contentOffset
+ fragmentLength
);
1053 frameInfo
.mFrame
= frame
;
1054 contentOffset
= runEnd
;
1056 aBpd
->mCurrentResolveLine
.AdvanceToFrame(frame
);
1057 } // if (runLength < fragmentLength)
1059 if (contentOffset
+ fragmentLength
== contentTextLength
) {
1061 * We have finished all the text in this content node. Convert any
1062 * further non-fluid continuations to fluid continuations and
1063 * advance frameIndex to the last frame in the content node
1065 int32_t newIndex
= aBpd
->GetLastFrameForContent(content
);
1066 if (newIndex
> frameIndex
) {
1067 currentLine
->MarkDirty();
1068 RemoveBidiContinuation(aBpd
, frame
, frameIndex
, newIndex
);
1069 frameIndex
= newIndex
;
1070 frameInfo
= aBpd
->FrameInfoAt(frameIndex
);
1071 frame
= frameInfo
.mFrame
;
1073 } else if (fragmentLength
> 0 && runLength
> fragmentLength
) {
1075 * There is more text that belongs to this directional run in the
1076 * next text frame: make sure it is a fluid continuation of the
1077 * current frame. Do not advance frameIndex, because the next frame
1078 * may contain multi-directional text and need to be split
1080 int32_t newIndex
= frameIndex
;
1082 } while (++newIndex
< frameCount
&&
1083 aBpd
->FrameAt(newIndex
) == NS_BIDI_CONTROL_FRAME
);
1084 if (newIndex
< frameCount
) {
1085 currentLine
->MarkDirty();
1086 RemoveBidiContinuation(aBpd
, frame
, frameIndex
, newIndex
);
1088 } else if (runLength
== fragmentLength
) {
1090 * If the directional run ends at the end of the frame, make sure
1091 * that any continuation is non-fluid, and do the same up the
1094 nsIFrame
* next
= frame
->GetNextInFlow();
1096 currentLine
->MarkDirty();
1097 MakeContinuationsNonFluidUpParentChain(frame
, next
);
1100 frame
->AdjustOffsetsForBidi(contentOffset
,
1101 contentOffset
+ fragmentLength
);
1104 } // not bidi control frame
1105 int32_t temp
= runLength
;
1106 runLength
-= fragmentLength
;
1107 fragmentLength
-= temp
;
1109 // Record last real frame so that we can do splitting properly even
1110 // if a run ends after a virtual bidi control frame.
1111 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1112 lastRealFrame
= frameInfo
;
1114 if (lastRealFrame
.mFrame
&& fragmentLength
<= 0) {
1115 // If the frame is at the end of a run, and this is not the end of our
1116 // paragraph, split all ancestor inlines that need splitting.
1117 // To determine whether we're at the end of the run, we check that we've
1118 // finished processing the current run, and that the current frame
1119 // doesn't have a fluid continuation (it could have a fluid continuation
1120 // of zero length, so testing runLength alone is not sufficient).
1121 if (runLength
<= 0 && !lastRealFrame
.mFrame
->GetNextInFlow()) {
1122 if (numRun
+ 1 < runCount
) {
1123 nsIFrame
* child
= lastRealFrame
.mFrame
;
1124 nsContainerFrame
* parent
= child
->GetParent();
1125 // As long as we're on the last sibling, the parent doesn't have to
1127 // However, if the parent has a fluid continuation, we do have to make
1128 // it non-fluid. This can happen e.g. when we have a first-letter
1129 // frame and the end of the first-letter coincides with the end of a
1131 while (parent
&& IsBidiSplittable(parent
) &&
1132 !child
->GetNextSibling()) {
1133 nsIFrame
* next
= parent
->GetNextInFlow();
1135 parent
->SetNextContinuation(next
);
1136 next
->SetPrevContinuation(parent
);
1139 parent
= child
->GetParent();
1141 if (parent
&& IsBidiSplittable(parent
)) {
1142 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(lastRealFrame
);
1143 SplitInlineAncestors(parent
, aBpd
->mCurrentResolveLine
.GetLine(),
1146 aBpd
->mCurrentResolveLine
.AdvanceToLinesAndFrame(lastRealFrame
);
1149 } else if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1150 // We're not at an end of a run. If |frame| is the last child of its
1151 // parent, and its ancestors happen to have bidi continuations, convert
1152 // them into fluid continuations.
1153 JoinInlineAncestors(frame
);
1159 # ifdef REALLY_NOISY_BIDI
1160 printf("---\nAfter Resolve(), frameTree =:\n");
1161 aBpd
->mCurrentBlock
->List(stdout
);
1169 void nsBidiPresUtils::TraverseFrames(nsIFrame
* aCurrentFrame
,
1170 BidiParagraphData
* aBpd
) {
1171 if (!aCurrentFrame
) return;
1174 nsBlockFrame
* initialLineContainer
=
1175 aBpd
->mCurrentTraverseLine
.mLineIterator
.GetContainer();
1178 nsIFrame
* childFrame
= aCurrentFrame
;
1181 * It's important to get the next sibling and next continuation *before*
1182 * handling the frame: If we encounter a forced paragraph break and call
1183 * ResolveParagraph within this loop, doing GetNextSibling and
1184 * GetNextContinuation after that could return a bidi continuation that had
1185 * just been split from the original childFrame and we would process it
1188 nsIFrame
* nextSibling
= childFrame
->GetNextSibling();
1190 // If the real frame for a placeholder is a first letter frame, we need to
1191 // drill down into it and include its contents in Bidi resolution.
1192 // If not, we just use the placeholder.
1193 nsIFrame
* frame
= childFrame
;
1194 if (childFrame
->IsPlaceholderFrame()) {
1195 nsIFrame
* realFrame
=
1196 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame
);
1197 if (realFrame
->IsLetterFrame()) {
1202 auto DifferentBidiValues
= [](ComputedStyle
* aSC1
, nsIFrame
* aFrame2
) {
1203 ComputedStyle
* sc2
= aFrame2
->Style();
1204 return GetBidiControl(aSC1
) != GetBidiControl(sc2
) ||
1205 GetBidiOverride(aSC1
) != GetBidiOverride(sc2
);
1208 ComputedStyle
* sc
= frame
->Style();
1209 nsIFrame
* nextContinuation
= frame
->GetNextContinuation();
1210 nsIFrame
* prevContinuation
= frame
->GetPrevContinuation();
1212 !nextContinuation
|| DifferentBidiValues(sc
, nextContinuation
);
1214 !prevContinuation
|| DifferentBidiValues(sc
, prevContinuation
);
1216 char16_t controlChar
= 0;
1217 char16_t overrideChar
= 0;
1218 LayoutFrameType frameType
= frame
->Type();
1219 if (frame
->IsBidiInlineContainer() || RubyUtils::IsRubyBox(frameType
)) {
1220 if (!frame
->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
1221 nsContainerFrame
* c
= static_cast<nsContainerFrame
*>(frame
);
1222 MOZ_ASSERT(c
== do_QueryFrame(frame
),
1223 "eBidiInlineContainer and ruby frame must be"
1224 " a nsContainerFrame subclass");
1225 c
->DrainSelfOverflowList();
1228 controlChar
= GetBidiControl(sc
);
1229 overrideChar
= GetBidiOverride(sc
);
1231 // Add dummy frame pointers representing bidi control codes before
1232 // the first frames of elements specifying override, isolation, or
1235 if (controlChar
!= 0) {
1236 aBpd
->PushBidiControl(controlChar
);
1238 if (overrideChar
!= 0) {
1239 aBpd
->PushBidiControl(overrideChar
);
1244 if (IsBidiLeaf(frame
)) {
1245 /* Bidi leaf frame: add the frame to the mLogicalFrames array,
1246 * and add its index to the mContentToFrameIndex hashtable. This
1247 * will be used in RemoveBidiContinuation() to identify the last
1248 * frame in the array with a given content.
1250 nsIContent
* content
= frame
->GetContent();
1251 aBpd
->AppendFrame(frame
, aBpd
->mCurrentTraverseLine
, content
);
1253 // Append the content of the frame to the paragraph buffer
1254 if (LayoutFrameType::Text
== frameType
) {
1255 if (content
!= aBpd
->mPrevContent
) {
1256 aBpd
->mPrevContent
= content
;
1257 if (!frame
->StyleText()->NewlineIsSignificant(
1258 static_cast<nsTextFrame
*>(frame
))) {
1259 content
->GetAsText()->AppendTextTo(aBpd
->mBuffer
);
1262 * For preformatted text we have to do bidi resolution on each line
1266 content
->GetAsText()->AppendTextTo(text
);
1271 auto [start
, end
] = frame
->GetOffsets();
1272 int32_t endLine
= text
.FindChar('\n', start
);
1273 if (endLine
== -1) {
1275 * If there is no newline in the text content, just save the
1276 * text from this frame and its continuations, and do bidi
1279 aBpd
->AppendString(Substring(text
, start
));
1280 while (frame
&& nextSibling
) {
1281 aBpd
->AdvanceAndAppendFrame(
1282 &frame
, aBpd
->mCurrentTraverseLine
, &nextSibling
);
1288 * If there is a newline in the frame, break the frame after the
1289 * newline, do bidi resolution and repeat until the last sibling
1294 * If the frame ends before the new line, save the text and move
1295 * into the next continuation
1298 Substring(text
, start
, std::min(end
, endLine
) - start
));
1299 while (end
< endLine
&& nextSibling
) {
1300 aBpd
->AdvanceAndAppendFrame(&frame
, aBpd
->mCurrentTraverseLine
,
1302 NS_ASSERTION(frame
, "Premature end of continuation chain");
1303 std::tie(start
, end
) = frame
->GetOffsets();
1305 Substring(text
, start
, std::min(end
, endLine
) - start
));
1308 if (end
< endLine
) {
1309 aBpd
->mPrevContent
= nullptr;
1313 bool createdContinuation
= false;
1314 if (uint32_t(endLine
) < text
.Length()) {
1316 * Timing is everything here: if the frame already has a bidi
1317 * continuation, we need to make the continuation fluid *before*
1318 * resetting the length of the current frame. Otherwise
1319 * nsTextFrame::SetLength won't set the continuation frame's
1320 * text offsets correctly.
1322 * On the other hand, if the frame doesn't have a continuation,
1323 * we need to create one *after* resetting the length, or
1324 * CreateContinuingFrame will complain that there is no more
1325 * content for the continuation.
1327 next
= frame
->GetNextInFlow();
1329 // If the frame already has a bidi continuation, make it fluid
1330 next
= frame
->GetNextContinuation();
1332 MakeContinuationFluid(frame
, next
);
1333 JoinInlineAncestors(frame
);
1337 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(frame
);
1338 textFrame
->SetLength(endLine
- start
, nullptr);
1340 // If it weren't for CreateContinuation needing this to
1341 // be current, we could restructure the marking dirty
1342 // below to use mCurrentResolveLine and eliminate
1343 // mCurrentTraverseLine entirely.
1344 aBpd
->mCurrentTraverseLine
.AdvanceToFrame(frame
);
1347 // If the frame has no next in flow, create one.
1349 frame
, aBpd
->mCurrentTraverseLine
.GetLine(), &next
, true);
1350 createdContinuation
= true;
1352 // Mark the line before the newline as dirty.
1353 aBpd
->mCurrentTraverseLine
.GetLine()->MarkDirty();
1355 ResolveParagraphWithinBlock(aBpd
);
1357 if (!nextSibling
&& !createdContinuation
) {
1362 aBpd
->AppendFrame(frame
, aBpd
->mCurrentTraverseLine
);
1363 // Mark the line after the newline as dirty.
1364 aBpd
->mCurrentTraverseLine
.AdvanceToFrame(frame
);
1365 aBpd
->mCurrentTraverseLine
.GetLine()->MarkDirty();
1369 * If we have already overshot the saved next-sibling while
1370 * scanning the frame's continuations, advance it.
1372 if (frame
&& frame
== nextSibling
) {
1373 nextSibling
= frame
->GetNextSibling();
1379 } else if (LayoutFrameType::Br
== frameType
) {
1380 // break frame -- append line separator
1381 aBpd
->AppendUnichar(kLineSeparator
);
1382 ResolveParagraphWithinBlock(aBpd
);
1384 // other frame type -- see the Unicode Bidi Algorithm:
1385 // "...inline objects (such as graphics) are treated as if they are ...
1387 // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
1388 // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1
1389 aBpd
->AppendUnichar(
1390 content
->IsHTMLElement(nsGkAtoms::wbr
) ? kZWSP
: kObjectSubstitute
);
1391 if (!frame
->IsInlineOutside()) {
1392 // if it is not inline, end the paragraph
1393 ResolveParagraphWithinBlock(aBpd
);
1397 // For a non-leaf frame, recurse into TraverseFrames
1398 nsIFrame
* kid
= frame
->PrincipalChildList().FirstChild();
1399 MOZ_ASSERT(!frame
->GetChildList(FrameChildListID::Overflow
).FirstChild(),
1400 "should have drained the overflow list above");
1402 TraverseFrames(kid
, aBpd
);
1406 // If the element is attributed by dir, indicate direction pop (add PDF
1409 // Add a dummy frame pointer representing a bidi control code after the
1410 // last frame of an element specifying embedding or override
1411 if (overrideChar
!= 0) {
1412 aBpd
->PopBidiControl(overrideChar
);
1414 if (controlChar
!= 0) {
1415 aBpd
->PopBidiControl(controlChar
);
1418 childFrame
= nextSibling
;
1419 } while (childFrame
);
1421 MOZ_ASSERT(initialLineContainer
==
1422 aBpd
->mCurrentTraverseLine
.mLineIterator
.GetContainer());
1425 bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame
* aFirstChild
,
1426 nsIContent
** aCurrContent
) {
1427 MOZ_ASSERT(!aFirstChild
|| !aFirstChild
->GetPrevSibling(),
1428 "Expecting to traverse from the start of a child list");
1430 for (nsIFrame
* childFrame
= aFirstChild
; childFrame
;
1431 childFrame
= childFrame
->GetNextSibling()) {
1432 nsIFrame
* frame
= childFrame
;
1434 // If the real frame for a placeholder is a first-letter frame, we need to
1435 // consider its contents for potential Bidi resolution.
1436 if (childFrame
->IsPlaceholderFrame()) {
1437 nsIFrame
* realFrame
=
1438 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame
);
1439 if (realFrame
->IsLetterFrame()) {
1444 // If unicode-bidi properties are present, we should do bidi resolution.
1445 ComputedStyle
* sc
= frame
->Style();
1446 if (GetBidiControl(sc
) || GetBidiOverride(sc
)) {
1450 if (IsBidiLeaf(frame
)) {
1451 if (frame
->IsTextFrame()) {
1452 // If the frame already has a BidiDataProperty, we know we need to
1453 // perform bidi resolution (even if no bidi content is NOW present --
1454 // we might need to remove the property set by a previous reflow, if
1455 // content has changed; see bug 1366623).
1456 if (frame
->HasProperty(nsIFrame::BidiDataProperty())) {
1460 // Check whether the text frame has any RTL characters; if so, bidi
1461 // resolution will be needed.
1462 dom::Text
* content
= frame
->GetContent()->AsText();
1463 if (content
!= *aCurrContent
) {
1464 *aCurrContent
= content
;
1465 const nsTextFragment
* txt
= &content
->TextFragment();
1467 HasRTLChars(Span(txt
->Get2b(), txt
->GetLength()))) {
1472 } else if (ChildListMayRequireBidi(frame
->PrincipalChildList().FirstChild(),
1481 void nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData
* aBpd
) {
1482 aBpd
->ClearBidiControls();
1483 ResolveParagraph(aBpd
);
1488 nscoord
nsBidiPresUtils::ReorderFrames(nsIFrame
* aFirstFrameOnLine
,
1489 int32_t aNumFramesOnLine
,
1490 WritingMode aLineWM
,
1491 const nsSize
& aContainerSize
,
1493 nsSize
containerSize(aContainerSize
);
1495 // If this line consists of a line frame, reorder the line frame's children.
1496 if (aFirstFrameOnLine
->IsLineFrame()) {
1497 // The line frame is positioned at the start-edge, so use its size
1498 // as the container size.
1499 containerSize
= aFirstFrameOnLine
->GetSize();
1501 aFirstFrameOnLine
= aFirstFrameOnLine
->PrincipalChildList().FirstChild();
1502 if (!aFirstFrameOnLine
) {
1505 // All children of the line frame are on the first line. Setting
1506 // aNumFramesOnLine to -1 makes InitLogicalArrayFromLine look at all of
1508 aNumFramesOnLine
= -1;
1509 // As the line frame itself has been adjusted at its inline-start position
1510 // by the caller, we do not want to apply this to its children.
1514 // No need to bidi-reorder the line if there's only a single frame.
1515 if (aNumFramesOnLine
== 1) {
1516 auto bidiData
= nsBidiPresUtils::GetFrameBidiData(aFirstFrameOnLine
);
1517 nsContinuationStates continuationStates
;
1518 InitContinuationStates(aFirstFrameOnLine
, &continuationStates
);
1519 return aStart
+ RepositionFrame(aFirstFrameOnLine
,
1520 bidiData
.embeddingLevel
.IsLTR(), aStart
,
1521 &continuationStates
, aLineWM
, false,
1525 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1526 return RepositionInlineFrames(bld
, aLineWM
, containerSize
, aStart
);
1529 nsIFrame
* nsBidiPresUtils::GetFirstLeaf(nsIFrame
* aFrame
) {
1530 nsIFrame
* firstLeaf
= aFrame
;
1531 while (!IsBidiLeaf(firstLeaf
)) {
1532 nsIFrame
* firstChild
= firstLeaf
->PrincipalChildList().FirstChild();
1533 nsIFrame
* realFrame
= nsPlaceholderFrame::GetRealFrameFor(firstChild
);
1534 firstLeaf
= (realFrame
->IsLetterFrame()) ? realFrame
: firstChild
;
1539 FrameBidiData
nsBidiPresUtils::GetFrameBidiData(nsIFrame
* aFrame
) {
1540 return GetFirstLeaf(aFrame
)->GetBidiData();
1543 BidiEmbeddingLevel
nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame
* aFrame
) {
1544 return GetFirstLeaf(aFrame
)->GetEmbeddingLevel();
1547 BidiEmbeddingLevel
nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame
* aFrame
) {
1548 const nsIFrame
* firstLeaf
= aFrame
;
1549 while (!IsBidiLeaf(firstLeaf
)) {
1550 firstLeaf
= firstLeaf
->PrincipalChildList().FirstChild();
1552 return firstLeaf
->GetBaseLevel();
1555 void nsBidiPresUtils::IsFirstOrLast(nsIFrame
* aFrame
,
1556 nsContinuationStates
* aContinuationStates
,
1557 bool aSpanDirMatchesLineDir
,
1558 bool& aIsFirst
/* out */,
1559 bool& aIsLast
/* out */) {
1561 * Since we lay out frames in the line's direction, visiting a frame with
1562 * 'mFirstVisualFrame == nullptr', means it's the first appearance of one
1563 * of its continuation chain frames on the line.
1564 * To determine if it's the last visual frame of its continuation chain on
1565 * the line or not, we count the number of frames of the chain on the line,
1566 * and then reduce it when we lay out a frame of the chain. If this value
1567 * becomes 1 it means that it's the last visual frame of its continuation
1568 * chain on this line.
1571 bool firstInLineOrder
, lastInLineOrder
;
1572 nsFrameContinuationState
* frameState
= aContinuationStates
->Get(aFrame
);
1573 nsFrameContinuationState
* firstFrameState
;
1575 if (!frameState
->mFirstVisualFrame
) {
1576 // aFrame is the first visual frame of its continuation chain
1577 nsFrameContinuationState
* contState
;
1580 frameState
->mFrameCount
= 1;
1581 frameState
->mFirstVisualFrame
= aFrame
;
1584 * Traverse continuation chain of aFrame in both backward and forward
1585 * directions while the frames are on this line. Count the frames and
1586 * set their mFirstVisualFrame to aFrame.
1588 // Traverse continuation chain backward
1589 for (frame
= aFrame
->GetPrevContinuation();
1590 frame
&& (contState
= aContinuationStates
->Get(frame
));
1591 frame
= frame
->GetPrevContinuation()) {
1592 frameState
->mFrameCount
++;
1593 contState
->mFirstVisualFrame
= aFrame
;
1595 frameState
->mHasContOnPrevLines
= (frame
!= nullptr);
1597 // Traverse continuation chain forward
1598 for (frame
= aFrame
->GetNextContinuation();
1599 frame
&& (contState
= aContinuationStates
->Get(frame
));
1600 frame
= frame
->GetNextContinuation()) {
1601 frameState
->mFrameCount
++;
1602 contState
->mFirstVisualFrame
= aFrame
;
1604 frameState
->mHasContOnNextLines
= (frame
!= nullptr);
1606 firstInLineOrder
= true;
1607 firstFrameState
= frameState
;
1609 // aFrame is not the first visual frame of its continuation chain
1610 firstInLineOrder
= false;
1611 firstFrameState
= aContinuationStates
->Get(frameState
->mFirstVisualFrame
);
1614 lastInLineOrder
= (firstFrameState
->mFrameCount
== 1);
1616 if (aSpanDirMatchesLineDir
) {
1617 aIsFirst
= firstInLineOrder
;
1618 aIsLast
= lastInLineOrder
;
1620 aIsFirst
= lastInLineOrder
;
1621 aIsLast
= firstInLineOrder
;
1624 if (frameState
->mHasContOnPrevLines
) {
1627 if (firstFrameState
->mHasContOnNextLines
) {
1631 if ((aIsFirst
|| aIsLast
) &&
1632 aFrame
->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT
)) {
1633 // For ib splits, don't treat anything except the last part as
1634 // endmost or anything except the first part as startmost.
1635 // As an optimization, only get the first continuation once.
1636 nsIFrame
* firstContinuation
= aFrame
->FirstContinuation();
1637 if (firstContinuation
->FrameIsNonLastInIBSplit()) {
1638 // We are not endmost
1641 if (firstContinuation
->FrameIsNonFirstInIBSplit()) {
1642 // We are not startmost
1647 // Reduce number of remaining frames of the continuation chain on the line.
1648 firstFrameState
->mFrameCount
--;
1650 nsInlineFrame
* testFrame
= do_QueryFrame(aFrame
);
1653 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET
);
1656 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST
);
1658 aFrame
->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST
);
1662 aFrame
->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST
);
1664 aFrame
->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST
);
1670 void nsBidiPresUtils::RepositionRubyContentFrame(
1671 nsIFrame
* aFrame
, WritingMode aFrameWM
,
1672 const LogicalMargin
& aBorderPadding
) {
1673 const nsFrameList
& childList
= aFrame
->PrincipalChildList();
1674 if (childList
.IsEmpty()) {
1678 // Reorder the children.
1680 ReorderFrames(childList
.FirstChild(), childList
.GetLength(), aFrameWM
,
1681 aFrame
->GetSize(), aBorderPadding
.IStart(aFrameWM
));
1682 isize
+= aBorderPadding
.IEnd(aFrameWM
);
1684 if (aFrame
->StyleText()->mRubyAlign
== StyleRubyAlign::Start
) {
1687 nscoord residualISize
= aFrame
->ISize(aFrameWM
) - isize
;
1688 if (residualISize
<= 0) {
1692 // When ruby-align is not "start", if the content does not fill this
1693 // frame, we need to center the children.
1694 const nsSize dummyContainerSize
;
1695 for (nsIFrame
* child
: childList
) {
1696 LogicalRect rect
= child
->GetLogicalRect(aFrameWM
, dummyContainerSize
);
1697 rect
.IStart(aFrameWM
) += residualISize
/ 2;
1698 child
->SetRect(aFrameWM
, rect
, dummyContainerSize
);
1703 nscoord
nsBidiPresUtils::RepositionRubyFrame(
1704 nsIFrame
* aFrame
, nsContinuationStates
* aContinuationStates
,
1705 const WritingMode aContainerWM
, const LogicalMargin
& aBorderPadding
) {
1706 LayoutFrameType frameType
= aFrame
->Type();
1707 MOZ_ASSERT(RubyUtils::IsRubyBox(frameType
));
1710 WritingMode frameWM
= aFrame
->GetWritingMode();
1711 bool isLTR
= frameWM
.IsBidiLTR();
1712 nsSize frameSize
= aFrame
->GetSize();
1713 if (frameType
== LayoutFrameType::Ruby
) {
1714 icoord
+= aBorderPadding
.IStart(frameWM
);
1715 // Reposition ruby segments in a ruby container
1716 for (RubySegmentEnumerator
e(static_cast<nsRubyFrame
*>(aFrame
)); !e
.AtEnd();
1718 nsRubyBaseContainerFrame
* rbc
= e
.GetBaseContainer();
1719 AutoRubyTextContainerArray
textContainers(rbc
);
1721 nscoord segmentISize
= RepositionFrame(
1722 rbc
, isLTR
, icoord
, aContinuationStates
, frameWM
, false, frameSize
);
1723 for (nsRubyTextContainerFrame
* rtc
: textContainers
) {
1724 nscoord isize
= RepositionFrame(rtc
, isLTR
, icoord
, aContinuationStates
,
1725 frameWM
, false, frameSize
);
1726 segmentISize
= std::max(segmentISize
, isize
);
1728 icoord
+= segmentISize
;
1730 icoord
+= aBorderPadding
.IEnd(frameWM
);
1731 } else if (frameType
== LayoutFrameType::RubyBaseContainer
) {
1732 // Reposition ruby columns in a ruby segment
1733 auto rbc
= static_cast<nsRubyBaseContainerFrame
*>(aFrame
);
1734 AutoRubyTextContainerArray
textContainers(rbc
);
1736 for (RubyColumnEnumerator
e(rbc
, textContainers
); !e
.AtEnd(); e
.Next()) {
1738 e
.GetColumn(column
);
1740 nscoord columnISize
=
1741 RepositionFrame(column
.mBaseFrame
, isLTR
, icoord
, aContinuationStates
,
1742 frameWM
, false, frameSize
);
1743 for (nsRubyTextFrame
* rt
: column
.mTextFrames
) {
1744 nscoord isize
= RepositionFrame(rt
, isLTR
, icoord
, aContinuationStates
,
1745 frameWM
, false, frameSize
);
1746 columnISize
= std::max(columnISize
, isize
);
1748 icoord
+= columnISize
;
1751 if (frameType
== LayoutFrameType::RubyBase
||
1752 frameType
== LayoutFrameType::RubyText
) {
1753 RepositionRubyContentFrame(aFrame
, frameWM
, aBorderPadding
);
1755 // Note that, ruby text container is not present in all conditions
1756 // above. It is intended, because the children of rtc are reordered
1757 // with the children of ruby base container simultaneously. We only
1758 // need to return its isize here, as it should not be changed.
1759 icoord
+= aFrame
->ISize(aContainerWM
);
1765 nscoord
nsBidiPresUtils::RepositionFrame(
1766 nsIFrame
* aFrame
, bool aIsEvenLevel
, nscoord aStartOrEnd
,
1767 nsContinuationStates
* aContinuationStates
, WritingMode aContainerWM
,
1768 bool aContainerReverseDir
, const nsSize
& aContainerSize
) {
1770 aContainerWM
.IsVertical() ? aContainerSize
.height
: aContainerSize
.width
;
1771 NS_ASSERTION(lineSize
!= NS_UNCONSTRAINEDSIZE
,
1772 "Unconstrained inline line size in bidi frame reordering");
1773 if (!aFrame
) return 0;
1775 bool isFirst
, isLast
;
1776 WritingMode frameWM
= aFrame
->GetWritingMode();
1777 IsFirstOrLast(aFrame
, aContinuationStates
,
1778 aContainerWM
.IsBidiLTR() == frameWM
.IsBidiLTR(),
1779 isFirst
/* out */, isLast
/* out */);
1781 // We only need the margin if the frame is first or last in its own
1782 // writing mode, but we're traversing the frames in the order of the
1783 // container's writing mode. To get the right values, we set start and
1784 // end margins on a logical margin in the frame's writing mode, and
1785 // then convert the margin to the container's writing mode to set the
1788 // This method is called from nsBlockFrame::PlaceLine via the call to
1789 // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
1790 // have been reflowed, which is required for GetUsedMargin/Border/Padding
1791 nscoord frameISize
= aFrame
->ISize();
1792 LogicalMargin frameMargin
= aFrame
->GetLogicalUsedMargin(frameWM
);
1793 LogicalMargin borderPadding
= aFrame
->GetLogicalUsedBorderAndPadding(frameWM
);
1794 // Since the visual order of frame could be different from the continuation
1795 // order, we need to remove any inline border/padding [that is already applied
1796 // based on continuation order] and then add it back based on the visual order
1797 // (i.e. isFirst/isLast) to get the correct isize for the current frame.
1798 // We don't need to do that for 'box-decoration-break:clone' because then all
1799 // continuations have border/padding/margin applied.
1800 if (aFrame
->StyleBorder()->mBoxDecorationBreak
==
1801 StyleBoxDecorationBreak::Slice
) {
1802 // First remove the border/padding that was applied based on logical order.
1803 if (!aFrame
->GetPrevContinuation()) {
1804 frameISize
-= borderPadding
.IStart(frameWM
);
1806 if (!aFrame
->GetNextContinuation()) {
1807 frameISize
-= borderPadding
.IEnd(frameWM
);
1809 // Set margin/border/padding based on visual order.
1811 frameMargin
.IStart(frameWM
) = 0;
1812 borderPadding
.IStart(frameWM
) = 0;
1815 frameMargin
.IEnd(frameWM
) = 0;
1816 borderPadding
.IEnd(frameWM
) = 0;
1818 // Add the border/padding which is now based on visual order.
1819 frameISize
+= borderPadding
.IStartEnd(frameWM
);
1823 if (IsBidiLeaf(aFrame
)) {
1825 frameWM
.IsOrthogonalTo(aContainerWM
) ? aFrame
->BSize() : frameISize
;
1826 } else if (RubyUtils::IsRubyBox(aFrame
->Type())) {
1827 icoord
+= RepositionRubyFrame(aFrame
, aContinuationStates
, aContainerWM
,
1830 bool reverseDir
= aIsEvenLevel
!= frameWM
.IsBidiLTR();
1831 icoord
+= reverseDir
? borderPadding
.IEnd(frameWM
)
1832 : borderPadding
.IStart(frameWM
);
1833 LogicalSize
logicalSize(frameWM
, frameISize
, aFrame
->BSize());
1834 nsSize frameSize
= logicalSize
.GetPhysicalSize(frameWM
);
1835 // Reposition the child frames
1836 for (nsIFrame
* f
: aFrame
->PrincipalChildList()) {
1837 icoord
+= RepositionFrame(f
, aIsEvenLevel
, icoord
, aContinuationStates
,
1838 frameWM
, reverseDir
, frameSize
);
1840 icoord
+= reverseDir
? borderPadding
.IStart(frameWM
)
1841 : borderPadding
.IEnd(frameWM
);
1844 // In the following variables, if aContainerReverseDir is true, i.e.
1845 // the container is positioning its children in reverse of its logical
1846 // direction, the "StartOrEnd" refers to the distance from the frame
1847 // to the inline end edge of the container, elsewise, it refers to the
1848 // distance to the inline start edge.
1849 const LogicalMargin margin
= frameMargin
.ConvertTo(aContainerWM
, frameWM
);
1850 nscoord marginStartOrEnd
= aContainerReverseDir
? margin
.IEnd(aContainerWM
)
1851 : margin
.IStart(aContainerWM
);
1852 nscoord frameStartOrEnd
= aStartOrEnd
+ marginStartOrEnd
;
1854 LogicalRect rect
= aFrame
->GetLogicalRect(aContainerWM
, aContainerSize
);
1855 rect
.ISize(aContainerWM
) = icoord
;
1856 rect
.IStart(aContainerWM
) = aContainerReverseDir
1857 ? lineSize
- frameStartOrEnd
- icoord
1859 aFrame
->SetRect(aContainerWM
, rect
, aContainerSize
);
1861 return icoord
+ margin
.IStartEnd(aContainerWM
);
1864 void nsBidiPresUtils::InitContinuationStates(
1865 nsIFrame
* aFrame
, nsContinuationStates
* aContinuationStates
) {
1866 aContinuationStates
->Insert(aFrame
);
1867 if (!IsBidiLeaf(aFrame
)) {
1868 // Continue for child frames
1869 for (nsIFrame
* frame
: aFrame
->PrincipalChildList()) {
1870 InitContinuationStates(frame
, aContinuationStates
);
1876 nscoord
nsBidiPresUtils::RepositionInlineFrames(const BidiLineData
& aBld
,
1877 WritingMode aLineWM
,
1878 const nsSize
& aContainerSize
,
1880 nsContinuationStates continuationStates
;
1881 aBld
.InitContinuationStates(&continuationStates
);
1883 if (aLineWM
.IsBidiLTR()) {
1884 for (auto index
: IntegerRange(aBld
.VisualFrameCount())) {
1885 auto [frame
, level
] = aBld
.VisualFrameAndLevelAt(index
);
1887 RepositionFrame(frame
, level
.IsLTR(), aStart
, &continuationStates
,
1888 aLineWM
, false, aContainerSize
);
1891 for (auto index
: Reversed(IntegerRange(aBld
.VisualFrameCount()))) {
1892 auto [frame
, level
] = aBld
.VisualFrameAndLevelAt(index
);
1894 RepositionFrame(frame
, level
.IsLTR(), aStart
, &continuationStates
,
1895 aLineWM
, false, aContainerSize
);
1902 bool nsBidiPresUtils::CheckLineOrder(nsIFrame
* aFirstFrameOnLine
,
1903 int32_t aNumFramesOnLine
,
1904 nsIFrame
** aFirstVisual
,
1905 nsIFrame
** aLastVisual
) {
1906 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1909 *aFirstVisual
= bld
.VisualFrameAt(0);
1912 *aLastVisual
= bld
.VisualFrameAt(bld
.VisualFrameCount() - 1);
1915 return bld
.IsReordered();
1918 nsIFrame
* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame
* aFrame
,
1919 nsIFrame
* aFirstFrameOnLine
,
1920 int32_t aNumFramesOnLine
) {
1921 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1923 int32_t count
= bld
.VisualFrameCount();
1925 if (!aFrame
&& count
) {
1926 return bld
.VisualFrameAt(0);
1929 for (int32_t i
= 0; i
< count
- 1; i
++) {
1930 if (bld
.VisualFrameAt(i
) == aFrame
) {
1931 return bld
.VisualFrameAt(i
+ 1);
1938 nsIFrame
* nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame
* aFrame
,
1939 nsIFrame
* aFirstFrameOnLine
,
1940 int32_t aNumFramesOnLine
) {
1941 BidiLineData
bld(aFirstFrameOnLine
, aNumFramesOnLine
);
1943 int32_t count
= bld
.VisualFrameCount();
1945 if (!aFrame
&& count
) {
1946 return bld
.VisualFrameAt(count
- 1);
1949 for (int32_t i
= 1; i
< count
; i
++) {
1950 if (bld
.VisualFrameAt(i
) == aFrame
) {
1951 return bld
.VisualFrameAt(i
- 1);
1958 inline void nsBidiPresUtils::EnsureBidiContinuation(
1959 nsIFrame
* aFrame
, const nsLineList::iterator aLine
, nsIFrame
** aNewFrame
,
1960 int32_t aStart
, int32_t aEnd
) {
1961 MOZ_ASSERT(aNewFrame
, "null OUT ptr");
1962 MOZ_ASSERT(aFrame
, "aFrame is null");
1964 aFrame
->AdjustOffsetsForBidi(aStart
, aEnd
);
1965 CreateContinuation(aFrame
, aLine
, aNewFrame
, false);
1968 void nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData
* aBpd
,
1970 int32_t aFirstIndex
,
1971 int32_t aLastIndex
) {
1972 FrameBidiData bidiData
= aFrame
->GetBidiData();
1973 bidiData
.precedingControl
= kBidiLevelNone
;
1974 for (int32_t index
= aFirstIndex
+ 1; index
<= aLastIndex
; index
++) {
1975 nsIFrame
* frame
= aBpd
->FrameAt(index
);
1976 if (frame
!= NS_BIDI_CONTROL_FRAME
) {
1977 // Make the frame and its continuation ancestors fluid,
1978 // so they can be reused or deleted by normal reflow code
1979 frame
->SetProperty(nsIFrame::BidiDataProperty(), bidiData
);
1980 frame
->AddStateBits(NS_FRAME_IS_BIDI
);
1981 while (frame
&& IsBidiSplittable(frame
)) {
1982 nsIFrame
* prev
= frame
->GetPrevContinuation();
1984 MakeContinuationFluid(prev
, frame
);
1985 frame
= frame
->GetParent();
1993 // Make sure that the last continuation we made fluid does not itself have a
1994 // fluid continuation (this can happen when re-resolving after dynamic changes
1996 nsIFrame
* lastFrame
= aBpd
->FrameAt(aLastIndex
);
1997 MakeContinuationsNonFluidUpParentChain(lastFrame
, lastFrame
->GetNextInFlow());
2000 nsresult
nsBidiPresUtils::FormatUnicodeText(nsPresContext
* aPresContext
,
2002 int32_t& aTextLength
,
2003 BidiClass aBidiClass
) {
2004 nsresult rv
= NS_OK
;
2006 // adjusted for correct numeral shaping
2007 uint32_t bidiOptions
= aPresContext
->GetBidi();
2008 switch (GET_BIDI_OPTION_NUMERAL(bidiOptions
)) {
2009 case IBMBIDI_NUMERAL_HINDI
:
2010 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2013 case IBMBIDI_NUMERAL_ARABIC
:
2014 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2017 case IBMBIDI_NUMERAL_PERSIAN
:
2018 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_PERSIAN
);
2021 case IBMBIDI_NUMERAL_REGULAR
:
2023 switch (aBidiClass
) {
2024 case BidiClass::EuropeanNumber
:
2025 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2028 case BidiClass::ArabicNumber
:
2029 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2037 case IBMBIDI_NUMERAL_HINDICONTEXT
:
2038 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions
) ==
2039 IBMBIDI_TEXTDIRECTION_RTL
) &&
2040 (IS_ARABIC_DIGIT(aText
[0]))) ||
2041 (BidiClass::ArabicNumber
== aBidiClass
)) {
2042 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_HINDI
);
2043 } else if (BidiClass::EuropeanNumber
== aBidiClass
) {
2044 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2048 case IBMBIDI_NUMERAL_PERSIANCONTEXT
:
2049 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions
) ==
2050 IBMBIDI_TEXTDIRECTION_RTL
) &&
2051 (IS_ARABIC_DIGIT(aText
[0]))) ||
2052 (BidiClass::ArabicNumber
== aBidiClass
)) {
2053 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_PERSIAN
);
2054 } else if (BidiClass::EuropeanNumber
== aBidiClass
) {
2055 HandleNumbers(aText
, aTextLength
, IBMBIDI_NUMERAL_ARABIC
);
2059 case IBMBIDI_NUMERAL_NOMINAL
:
2064 StripBidiControlCharacters(aText
, aTextLength
);
2068 void nsBidiPresUtils::StripBidiControlCharacters(char16_t
* aText
,
2069 int32_t& aTextLength
) {
2070 if ((nullptr == aText
) || (aTextLength
< 1)) {
2074 int32_t stripLen
= 0;
2076 for (int32_t i
= 0; i
< aTextLength
; i
++) {
2077 // XXX: This silently ignores surrogate characters.
2078 // As of Unicode 4.0, all Bidi control characters are within the BMP.
2079 if (IsBidiControl((uint32_t)aText
[i
])) {
2082 aText
[i
- stripLen
] = aText
[i
];
2085 aTextLength
-= stripLen
;
2088 void nsBidiPresUtils::CalculateBidiClass(
2089 const char16_t
* aText
, int32_t& aOffset
, int32_t aBidiClassLimit
,
2090 int32_t& aRunLimit
, int32_t& aRunLength
, int32_t& aRunCount
,
2091 BidiClass
& aBidiClass
, BidiClass
& aPrevBidiClass
) {
2092 bool strongTypeFound
= false;
2094 BidiClass bidiClass
;
2096 aBidiClass
= BidiClass::OtherNeutral
;
2099 for (offset
= aOffset
; offset
< aBidiClassLimit
; offset
+= charLen
) {
2100 // Make sure we give RTL chartype to all characters that would be classified
2101 // as Right-To-Left by a bidi platform.
2102 // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
2104 uint32_t ch
= aText
[offset
];
2105 if (IS_HEBREW_CHAR(ch
)) {
2106 bidiClass
= BidiClass::RightToLeft
;
2107 } else if (IS_ARABIC_ALPHABETIC(ch
)) {
2108 bidiClass
= BidiClass::RightToLeftArabic
;
2110 if (offset
+ 1 < aBidiClassLimit
&&
2111 NS_IS_SURROGATE_PAIR(ch
, aText
[offset
+ 1])) {
2112 ch
= SURROGATE_TO_UCS4(ch
, aText
[offset
+ 1]);
2115 bidiClass
= intl::UnicodeProperties::GetBidiClass(ch
);
2118 if (!BIDICLASS_IS_WEAK(bidiClass
)) {
2119 if (strongTypeFound
&& (bidiClass
!= aPrevBidiClass
) &&
2120 (BIDICLASS_IS_RTL(bidiClass
) || BIDICLASS_IS_RTL(aPrevBidiClass
))) {
2121 // Stop at this point to ensure uni-directionality of the text
2122 // (from platform's point of view).
2123 // Also, don't mix Arabic and Hebrew content (since platform may
2124 // provide BIDI support to one of them only).
2125 aRunLength
= offset
- aOffset
;
2131 if ((BidiClass::RightToLeftArabic
== aPrevBidiClass
||
2132 BidiClass::ArabicNumber
== aPrevBidiClass
) &&
2133 BidiClass::EuropeanNumber
== bidiClass
) {
2134 bidiClass
= BidiClass::ArabicNumber
;
2137 // Set PrevBidiClass to the last strong type in this frame
2138 // (for correct numeric shaping)
2139 aPrevBidiClass
= bidiClass
;
2141 strongTypeFound
= true;
2142 aBidiClass
= bidiClass
;
2148 nsresult
nsBidiPresUtils::ProcessText(const char16_t
* aText
, size_t aLength
,
2149 BidiEmbeddingLevel aBaseLevel
,
2150 nsPresContext
* aPresContext
,
2151 BidiProcessor
& aprocessor
, Mode aMode
,
2152 nsBidiPositionResolve
* aPosResolve
,
2153 int32_t aPosResolveCount
, nscoord
* aWidth
,
2154 BidiEngine
& aBidiEngine
) {
2155 MOZ_ASSERT((aPosResolve
== nullptr) != (aPosResolveCount
> 0),
2156 "Incorrect aPosResolve / aPosResolveCount arguments");
2158 // Caller should have already replaced any separators in the original text
2159 // with <space> characters.
2160 MOZ_ASSERT(nsDependentSubstring(aText
, aLength
).FindCharInSet(kSeparators
) ==
2163 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2164 aPosResolve
[nPosResolve
].visualIndex
= kNotFound
;
2165 aPosResolve
[nPosResolve
].visualLeftTwips
= kNotFound
;
2166 aPosResolve
[nPosResolve
].visualWidth
= kNotFound
;
2169 // For a single-char string, or a string that is purely LTR, use a simplified
2170 // path as it cannot have multiple direction or bidi-class runs.
2172 (aLength
== 2 && NS_IS_SURROGATE_PAIR(aText
[0], aText
[1])) ||
2173 (aBaseLevel
.Direction() == BidiDirection::LTR
&&
2174 !encoding_mem_is_utf16_bidi(aText
, aLength
))) {
2175 ProcessSimpleRun(aText
, aLength
, aBaseLevel
, aPresContext
, aprocessor
,
2176 aMode
, aPosResolve
, aPosResolveCount
, aWidth
);
2180 if (aBidiEngine
.SetParagraph(Span(aText
, aLength
), aBaseLevel
).isErr()) {
2181 return NS_ERROR_FAILURE
;
2184 auto result
= aBidiEngine
.CountRuns();
2185 if (result
.isErr()) {
2186 return NS_ERROR_FAILURE
;
2188 int32_t runCount
= result
.unwrap();
2190 nscoord xOffset
= 0;
2191 nscoord width
, xEndRun
= 0;
2192 nscoord totalWidth
= 0;
2193 int32_t i
, start
, limit
, length
;
2194 uint32_t visualStart
= 0;
2195 BidiClass bidiClass
;
2196 BidiClass prevClass
= BidiClass::LeftToRight
;
2198 for (i
= 0; i
< runCount
; i
++) {
2199 aBidiEngine
.GetVisualRun(i
, &start
, &length
);
2201 BidiEmbeddingLevel level
;
2202 aBidiEngine
.GetLogicalRun(start
, &limit
, &level
);
2204 BidiDirection dir
= level
.Direction();
2205 int32_t subRunLength
= limit
- start
;
2206 int32_t lineOffset
= start
;
2207 int32_t typeLimit
= std::min(limit
, AssertedCast
<int32_t>(aLength
));
2208 int32_t subRunCount
= 1;
2209 int32_t subRunLimit
= typeLimit
;
2212 * If |level| is even, i.e. the direction of the run is left-to-right, we
2213 * render the subruns from left to right and increment the x-coordinate
2214 * |xOffset| by the width of each subrun after rendering.
2216 * If |level| is odd, i.e. the direction of the run is right-to-left, we
2217 * render the subruns from right to left. We begin by incrementing |xOffset|
2218 * by the width of the whole run, and then decrement it by the width of each
2219 * subrun before rendering. After rendering all the subruns, we restore the
2220 * x-coordinate of the end of the run for the start of the next run.
2223 if (dir
== BidiDirection::RTL
) {
2224 aprocessor
.SetText(aText
+ start
, subRunLength
, BidiDirection::RTL
);
2225 width
= aprocessor
.GetWidth();
2230 while (subRunCount
> 0) {
2231 // CalculateBidiClass can increment subRunCount if the run
2232 // contains mixed character types
2233 CalculateBidiClass(aText
, lineOffset
, typeLimit
, subRunLimit
,
2234 subRunLength
, subRunCount
, bidiClass
, prevClass
);
2236 nsAutoString
runVisualText(aText
+ start
, subRunLength
);
2238 FormatUnicodeText(aPresContext
, runVisualText
.BeginWriting(),
2239 subRunLength
, bidiClass
);
2242 aprocessor
.SetText(runVisualText
.get(), subRunLength
, dir
);
2243 width
= aprocessor
.GetWidth();
2244 totalWidth
+= width
;
2245 if (dir
== BidiDirection::RTL
) {
2248 if (aMode
== MODE_DRAW
) {
2249 aprocessor
.DrawText(xOffset
);
2253 * The caller may request to calculate the visual position of one
2254 * or more characters.
2256 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2257 nsBidiPositionResolve
* posResolve
= &aPosResolve
[nPosResolve
];
2259 * Did we already resolve this position's visual metric? If so, skip.
2261 if (posResolve
->visualLeftTwips
!= kNotFound
) continue;
2264 * First find out if the logical position is within this run.
2266 if (start
<= posResolve
->logicalIndex
&&
2267 start
+ subRunLength
> posResolve
->logicalIndex
) {
2269 * If this run is only one character long, we have an easy case:
2270 * the visual position is the x-coord of the start of the run
2271 * less the x-coord of the start of the whole text.
2273 if (subRunLength
== 1) {
2274 posResolve
->visualIndex
= visualStart
;
2275 posResolve
->visualLeftTwips
= xOffset
;
2276 posResolve
->visualWidth
= width
;
2279 * Otherwise, we need to measure the width of the run's part
2280 * which is to the visual left of the index.
2281 * In other words, the run is broken in two, around the logical index,
2282 * and we measure the part which is visually left.
2283 * If the run is right-to-left, this part will span from after the
2284 * index up to the end of the run; if it is left-to-right, this part
2285 * will span from the start of the run up to (and inclduing) the
2286 * character before the index.
2290 * Here is a description of how the width of the current character
2291 * (posResolve->visualWidth) is calculated:
2293 * LTR (current char: "P"):
2294 * S A M P L E (logical index: 3, visual index: 3)
2295 * ^ (visualLeftPart)
2296 * ^ (visualRightSide)
2297 * visualLeftLength == 3
2299 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2300 * ^^ (posResolve->visualWidth)
2302 * RTL (current char: "M"):
2303 * E L P M A S (logical index: 2, visual index: 3)
2304 * ^ (visualLeftPart)
2305 * ^ (visualRightSide)
2306 * visualLeftLength == 3
2308 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2309 * ^^ (posResolve->visualWidth)
2312 // The position in the text where this run's "left part" begins.
2313 const char16_t
* visualLeftPart
;
2314 const char16_t
* visualRightSide
;
2315 if (dir
== BidiDirection::RTL
) {
2316 // One day, son, this could all be replaced with
2317 // mPresContext->BidiEngine().GetVisualIndex() ...
2318 posResolve
->visualIndex
=
2320 (subRunLength
- (posResolve
->logicalIndex
+ 1 - start
));
2321 // Skipping to the "left part".
2322 visualLeftPart
= aText
+ posResolve
->logicalIndex
+ 1;
2323 // Skipping to the right side of the current character
2324 visualRightSide
= visualLeftPart
- 1;
2326 posResolve
->visualIndex
=
2327 visualStart
+ (posResolve
->logicalIndex
- start
);
2328 // Skipping to the "left part".
2329 visualLeftPart
= aText
+ start
;
2330 // In LTR mode this is the same as visualLeftPart
2331 visualRightSide
= visualLeftPart
;
2333 // The delta between the start of the run and the left part's end.
2334 int32_t visualLeftLength
= posResolve
->visualIndex
- visualStart
;
2335 aprocessor
.SetText(visualLeftPart
, visualLeftLength
, dir
);
2336 subWidth
= aprocessor
.GetWidth();
2337 aprocessor
.SetText(visualRightSide
, visualLeftLength
+ 1, dir
);
2338 posResolve
->visualLeftTwips
= xOffset
+ subWidth
;
2339 posResolve
->visualWidth
= aprocessor
.GetWidth() - subWidth
;
2344 if (dir
== BidiDirection::LTR
) {
2350 subRunLimit
= typeLimit
;
2351 subRunLength
= typeLimit
- lineOffset
;
2353 if (dir
== BidiDirection::RTL
) {
2357 visualStart
+= length
;
2361 *aWidth
= totalWidth
;
2366 // This is called either for a single character (one code unit, or a surrogate
2367 // pair), or for a run that is known to be purely LTR.
2368 void nsBidiPresUtils::ProcessSimpleRun(const char16_t
* aText
, size_t aLength
,
2369 BidiEmbeddingLevel aBaseLevel
,
2370 nsPresContext
* aPresContext
,
2371 BidiProcessor
& aprocessor
, Mode aMode
,
2372 nsBidiPositionResolve
* aPosResolve
,
2373 int32_t aPosResolveCount
,
2381 // Get bidi class from the first (or only) character.
2382 uint32_t ch
= aText
[0];
2383 if (aLength
> 1 && NS_IS_HIGH_SURROGATE(ch
) &&
2384 NS_IS_LOW_SURROGATE(aText
[1])) {
2385 ch
= SURROGATE_TO_UCS4(aText
[0], aText
[1]);
2387 BidiClass bidiClass
= intl::UnicodeProperties::GetBidiClass(ch
);
2389 nsAutoString
runVisualText(aText
, aLength
);
2390 int32_t length
= aLength
;
2392 FormatUnicodeText(aPresContext
, runVisualText
.BeginWriting(), length
,
2396 BidiDirection dir
= bidiClass
== BidiClass::RightToLeft
||
2397 bidiClass
== BidiClass::RightToLeftArabic
2398 ? BidiDirection::RTL
2399 : BidiDirection::LTR
;
2400 aprocessor
.SetText(runVisualText
.get(), length
, dir
);
2402 if (aMode
== MODE_DRAW
) {
2403 aprocessor
.DrawText(0);
2406 if (!aWidth
&& !aPosResolve
) {
2410 nscoord width
= aprocessor
.GetWidth();
2412 for (int nPosResolve
= 0; nPosResolve
< aPosResolveCount
; ++nPosResolve
) {
2413 nsBidiPositionResolve
* posResolve
= &aPosResolve
[nPosResolve
];
2414 if (posResolve
->visualLeftTwips
!= kNotFound
) {
2417 if (0 <= posResolve
->logicalIndex
&& length
> posResolve
->logicalIndex
) {
2418 posResolve
->visualIndex
= 0;
2419 posResolve
->visualLeftTwips
= 0;
2420 posResolve
->visualWidth
= width
;
2429 class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final
2430 : public nsBidiPresUtils::BidiProcessor
{
2432 typedef gfx::DrawTarget DrawTarget
;
2434 nsIRenderingContextBidiProcessor(gfxContext
* aCtx
,
2435 DrawTarget
* aTextRunConstructionDrawTarget
,
2436 nsFontMetrics
* aFontMetrics
,
2439 mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget
),
2440 mFontMetrics(aFontMetrics
),
2445 ~nsIRenderingContextBidiProcessor() { mFontMetrics
->SetTextRunRTL(false); }
2447 virtual void SetText(const char16_t
* aText
, int32_t aLength
,
2448 BidiDirection aDirection
) override
{
2449 mFontMetrics
->SetTextRunRTL(aDirection
== BidiDirection::RTL
);
2454 virtual nscoord
GetWidth() override
{
2455 return nsLayoutUtils::AppUnitWidthOfString(mText
, mLength
, *mFontMetrics
,
2456 mTextRunConstructionDrawTarget
);
2459 virtual void DrawText(nscoord aIOffset
) override
{
2461 if (mFontMetrics
->GetVertical()) {
2466 mFontMetrics
->DrawString(mText
, mLength
, pt
.x
, pt
.y
, mCtx
,
2467 mTextRunConstructionDrawTarget
);
2472 DrawTarget
* mTextRunConstructionDrawTarget
;
2473 nsFontMetrics
* mFontMetrics
;
2475 const char16_t
* mText
;
2479 nsresult
nsBidiPresUtils::ProcessTextForRenderingContext(
2480 const char16_t
* aText
, int32_t aLength
, BidiEmbeddingLevel aBaseLevel
,
2481 nsPresContext
* aPresContext
, gfxContext
& aRenderingContext
,
2482 DrawTarget
* aTextRunConstructionDrawTarget
, nsFontMetrics
& aFontMetrics
,
2483 Mode aMode
, nscoord aX
, nscoord aY
, nsBidiPositionResolve
* aPosResolve
,
2484 int32_t aPosResolveCount
, nscoord
* aWidth
) {
2485 nsIRenderingContextBidiProcessor
processor(&aRenderingContext
,
2486 aTextRunConstructionDrawTarget
,
2487 &aFontMetrics
, nsPoint(aX
, aY
));
2488 nsDependentSubstring
text(aText
, aLength
);
2489 auto separatorIndex
= text
.FindCharInSet(kSeparators
);
2490 if (separatorIndex
== kNotFound
) {
2491 return ProcessText(text
.BeginReading(), text
.Length(), aBaseLevel
,
2492 aPresContext
, processor
, aMode
, aPosResolve
,
2493 aPosResolveCount
, aWidth
, aPresContext
->BidiEngine());
2496 // We need to replace any block or segment separators with space for bidi
2497 // processing, so make a local copy.
2498 nsAutoString
localText(text
);
2499 ReplaceSeparators(localText
, separatorIndex
);
2500 return ProcessText(localText
.BeginReading(), localText
.Length(), aBaseLevel
,
2501 aPresContext
, processor
, aMode
, aPosResolve
,
2502 aPosResolveCount
, aWidth
, aPresContext
->BidiEngine());
2506 BidiEmbeddingLevel
nsBidiPresUtils::BidiLevelFromStyle(
2507 ComputedStyle
* aComputedStyle
) {
2508 if (aComputedStyle
->StyleTextReset()->mUnicodeBidi
==
2509 StyleUnicodeBidi::Plaintext
) {
2510 return BidiEmbeddingLevel::DefaultLTR();
2513 if (aComputedStyle
->StyleVisibility()->mDirection
== StyleDirection::Rtl
) {
2514 return BidiEmbeddingLevel::RTL();
2517 return BidiEmbeddingLevel::LTR();