Bug 1877662 - expose mozconfig as an artifact from build-fat-aar. r=glandium,geckovie...
[gecko.git] / layout / base / nsBidiPresUtils.cpp
blob0331a7e5a0f4bdfeed6341a2e0bba5ba904aae50
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"
38 #include <algorithm>
40 #undef NOISY_BIDI
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) {
91 return kLRO;
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;
98 return 0;
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
104 // algorithm.
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
110 // FSI and LRO/RLO.
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:
123 return kFSI;
124 case StyleUnicodeBidi::Normal:
125 case StyleUnicodeBidi::BidiOverride:
126 break;
129 return 0;
132 #ifdef DEBUG
133 static inline bool AreContinuationsInOrder(nsIFrame* aFrame1,
134 nsIFrame* aFrame2) {
135 nsIFrame* f = aFrame1;
136 do {
137 f = f->GetNextContinuation();
138 } while (f && f != aFrame2);
139 return !!f;
141 #endif
143 struct MOZ_STACK_CLASS BidiParagraphData {
144 struct FrameInfo {
145 FrameInfo(nsIFrame* aFrame, nsBlockInFlowLineIterator& aLineIter)
146 : mFrame(aFrame),
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) {}
155 FrameInfo()
156 : mFrame(nullptr), mBlockContainer(nullptr), mInOverflow(false) {}
158 nsIFrame* mFrame;
160 // The block containing mFrame (i.e., which continuation).
161 nsBlockFrame* mBlockContainer;
163 // true if mFrame is in mBlockContainer's overflow lines, false if
164 // in primary lines
165 bool mInOverflow;
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;
174 bool mIsVisual;
175 bool mRequiresBidi;
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
200 // iterating over.
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()
221 ? nullptr
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;
229 return false;
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
244 // over.
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()) {
256 child = parent;
257 parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
259 MOZ_ASSERT(parent, "aFrame is not a descendent of a block frame");
260 while (!IsFrameInCurrentLine(&mLineIterator, mPrevFrame, child)) {
261 #ifdef DEBUG
262 bool hasNext =
263 #endif
264 mLineIterator.Next();
265 MOZ_ASSERT(hasNext, "Can't find frame in lines!");
266 mPrevFrame = nullptr;
268 mPrevFrame = child;
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) {
278 MOZ_ASSERT(
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();
289 mLineIterator =
290 nsBlockInFlowLineIterator(block, lines, aFrameInfo.mInOverflow);
291 mPrevFrame = nullptr;
293 AdvanceToFrame(aFrameInfo.mFrame);
297 FastLineIterator mCurrentTraverseLine, mCurrentResolveLine;
299 #ifdef DEBUG
300 // Only used for NOISY debug output.
301 // Matches the current TraverseFrames state, not the ResolveParagraph
302 // state.
303 nsBlockFrame* mCurrentBlock;
304 #endif
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)
312 #ifdef DEBUG
314 mCurrentBlock(aBlockFrame)
315 #endif
317 if (mParaLevel > 0) {
318 mRequiresBidi = true;
321 if (mIsVisual) {
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()) {
339 mIsVisual = false;
340 break;
346 nsresult SetPara() {
347 if (mPresContext->BidiEngine().SetParagraph(mBuffer, mParaLevel).isErr()) {
348 return NS_ERROR_FAILURE;
350 return NS_OK;
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
358 * BidiDirection::RTL
360 BidiEmbeddingLevel GetParagraphEmbeddingLevel() {
361 BidiEmbeddingLevel paraLevel = mParaLevel;
362 if (paraLevel == BidiEmbeddingLevel::DefaultLTR() ||
363 paraLevel == BidiEmbeddingLevel::DefaultRTL()) {
364 paraLevel = mPresContext->BidiEngine().GetParagraphEmbeddingLevel();
366 return paraLevel;
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();
379 return NS_OK;
382 void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit,
383 BidiEmbeddingLevel* aLevel) {
384 mPresContext->BidiEngine().GetLogicalRun(aLogicalStart, aLogicalLimit,
385 aLevel);
386 if (mIsVisual) {
387 *aLevel = GetParagraphEmbeddingLevel();
391 void ResetData() {
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) {
404 if (aContent) {
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();
419 if (frame) {
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();
431 *aFrame = frame;
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));
457 AppendUnichar(aCh);
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());
472 AppendPopChar(aCh);
473 mEmbeddingStack.RemoveLastElement();
476 void ClearBidiControls() {
477 for (char16_t c : Reversed(mEmbeddingStack)) {
478 AppendPopChar(c);
483 class MOZ_STACK_CLASS BidiLineData {
484 public:
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);
503 // Reorder the line
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) {
512 continue;
514 mVisualFrameIndex.AppendElement(mIndexMap[i]);
515 if (int32_t(i) != mIndexMap[i]) {
516 mIsReordered = true;
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);
548 private:
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;
556 #ifdef DEBUG
557 extern "C" {
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");
562 } else {
563 frame->List();
567 if (aVisualOrder) {
568 for (uint32_t i = 0; i < aData->VisualFrameCount(); i++) {
569 dump(aData->VisualFrameAt(i));
571 } else {
572 for (uint32_t i = 0; i < aData->LogicalFrameCount(); i++) {
573 dump(aData->LogicalFrameAt(i));
578 #endif
580 /* Some helper methods for Resolve() */
582 // Should this frame be split between text runs?
583 static bool IsBidiSplittable(nsIFrame* aFrame) {
584 MOZ_ASSERT(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();
595 if (kid) {
596 if (aFrame->IsBidiInlineContainer() ||
597 RubyUtils::IsRubyBox(aFrame->Type())) {
598 return false;
601 return true;
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));
650 parentLine = &aLine;
651 } else {
652 parentLine = nullptr;
655 // The list name FrameChildListID::NoReflowPrincipal would indicate we
656 // don't want reflow
657 grandparent->InsertFrames(FrameChildListID::NoReflowPrincipal, parent,
658 parentLine, nsFrameList(newParent, newParent));
661 frame = parent;
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,
677 nsIFrame* aNext) {
678 nsIFrame* frame;
679 nsIFrame* next;
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();
697 if (next) {
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();
721 NS_ASSERTION(
722 parent,
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));
732 parentLine = &aLine;
733 } else {
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,
743 aIsFluid);
744 return;
747 *aNewFrame = presShell->FrameConstructor()->CreateContinuingFrame(
748 aFrame, parent, aIsFluid);
750 // The list name FrameChildListID::NoReflowPrincipal would indicate we don't
751 // want reflow
752 // XXXbz this needs higher-level framelist love
753 parent->InsertFrames(FrameChildListID::NoReflowPrincipal, aFrame, parentLine,
754 nsFrameList(*aNewFrame, *aNewFrame));
756 if (!aIsFluid) {
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
776 * CHARACTER
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
796 // TraverseFrames.
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());
802 if (ch != 0) {
803 bpd.PushBidiControl(ch);
804 bpd.mRequiresBidi = true;
805 } else {
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
808 // resolution.
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(),
815 &currContent)) {
816 bpd.mRequiresBidi = true;
818 if (!bpd.mRequiresBidi) {
819 nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
820 if (overflowLines) {
821 if (ChildListMayRequireBidi(overflowLines->mFrames.FirstChild(),
822 &currContent)) {
823 bpd.mRequiresBidi = true;
828 if (!bpd.mRequiresBidi) {
829 return NS_OK;
833 for (nsBlockFrame* block = aBlockFrame; block;
834 block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
835 #ifdef DEBUG
836 bpd.mCurrentBlock = block;
837 #endif
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();
844 if (overflowLines) {
845 bpd.mCurrentTraverseLine.mLineIterator =
846 nsBlockInFlowLineIterator(block, overflowLines->mLines.begin(), true);
847 bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
848 TraverseFrames(overflowLines->mFrames.FirstChild(), &bpd);
852 if (ch != 0) {
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)) {
874 *cp = ' ';
879 nsresult nsBidiPresUtils::ResolveParagraph(BidiParagraphData* aBpd) {
880 if (aBpd->BufferLength() < 1) {
881 return NS_OK;
884 ReplaceSeparators(aBpd->mBuffer);
886 int32_t runCount;
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
898 int32_t numRun = -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;
909 #ifdef DEBUG
910 # ifdef NOISY_BIDI
911 printf(
912 "Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, "
913 "runCount=%d\n",
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);
919 # endif
920 # endif
921 #endif
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) {
935 #ifdef DEBUG
936 # ifdef NOISY_BIDI
937 printf("early return for single direction frame %p\n", (void*)frame);
938 # endif
939 #endif
940 frame->AddStateBits(NS_FRAME_IS_BIDI);
941 return NS_OK;
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;
961 } else {
962 bidiData.precedingControl = precedingControl;
964 precedingControl = kBidiLevelNone;
965 lastEmbeddingLevel = embeddingLevel;
966 frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData);
969 for (;;) {
970 if (fragmentLength <= 0) {
971 // Get the next frame from mLogicalFrames
972 if (++frameIndex >= frameCount) {
973 break;
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
981 * SUBSTITUTE)
983 isTextFrame = false;
984 fragmentLength = 1;
985 } else {
986 aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(frameInfo);
987 content = frame->GetContent();
988 if (!content) {
989 rv = NS_OK;
990 break;
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;
998 isTextFrame = true;
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();
1009 if (isTextFrame) {
1010 frame->AdjustOffsetsForBidi(contentOffset,
1011 contentOffset + fragmentLength);
1014 break;
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);
1028 } else {
1029 storeBidiDataToFrame();
1030 if (isTextFrame) {
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;
1038 continue;
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
1045 * run.
1047 currentLine->MarkDirty();
1048 nsIFrame* nextBidi;
1049 int32_t runEnd = contentOffset + runLength;
1050 EnsureBidiContinuation(frame, currentLine, &nextBidi, contentOffset,
1051 runEnd);
1052 nextBidi->AdjustOffsetsForBidi(runEnd,
1053 contentOffset + fragmentLength);
1054 frame = nextBidi;
1055 frameInfo.mFrame = frame;
1056 contentOffset = runEnd;
1058 aBpd->mCurrentResolveLine.AdvanceToFrame(frame);
1059 } // if (runLength < fragmentLength)
1060 else {
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;
1083 do {
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
1094 * parent chain
1096 nsIFrame* next = frame->GetNextInFlow();
1097 if (next) {
1098 currentLine->MarkDirty();
1099 MakeContinuationsNonFluidUpParentChain(frame, next);
1102 frame->AdjustOffsetsForBidi(contentOffset,
1103 contentOffset + fragmentLength);
1105 } // isTextFrame
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
1128 // be split.
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
1132 // directional run.
1133 while (parent && IsBidiSplittable(parent) &&
1134 !child->GetNextSibling()) {
1135 nsIFrame* next = parent->GetNextInFlow();
1136 if (next) {
1137 parent->SetNextContinuation(next);
1138 next->SetPrevContinuation(parent);
1140 child = parent;
1141 parent = child->GetParent();
1143 if (parent && IsBidiSplittable(parent)) {
1144 aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
1145 SplitInlineAncestors(parent, aBpd->mCurrentResolveLine.GetLine(),
1146 child);
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);
1158 } // for
1160 #ifdef DEBUG
1161 # ifdef REALLY_NOISY_BIDI
1162 printf("---\nAfter Resolve(), frameTree =:\n");
1163 aBpd->mCurrentBlock->List(stdout);
1164 printf("===\n");
1165 # endif
1166 #endif
1168 return rv;
1171 void nsBidiPresUtils::TraverseFrames(nsIFrame* aCurrentFrame,
1172 BidiParagraphData* aBpd) {
1173 if (!aCurrentFrame) return;
1175 #ifdef DEBUG
1176 nsBlockFrame* initialLineContainer =
1177 aBpd->mCurrentTraverseLine.mLineIterator.GetContainer();
1178 #endif
1180 nsIFrame* childFrame = aCurrentFrame;
1181 do {
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
1188 * twice.
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()) {
1200 frame = realFrame;
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();
1213 bool isLastFrame =
1214 !nextContinuation || DifferentBidiValues(sc, nextContinuation);
1215 bool isFirstFrame =
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
1235 // plaintext.
1236 if (isFirstFrame) {
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);
1262 } else {
1264 * For preformatted text we have to do bidi resolution on each line
1265 * separately.
1267 nsAutoString text;
1268 content->GetAsText()->AppendTextTo(text);
1269 nsIFrame* next;
1270 do {
1271 next = nullptr;
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
1279 * resolution later
1281 aBpd->AppendString(Substring(text, start));
1282 while (frame && nextSibling) {
1283 aBpd->AdvanceAndAppendFrame(
1284 &frame, aBpd->mCurrentTraverseLine, &nextSibling);
1286 break;
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
1293 ++endLine;
1296 * If the frame ends before the new line, save the text and move
1297 * into the next continuation
1299 aBpd->AppendString(
1300 Substring(text, start, std::min(end, endLine) - start));
1301 while (end < endLine && nextSibling) {
1302 aBpd->AdvanceAndAppendFrame(&frame, aBpd->mCurrentTraverseLine,
1303 &nextSibling);
1304 NS_ASSERTION(frame, "Premature end of continuation chain");
1305 std::tie(start, end) = frame->GetOffsets();
1306 aBpd->AppendString(
1307 Substring(text, start, std::min(end, endLine) - start));
1310 if (end < endLine) {
1311 aBpd->mPrevContent = nullptr;
1312 break;
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();
1330 if (!next) {
1331 // If the frame already has a bidi continuation, make it fluid
1332 next = frame->GetNextContinuation();
1333 if (next) {
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);
1348 if (!next) {
1349 // If the frame has no next in flow, create one.
1350 CreateContinuation(
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) {
1360 break;
1362 if (next) {
1363 frame = next;
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();
1378 } while (next);
1381 } else if (LayoutFrameType::Br == frameType) {
1382 // break frame -- append line separator
1383 aBpd->AppendUnichar(kLineSeparator);
1384 ResolveParagraphWithinBlock(aBpd);
1385 } else {
1386 // other frame type -- see the Unicode Bidi Algorithm:
1387 // "...inline objects (such as graphics) are treated as if they are ...
1388 // U+FFFC"
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);
1398 } else {
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");
1403 if (kid) {
1404 TraverseFrames(kid, aBpd);
1408 // If the element is attributed by dir, indicate direction pop (add PDF
1409 // frame)
1410 if (isLastFrame) {
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()) {
1442 frame = realFrame;
1446 // If unicode-bidi properties are present, we should do bidi resolution.
1447 ComputedStyle* sc = frame->Style();
1448 if (GetBidiControl(sc) || GetBidiOverride(sc)) {
1449 return true;
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())) {
1459 return true;
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();
1468 if (txt->Is2b() &&
1469 HasRTLChars(Span(txt->Get2b(), txt->GetLength()))) {
1470 return true;
1474 } else if (ChildListMayRequireBidi(frame->PrincipalChildList().FirstChild(),
1475 aCurrContent)) {
1476 return true;
1480 return false;
1483 void nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData* aBpd) {
1484 aBpd->ClearBidiControls();
1485 ResolveParagraph(aBpd);
1486 aBpd->ResetData();
1489 /* static */
1490 nscoord nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine,
1491 int32_t aNumFramesOnLine,
1492 WritingMode aLineWM,
1493 const nsSize& aContainerSize,
1494 nscoord aStart) {
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) {
1505 return 0;
1507 // All children of the line frame are on the first line. Setting
1508 // aNumFramesOnLine to -1 makes InitLogicalArrayFromLine look at all of
1509 // them.
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.
1513 aStart = 0;
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,
1524 containerSize);
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;
1538 return firstLeaf;
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;
1580 nsIFrame* frame;
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;
1610 } else {
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;
1621 } else {
1622 aIsFirst = lastInLineOrder;
1623 aIsLast = firstInLineOrder;
1626 if (frameState->mHasContOnPrevLines) {
1627 aIsFirst = false;
1629 if (firstFrameState->mHasContOnNextLines) {
1630 aIsLast = false;
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
1641 aIsLast = false;
1643 if (firstContinuation->FrameIsNonFirstInIBSplit()) {
1644 // We are not startmost
1645 aIsFirst = false;
1649 // Reduce number of remaining frames of the continuation chain on the line.
1650 firstFrameState->mFrameCount--;
1652 nsInlineFrame* testFrame = do_QueryFrame(aFrame);
1654 if (testFrame) {
1655 aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET);
1657 if (aIsFirst) {
1658 aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
1659 } else {
1660 aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
1663 if (aIsLast) {
1664 aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
1665 } else {
1666 aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
1671 /* static */
1672 void nsBidiPresUtils::RepositionRubyContentFrame(
1673 nsIFrame* aFrame, WritingMode aFrameWM,
1674 const LogicalMargin& aBorderPadding) {
1675 const nsFrameList& childList = aFrame->PrincipalChildList();
1676 if (childList.IsEmpty()) {
1677 return;
1680 // Reorder the children.
1681 nscoord isize =
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) {
1687 return;
1689 nscoord residualISize = aFrame->ISize(aFrameWM) - isize;
1690 if (residualISize <= 0) {
1691 return;
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);
1704 /* static */
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));
1711 nscoord icoord = 0;
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();
1719 e.Next()) {
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()) {
1739 RubyColumn column;
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;
1752 } else {
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);
1763 return icoord;
1766 /* static */
1767 nscoord nsBidiPresUtils::RepositionFrame(
1768 nsIFrame* aFrame, bool aIsEvenLevel, nscoord aStartOrEnd,
1769 nsContinuationStates* aContinuationStates, WritingMode aContainerWM,
1770 bool aContainerReverseDir, const nsSize& aContainerSize) {
1771 nscoord lineSize =
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
1788 // coordinates.
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.
1812 if (!isFirst) {
1813 frameMargin.IStart(frameWM) = 0;
1814 borderPadding.IStart(frameWM) = 0;
1816 if (!isLast) {
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);
1824 nscoord icoord = 0;
1825 if (IsBidiLeaf(aFrame)) {
1826 icoord +=
1827 frameWM.IsOrthogonalTo(aContainerWM) ? aFrame->BSize() : frameISize;
1828 } else if (RubyUtils::IsRubyBox(aFrame->Type())) {
1829 icoord += RepositionRubyFrame(aFrame, aContinuationStates, aContainerWM,
1830 borderPadding);
1831 } else {
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
1860 : frameStartOrEnd;
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);
1877 /* static */
1878 nscoord nsBidiPresUtils::RepositionInlineFrames(const BidiLineData& aBld,
1879 WritingMode aLineWM,
1880 const nsSize& aContainerSize,
1881 nscoord aStart) {
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);
1888 aStart +=
1889 RepositionFrame(frame, level.IsLTR(), aStart, &continuationStates,
1890 aLineWM, false, aContainerSize);
1892 } else {
1893 for (auto index : Reversed(IntegerRange(aBld.VisualFrameCount()))) {
1894 auto [frame, level] = aBld.VisualFrameAndLevelAt(index);
1895 aStart +=
1896 RepositionFrame(frame, level.IsLTR(), aStart, &continuationStates,
1897 aLineWM, false, aContainerSize);
1901 return aStart;
1904 bool nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine,
1905 int32_t aNumFramesOnLine,
1906 nsIFrame** aFirstVisual,
1907 nsIFrame** aLastVisual) {
1908 BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
1910 if (aFirstVisual) {
1911 *aFirstVisual = bld.VisualFrameAt(0);
1913 if (aLastVisual) {
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);
1937 return nullptr;
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);
1957 return nullptr;
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,
1971 nsIFrame* aFrame,
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();
1985 if (prev) {
1986 MakeContinuationFluid(prev, frame);
1987 frame = frame->GetParent();
1988 } else {
1989 break;
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
1997 // to content)
1998 nsIFrame* lastFrame = aBpd->FrameAt(aLastIndex);
1999 MakeContinuationsNonFluidUpParentChain(lastFrame, lastFrame->GetNextInFlow());
2002 nsresult nsBidiPresUtils::FormatUnicodeText(nsPresContext* aPresContext,
2003 char16_t* aText,
2004 int32_t& aTextLength,
2005 BidiClass aBidiClass) {
2006 nsresult rv = NS_OK;
2007 // ahmed
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);
2013 break;
2015 case IBMBIDI_NUMERAL_ARABIC:
2016 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
2017 break;
2019 case IBMBIDI_NUMERAL_PERSIAN:
2020 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN);
2021 break;
2023 case IBMBIDI_NUMERAL_REGULAR:
2025 switch (aBidiClass) {
2026 case BidiClass::EuropeanNumber:
2027 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
2028 break;
2030 case BidiClass::ArabicNumber:
2031 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
2032 break;
2034 default:
2035 break;
2037 break;
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);
2048 break;
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);
2059 break;
2061 case IBMBIDI_NUMERAL_NOMINAL:
2062 default:
2063 break;
2066 StripBidiControlCharacters(aText, aTextLength);
2067 return rv;
2070 void nsBidiPresUtils::StripBidiControlCharacters(char16_t* aText,
2071 int32_t& aTextLength) {
2072 if ((nullptr == aText) || (aTextLength < 1)) {
2073 return;
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])) {
2082 ++stripLen;
2083 } else {
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;
2095 int32_t offset;
2096 BidiClass bidiClass;
2098 aBidiClass = BidiClass::OtherNeutral;
2100 int32_t charLen;
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.)
2105 charLen = 1;
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;
2111 } else {
2112 if (offset + 1 < aBidiClassLimit &&
2113 NS_IS_SURROGATE_PAIR(ch, aText[offset + 1])) {
2114 ch = SURROGATE_TO_UCS4(ch, aText[offset + 1]);
2115 charLen = 2;
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;
2128 aRunLimit = offset;
2129 ++aRunCount;
2130 break;
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;
2147 aOffset = offset;
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) ==
2163 kNotFound);
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.
2173 if (aLength == 1 ||
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);
2179 return NS_OK;
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();
2228 xOffset += width;
2229 xEndRun = xOffset;
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);
2239 if (aPresContext) {
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) {
2248 xOffset -= width;
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.
2290 else {
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
2300 * ^^^^^^ (subWidth)
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
2309 * ^^^^^^ (subWidth)
2310 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2311 * ^^ (posResolve->visualWidth)
2313 nscoord subWidth;
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 =
2321 visualStart +
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;
2327 } else {
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) {
2347 xOffset += width;
2350 --subRunCount;
2351 start = lineOffset;
2352 subRunLimit = typeLimit;
2353 subRunLength = typeLimit - lineOffset;
2354 } // while
2355 if (dir == BidiDirection::RTL) {
2356 xOffset = xEndRun;
2359 visualStart += length;
2360 } // for
2362 if (aWidth) {
2363 *aWidth = totalWidth;
2365 return NS_OK;
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,
2376 nscoord* aWidth) {
2377 if (!aLength) {
2378 if (aWidth) {
2379 *aWidth = 0;
2381 return;
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;
2393 if (aPresContext) {
2394 FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), length,
2395 bidiClass);
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) {
2409 return;
2412 nscoord width = aprocessor.GetWidth();
2414 for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
2415 nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
2416 if (posResolve->visualLeftTwips != kNotFound) {
2417 continue;
2419 if (0 <= posResolve->logicalIndex && length > posResolve->logicalIndex) {
2420 posResolve->visualIndex = 0;
2421 posResolve->visualLeftTwips = 0;
2422 posResolve->visualWidth = width;
2426 if (aWidth) {
2427 *aWidth = width;
2431 class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final
2432 : public nsBidiPresUtils::BidiProcessor {
2433 public:
2434 typedef gfx::DrawTarget DrawTarget;
2436 nsIRenderingContextBidiProcessor(gfxContext* aCtx,
2437 DrawTarget* aTextRunConstructionDrawTarget,
2438 nsFontMetrics* aFontMetrics,
2439 const nsPoint& aPt)
2440 : mCtx(aCtx),
2441 mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget),
2442 mFontMetrics(aFontMetrics),
2443 mPt(aPt),
2444 mText(nullptr),
2445 mLength(0) {}
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);
2452 mText = aText;
2453 mLength = aLength;
2456 virtual nscoord GetWidth() override {
2457 return nsLayoutUtils::AppUnitWidthOfString(mText, mLength, *mFontMetrics,
2458 mTextRunConstructionDrawTarget);
2461 virtual void DrawText(nscoord aIOffset) override {
2462 nsPoint pt(mPt);
2463 if (mFontMetrics->GetVertical()) {
2464 pt.y += aIOffset;
2465 } else {
2466 pt.x += aIOffset;
2468 mFontMetrics->DrawString(mText, mLength, pt.x, pt.y, mCtx,
2469 mTextRunConstructionDrawTarget);
2472 private:
2473 gfxContext* mCtx;
2474 DrawTarget* mTextRunConstructionDrawTarget;
2475 nsFontMetrics* mFontMetrics;
2476 nsPoint mPt;
2477 const char16_t* mText;
2478 int32_t mLength;
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());
2507 /* static */
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();