Bug 1840065 [wpt PR 40721] - Switch from using AV1 to VP9 for the test trying to...
[gecko.git] / layout / base / nsBidiPresUtils.cpp
blob0bd480a0aa5ef8255d0aa172213319e9a5412932
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 kSpace = 0x0020;
51 static const char16_t kZWSP = 0x200B;
52 static const char16_t kLineSeparator = 0x2028;
53 static const char16_t kObjectSubstitute = 0xFFFC;
54 static const char16_t kLRE = 0x202A;
55 static const char16_t kRLE = 0x202B;
56 static const char16_t kLRO = 0x202D;
57 static const char16_t kRLO = 0x202E;
58 static const char16_t kPDF = 0x202C;
59 static const char16_t kLRI = 0x2066;
60 static const char16_t kRLI = 0x2067;
61 static const char16_t kFSI = 0x2068;
62 static const char16_t kPDI = 0x2069;
63 // All characters with Bidi type Segment Separator or Block Separator
64 static const char16_t kSeparators[] = {
65 char16_t('\t'), char16_t('\r'), char16_t('\n'), char16_t(0xb),
66 char16_t(0x1c), char16_t(0x1d), char16_t(0x1e), char16_t(0x1f),
67 char16_t(0x85), char16_t(0x2029), char16_t(0)};
69 #define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1)
71 // This exists just to be a type; the value doesn't matter.
72 enum class BidiControlFrameType { Value };
74 static bool IsIsolateControl(char16_t aChar) {
75 return aChar == kLRI || aChar == kRLI || aChar == kFSI;
78 // Given a ComputedStyle, return any bidi control character necessary to
79 // implement style properties that override directionality (i.e. if it has
80 // unicode-bidi:bidi-override, or text-orientation:upright in vertical
81 // writing mode) when applying the bidi algorithm.
83 // Returns 0 if no override control character is implied by this style.
84 static char16_t GetBidiOverride(ComputedStyle* aComputedStyle) {
85 const nsStyleVisibility* vis = aComputedStyle->StyleVisibility();
86 if ((vis->mWritingMode == StyleWritingModeProperty::VerticalRl ||
87 vis->mWritingMode == StyleWritingModeProperty::VerticalLr) &&
88 vis->mTextOrientation == StyleTextOrientation::Upright) {
89 return kLRO;
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;
96 return 0;
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
102 // algorithm.
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
108 // FSI and LRO/RLO.
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:
121 return kFSI;
122 case StyleUnicodeBidi::Normal:
123 case StyleUnicodeBidi::BidiOverride:
124 break;
127 return 0;
130 #ifdef DEBUG
131 static inline bool AreContinuationsInOrder(nsIFrame* aFrame1,
132 nsIFrame* aFrame2) {
133 nsIFrame* f = aFrame1;
134 do {
135 f = f->GetNextContinuation();
136 } while (f && f != aFrame2);
137 return !!f;
139 #endif
141 struct MOZ_STACK_CLASS BidiParagraphData {
142 struct FrameInfo {
143 FrameInfo(nsIFrame* aFrame, nsBlockInFlowLineIterator& aLineIter)
144 : mFrame(aFrame),
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) {}
153 FrameInfo()
154 : mFrame(nullptr), mBlockContainer(nullptr), mInOverflow(false) {}
156 nsIFrame* mFrame;
158 // The block containing mFrame (i.e., which continuation).
159 nsBlockFrame* mBlockContainer;
161 // true if mFrame is in mBlockContainer's overflow lines, false if
162 // in primary lines
163 bool mInOverflow;
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;
172 bool mIsVisual;
173 bool mRequiresBidi;
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
198 // iterating over.
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()
219 ? nullptr
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;
227 return false;
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
242 // over.
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()) {
254 child = parent;
255 parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
257 MOZ_ASSERT(parent, "aFrame is not a descendent of a block frame");
258 while (!IsFrameInCurrentLine(&mLineIterator, mPrevFrame, child)) {
259 #ifdef DEBUG
260 bool hasNext =
261 #endif
262 mLineIterator.Next();
263 MOZ_ASSERT(hasNext, "Can't find frame in lines!");
264 mPrevFrame = nullptr;
266 mPrevFrame = child;
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) {
276 MOZ_ASSERT(
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();
287 mLineIterator =
288 nsBlockInFlowLineIterator(block, lines, aFrameInfo.mInOverflow);
289 mPrevFrame = nullptr;
291 AdvanceToFrame(aFrameInfo.mFrame);
295 FastLineIterator mCurrentTraverseLine, mCurrentResolveLine;
297 #ifdef DEBUG
298 // Only used for NOISY debug output.
299 // Matches the current TraverseFrames state, not the ResolveParagraph
300 // state.
301 nsBlockFrame* mCurrentBlock;
302 #endif
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)
310 #ifdef DEBUG
312 mCurrentBlock(aBlockFrame)
313 #endif
315 if (mParaLevel > 0) {
316 mRequiresBidi = true;
319 if (mIsVisual) {
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()) {
337 mIsVisual = false;
338 break;
344 nsresult SetPara() {
345 if (mPresContext->BidiEngine().SetParagraph(mBuffer, mParaLevel).isErr()) {
346 return NS_ERROR_FAILURE;
348 return NS_OK;
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
356 * BidiDirection::RTL
358 BidiEmbeddingLevel GetParagraphEmbeddingLevel() {
359 BidiEmbeddingLevel paraLevel = mParaLevel;
360 if (paraLevel == BidiEmbeddingLevel::DefaultLTR() ||
361 paraLevel == BidiEmbeddingLevel::DefaultRTL()) {
362 paraLevel = mPresContext->BidiEngine().GetParagraphEmbeddingLevel();
364 return paraLevel;
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();
377 return NS_OK;
380 void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit,
381 BidiEmbeddingLevel* aLevel) {
382 mPresContext->BidiEngine().GetLogicalRun(aLogicalStart, aLogicalLimit,
383 aLevel);
384 if (mIsVisual) {
385 *aLevel = GetParagraphEmbeddingLevel();
389 void ResetData() {
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) {
402 if (aContent) {
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();
417 if (frame) {
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();
429 *aFrame = frame;
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));
455 AppendUnichar(aCh);
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());
470 AppendPopChar(aCh);
471 mEmbeddingStack.RemoveLastElement();
474 void ClearBidiControls() {
475 for (char16_t c : Reversed(mEmbeddingStack)) {
476 AppendPopChar(c);
481 struct MOZ_STACK_CLASS BidiLineData {
482 AutoTArray<nsIFrame*, 16> mLogicalFrames;
483 AutoTArray<nsIFrame*, 16> mVisualFrames;
484 AutoTArray<int32_t, 16> mIndexMap;
485 AutoTArray<BidiEmbeddingLevel, 16> mLevels;
486 bool mIsReordered;
488 BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) {
490 * Initialize the logically-ordered array of frames using the top-level
491 * frames of a single line
493 bool isReordered = false;
494 bool hasRTLFrames = false;
495 bool hasVirtualControls = false;
497 auto appendFrame = [&](nsIFrame* frame, BidiEmbeddingLevel level) {
498 mLogicalFrames.AppendElement(frame);
499 mLevels.AppendElement(level);
500 mIndexMap.AppendElement(0);
501 if (level.IsRTL()) {
502 hasRTLFrames = true;
506 bool firstFrame = true;
507 for (nsIFrame* frame = aFirstFrameOnLine; frame && aNumFramesOnLine--;
508 frame = frame->GetNextSibling()) {
509 FrameBidiData bidiData = nsBidiPresUtils::GetFrameBidiData(frame);
510 // Ignore virtual control before the first frame. Doing so should
511 // not affect the visual result, but could avoid running into the
512 // stripping code below for many cases.
513 if (!firstFrame && bidiData.precedingControl != kBidiLevelNone) {
514 appendFrame(NS_BIDI_CONTROL_FRAME, bidiData.precedingControl);
515 hasVirtualControls = true;
517 appendFrame(frame, bidiData.embeddingLevel);
518 firstFrame = false;
521 // Reorder the line
522 BidiEngine::ReorderVisual(mLevels.Elements(), FrameCount(),
523 mIndexMap.Elements());
525 // Strip virtual frames
526 if (hasVirtualControls) {
527 auto originalCount = mLogicalFrames.Length();
528 AutoTArray<int32_t, 16> realFrameMap;
529 realFrameMap.SetCapacity(originalCount);
530 size_t count = 0;
531 for (auto i : IntegerRange(originalCount)) {
532 if (mLogicalFrames[i] == NS_BIDI_CONTROL_FRAME) {
533 realFrameMap.AppendElement(-1);
534 } else {
535 mLogicalFrames[count] = mLogicalFrames[i];
536 mLevels[count] = mLevels[i];
537 realFrameMap.AppendElement(count);
538 count++;
541 // Only keep index map for real frames.
542 for (size_t i = 0, j = 0; i < originalCount; ++i) {
543 auto newIndex = realFrameMap[mIndexMap[i]];
544 if (newIndex != -1) {
545 mIndexMap[j] = newIndex;
546 j++;
549 mLogicalFrames.TruncateLength(count);
550 mLevels.TruncateLength(count);
551 mIndexMap.TruncateLength(count);
554 for (int32_t i = 0; i < FrameCount(); i++) {
555 mVisualFrames.AppendElement(LogicalFrameAt(mIndexMap[i]));
556 if (i != mIndexMap[i]) {
557 isReordered = true;
561 // If there's an RTL frame, assume the line is reordered
562 mIsReordered = isReordered || hasRTLFrames;
565 int32_t FrameCount() const { return mLogicalFrames.Length(); }
567 nsIFrame* LogicalFrameAt(int32_t aIndex) const {
568 return mLogicalFrames[aIndex];
571 nsIFrame* VisualFrameAt(int32_t aIndex) const {
572 return mVisualFrames[aIndex];
576 #ifdef DEBUG
577 extern "C" {
578 void MOZ_EXPORT DumpFrameArray(const nsTArray<nsIFrame*>& aFrames) {
579 for (nsIFrame* frame : aFrames) {
580 if (frame == NS_BIDI_CONTROL_FRAME) {
581 fprintf_stderr(stderr, "(Bidi control frame)\n");
582 } else {
583 frame->List();
588 void MOZ_EXPORT DumpBidiLine(BidiLineData* aData, bool aVisualOrder) {
589 DumpFrameArray(aVisualOrder ? aData->mVisualFrames : aData->mLogicalFrames);
592 #endif
594 /* Some helper methods for Resolve() */
596 // Should this frame be split between text runs?
597 static bool IsBidiSplittable(nsIFrame* aFrame) {
598 MOZ_ASSERT(aFrame);
599 // Bidi inline containers should be split, unless they're line frames.
600 LayoutFrameType frameType = aFrame->Type();
601 return (aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer) &&
602 frameType != LayoutFrameType::Line) ||
603 frameType == LayoutFrameType::Text;
606 // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
607 static bool IsBidiLeaf(const nsIFrame* aFrame) {
608 nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
609 if (kid) {
610 if (aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer) ||
611 RubyUtils::IsRubyBox(aFrame->Type())) {
612 return false;
615 return true;
619 * Create non-fluid continuations for the ancestors of a given frame all the way
620 * up the frame tree until we hit a non-splittable frame (a line or a block).
622 * @param aParent the first parent frame to be split
623 * @param aFrame the child frames after this frame are reparented to the
624 * newly-created continuation of aParent.
625 * If aFrame is null, all the children of aParent are reparented.
627 static void SplitInlineAncestors(nsContainerFrame* aParent,
628 nsLineList::iterator aLine, nsIFrame* aFrame) {
629 PresShell* presShell = aParent->PresShell();
630 nsIFrame* frame = aFrame;
631 nsContainerFrame* parent = aParent;
632 nsContainerFrame* newParent;
634 while (IsBidiSplittable(parent)) {
635 nsContainerFrame* grandparent = parent->GetParent();
636 NS_ASSERTION(grandparent,
637 "Couldn't get parent's parent in "
638 "nsBidiPresUtils::SplitInlineAncestors");
640 // Split the child list after |frame|, unless it is the last child.
641 if (!frame || frame->GetNextSibling()) {
642 newParent = static_cast<nsContainerFrame*>(
643 presShell->FrameConstructor()->CreateContinuingFrame(
644 parent, grandparent, false));
646 nsFrameList tail = parent->StealFramesAfter(frame);
648 // Reparent views as necessary
649 nsContainerFrame::ReparentFrameViewList(tail, parent, newParent);
651 // The parent's continuation adopts the siblings after the split.
652 MOZ_ASSERT(!newParent->IsBlockFrameOrSubclass(),
653 "blocks should not be IsBidiSplittable");
654 newParent->InsertFrames(FrameChildListID::NoReflowPrincipal, nullptr,
655 nullptr, std::move(tail));
657 // While passing &aLine to InsertFrames for a non-block isn't harmful
658 // because it's a no-op, it doesn't really make sense. However, the
659 // MOZ_ASSERT() we need to guarantee that it's safe only works if the
660 // parent is actually the block.
661 const nsLineList::iterator* parentLine;
662 if (grandparent->IsBlockFrameOrSubclass()) {
663 MOZ_ASSERT(aLine->Contains(parent));
664 parentLine = &aLine;
665 } else {
666 parentLine = nullptr;
669 // The list name FrameChildListID::NoReflowPrincipal would indicate we
670 // don't want reflow
671 grandparent->InsertFrames(FrameChildListID::NoReflowPrincipal, parent,
672 parentLine, nsFrameList(newParent, newParent));
675 frame = parent;
676 parent = grandparent;
680 static void MakeContinuationFluid(nsIFrame* aFrame, nsIFrame* aNext) {
681 NS_ASSERTION(!aFrame->GetNextInFlow() || aFrame->GetNextInFlow() == aNext,
682 "next-in-flow is not next continuation!");
683 aFrame->SetNextInFlow(aNext);
685 NS_ASSERTION(!aNext->GetPrevInFlow() || aNext->GetPrevInFlow() == aFrame,
686 "prev-in-flow is not prev continuation!");
687 aNext->SetPrevInFlow(aFrame);
690 static void MakeContinuationsNonFluidUpParentChain(nsIFrame* aFrame,
691 nsIFrame* aNext) {
692 nsIFrame* frame;
693 nsIFrame* next;
695 for (frame = aFrame, next = aNext;
696 frame && next && next != frame && next == frame->GetNextInFlow() &&
697 IsBidiSplittable(frame);
698 frame = frame->GetParent(), next = next->GetParent()) {
699 frame->SetNextContinuation(next);
700 next->SetPrevContinuation(frame);
704 // If aFrame is the last child of its parent, convert bidi continuations to
705 // fluid continuations for all of its inline ancestors.
706 // If it isn't the last child, make sure that its continuation is fluid.
707 static void JoinInlineAncestors(nsIFrame* aFrame) {
708 nsIFrame* frame = aFrame;
709 while (frame && IsBidiSplittable(frame)) {
710 nsIFrame* next = frame->GetNextContinuation();
711 if (next) {
712 MakeContinuationFluid(frame, next);
714 // Join the parent only as long as we're its last child.
715 if (frame->GetNextSibling()) break;
716 frame = frame->GetParent();
720 static void CreateContinuation(nsIFrame* aFrame,
721 const nsLineList::iterator aLine,
722 nsIFrame** aNewFrame, bool aIsFluid) {
723 MOZ_ASSERT(aNewFrame, "null OUT ptr");
724 MOZ_ASSERT(aFrame, "null ptr");
726 *aNewFrame = nullptr;
728 nsPresContext* presContext = aFrame->PresContext();
729 PresShell* presShell = presContext->PresShell();
730 NS_ASSERTION(presShell,
731 "PresShell must be set on PresContext before calling "
732 "nsBidiPresUtils::CreateContinuation");
734 nsContainerFrame* parent = aFrame->GetParent();
735 NS_ASSERTION(
736 parent,
737 "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
739 // While passing &aLine to InsertFrames for a non-block isn't harmful
740 // because it's a no-op, it doesn't really make sense. However, the
741 // MOZ_ASSERT() we need to guarantee that it's safe only works if the
742 // parent is actually the block.
743 const nsLineList::iterator* parentLine;
744 if (parent->IsBlockFrameOrSubclass()) {
745 MOZ_ASSERT(aLine->Contains(aFrame));
746 parentLine = &aLine;
747 } else {
748 parentLine = nullptr;
751 // Have to special case floating first letter frames because the continuation
752 // doesn't go in the first letter frame. The continuation goes with the rest
753 // of the text that the first letter frame was made out of.
754 if (parent->IsLetterFrame() && parent->IsFloating()) {
755 nsFirstLetterFrame* letterFrame = do_QueryFrame(parent);
756 letterFrame->CreateContinuationForFloatingParent(aFrame, aNewFrame,
757 aIsFluid);
758 return;
761 *aNewFrame = presShell->FrameConstructor()->CreateContinuingFrame(
762 aFrame, parent, aIsFluid);
764 // The list name FrameChildListID::NoReflowPrincipal would indicate we don't
765 // want reflow
766 // XXXbz this needs higher-level framelist love
767 parent->InsertFrames(FrameChildListID::NoReflowPrincipal, aFrame, parentLine,
768 nsFrameList(*aNewFrame, *aNewFrame));
770 if (!aIsFluid) {
771 // Split inline ancestor frames
772 SplitInlineAncestors(parent, aLine, aFrame);
777 * Overview of the implementation of Resolve():
779 * Walk through the descendants of aBlockFrame and build:
780 * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
781 * * mBuffer: an nsString containing a representation of
782 * the content of the frames.
783 * In the case of text frames, this is the actual text context of the
784 * frames, but some other elements are represented in a symbolic form which
785 * will make the Unicode Bidi Algorithm give the correct results.
786 * Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo>
787 * elements are represented by the corresponding Unicode control characters.
788 * <br> elements are represented by U+2028 LINE SEPARATOR
789 * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
790 * CHARACTER
792 * Then pass mBuffer to the Bidi engine for resolving of embedding levels
793 * by nsBidi::SetPara() and division into directional runs by
794 * nsBidi::CountRuns().
796 * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
797 * correlate them with the frames indexed in mLogicalFrames, setting the
798 * baseLevel and embeddingLevel properties according to the results returned
799 * by the Bidi engine.
801 * The rendering layer requires each text frame to contain text in only one
802 * direction, so we may need to call EnsureBidiContinuation() to split frames.
803 * We may also need to call RemoveBidiContinuation() to convert frames created
804 * by EnsureBidiContinuation() in previous reflows into fluid continuations.
806 nsresult nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame) {
807 BidiParagraphData bpd(aBlockFrame);
809 // Handle bidi-override being set on the block itself before calling
810 // TraverseFrames.
811 // No need to call GetBidiControl as well, because isolate and embed
812 // values of unicode-bidi property are redundant on block elements.
813 // unicode-bidi:plaintext on a block element is handled by block frame
814 // via using nsIFrame::GetWritingMode(nsIFrame*).
815 char16_t ch = GetBidiOverride(aBlockFrame->Style());
816 if (ch != 0) {
817 bpd.PushBidiControl(ch);
818 bpd.mRequiresBidi = true;
819 } else {
820 // If there are no unicode-bidi properties and no RTL characters in the
821 // block's content, then it is pure LTR and we can skip the rest of bidi
822 // resolution.
823 nsIContent* currContent = nullptr;
824 for (nsBlockFrame* block = aBlockFrame; block;
825 block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
826 block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
827 if (!bpd.mRequiresBidi &&
828 ChildListMayRequireBidi(block->PrincipalChildList().FirstChild(),
829 &currContent)) {
830 bpd.mRequiresBidi = true;
832 if (!bpd.mRequiresBidi) {
833 nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
834 if (overflowLines) {
835 if (ChildListMayRequireBidi(overflowLines->mFrames.FirstChild(),
836 &currContent)) {
837 bpd.mRequiresBidi = true;
842 if (!bpd.mRequiresBidi) {
843 return NS_OK;
847 for (nsBlockFrame* block = aBlockFrame; block;
848 block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
849 #ifdef DEBUG
850 bpd.mCurrentBlock = block;
851 #endif
852 block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
853 bpd.mCurrentTraverseLine.mLineIterator =
854 nsBlockInFlowLineIterator(block, block->LinesBegin());
855 bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
856 TraverseFrames(block->PrincipalChildList().FirstChild(), &bpd);
857 nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
858 if (overflowLines) {
859 bpd.mCurrentTraverseLine.mLineIterator =
860 nsBlockInFlowLineIterator(block, overflowLines->mLines.begin(), true);
861 bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
862 TraverseFrames(overflowLines->mFrames.FirstChild(), &bpd);
866 if (ch != 0) {
867 bpd.PopBidiControl(ch);
870 return ResolveParagraph(&bpd);
873 nsresult nsBidiPresUtils::ResolveParagraph(BidiParagraphData* aBpd) {
874 if (aBpd->BufferLength() < 1) {
875 return NS_OK;
877 aBpd->mBuffer.ReplaceChar(kSeparators, kSpace);
879 int32_t runCount;
881 nsresult rv = aBpd->SetPara();
882 NS_ENSURE_SUCCESS(rv, rv);
884 BidiEmbeddingLevel embeddingLevel = aBpd->GetParagraphEmbeddingLevel();
886 rv = aBpd->CountRuns(&runCount);
887 NS_ENSURE_SUCCESS(rv, rv);
889 int32_t runLength = 0; // the length of the current run of text
890 int32_t logicalLimit = 0; // the end of the current run + 1
891 int32_t numRun = -1;
892 int32_t fragmentLength = 0; // the length of the current text frame
893 int32_t frameIndex = -1; // index to the frames in mLogicalFrames
894 int32_t frameCount = aBpd->FrameCount();
895 int32_t contentOffset = 0; // offset of current frame in its content node
896 bool isTextFrame = false;
897 nsIFrame* frame = nullptr;
898 BidiParagraphData::FrameInfo frameInfo;
899 nsIContent* content = nullptr;
900 int32_t contentTextLength = 0;
902 #ifdef DEBUG
903 # ifdef NOISY_BIDI
904 printf(
905 "Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, "
906 "runCount=%d\n",
907 (void*)aBpd->mCurrentBlock, NS_ConvertUTF16toUTF8(aBpd->mBuffer).get(),
908 frameCount, runCount);
909 # ifdef REALLY_NOISY_BIDI
910 printf(" block frame tree=:\n");
911 aBpd->mCurrentBlock->List(stdout);
912 # endif
913 # endif
914 #endif
916 if (runCount == 1 && frameCount == 1 &&
917 aBpd->GetParagraphDirection() == BidiEngine::ParagraphDirection::LTR &&
918 aBpd->GetParagraphEmbeddingLevel() == 0) {
919 // We have a single left-to-right frame in a left-to-right paragraph,
920 // without bidi isolation from the surrounding text.
921 // Make sure that the embedding level and base level frame properties aren't
922 // set (because if they are this frame used to have some other direction,
923 // so we can't do this optimization), and we're done.
924 nsIFrame* frame = aBpd->FrameAt(0);
925 if (frame != NS_BIDI_CONTROL_FRAME) {
926 FrameBidiData bidiData = frame->GetBidiData();
927 if (!bidiData.embeddingLevel && !bidiData.baseLevel) {
928 #ifdef DEBUG
929 # ifdef NOISY_BIDI
930 printf("early return for single direction frame %p\n", (void*)frame);
931 # endif
932 #endif
933 frame->AddStateBits(NS_FRAME_IS_BIDI);
934 return NS_OK;
939 BidiParagraphData::FrameInfo lastRealFrame;
940 BidiEmbeddingLevel lastEmbeddingLevel = kBidiLevelNone;
941 BidiEmbeddingLevel precedingControl = kBidiLevelNone;
943 auto storeBidiDataToFrame = [&]() {
944 FrameBidiData bidiData;
945 bidiData.embeddingLevel = embeddingLevel;
946 bidiData.baseLevel = aBpd->GetParagraphEmbeddingLevel();
947 // If a control character doesn't have a lower embedding level than
948 // both the preceding and the following frame, it isn't something
949 // needed for getting the correct result. This optimization should
950 // remove almost all of embeds and overrides, and some of isolates.
951 if (precedingControl >= embeddingLevel ||
952 precedingControl >= lastEmbeddingLevel) {
953 bidiData.precedingControl = kBidiLevelNone;
954 } else {
955 bidiData.precedingControl = precedingControl;
957 precedingControl = kBidiLevelNone;
958 lastEmbeddingLevel = embeddingLevel;
959 frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData);
962 for (;;) {
963 if (fragmentLength <= 0) {
964 // Get the next frame from mLogicalFrames
965 if (++frameIndex >= frameCount) {
966 break;
968 frameInfo = aBpd->FrameInfoAt(frameIndex);
969 frame = frameInfo.mFrame;
970 if (frame == NS_BIDI_CONTROL_FRAME || !frame->IsTextFrame()) {
972 * Any non-text frame corresponds to a single character in the text
973 * buffer (a bidi control character, LINE SEPARATOR, or OBJECT
974 * SUBSTITUTE)
976 isTextFrame = false;
977 fragmentLength = 1;
978 } else {
979 aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(frameInfo);
980 content = frame->GetContent();
981 if (!content) {
982 rv = NS_OK;
983 break;
985 contentTextLength = content->TextLength();
986 auto [start, end] = frame->GetOffsets();
987 NS_ASSERTION(!(contentTextLength < end - start),
988 "Frame offsets don't fit in content");
989 fragmentLength = std::min(contentTextLength, end - start);
990 contentOffset = start;
991 isTextFrame = true;
993 } // if (fragmentLength <= 0)
995 if (runLength <= 0) {
996 // Get the next run of text from the Bidi engine
997 if (++numRun >= runCount) {
998 // We've run out of runs of text; but don't forget to store bidi data
999 // to the frame before breaking out of the loop (bug 1426042).
1000 storeBidiDataToFrame();
1001 if (isTextFrame) {
1002 frame->AdjustOffsetsForBidi(contentOffset,
1003 contentOffset + fragmentLength);
1005 break;
1007 int32_t lineOffset = logicalLimit;
1008 aBpd->GetLogicalRun(lineOffset, &logicalLimit, &embeddingLevel);
1009 runLength = logicalLimit - lineOffset;
1010 } // if (runLength <= 0)
1012 if (frame == NS_BIDI_CONTROL_FRAME) {
1013 // In theory, we only need to do this for isolates. However, it is
1014 // easier to do this for all here because we do not maintain the
1015 // index to get corresponding character from buffer. Since we do
1016 // have proper embedding level for all those characters, including
1017 // them wouldn't affect the final result.
1018 precedingControl = std::min(precedingControl, embeddingLevel);
1019 } else {
1020 storeBidiDataToFrame();
1021 if (isTextFrame) {
1022 if (contentTextLength == 0) {
1023 // Set the base level and embedding level of the current run even
1024 // on an empty frame. Otherwise frame reordering will not be correct.
1025 frame->AdjustOffsetsForBidi(0, 0);
1026 // Nothing more to do for an empty frame, except update
1027 // lastRealFrame like we do below.
1028 lastRealFrame = frameInfo;
1029 continue;
1031 nsLineList::iterator currentLine = aBpd->mCurrentResolveLine.GetLine();
1032 if ((runLength > 0) && (runLength < fragmentLength)) {
1034 * The text in this frame continues beyond the end of this directional
1035 * run. Create a non-fluid continuation frame for the next directional
1036 * run.
1038 currentLine->MarkDirty();
1039 nsIFrame* nextBidi;
1040 int32_t runEnd = contentOffset + runLength;
1041 EnsureBidiContinuation(frame, currentLine, &nextBidi, contentOffset,
1042 runEnd);
1043 nextBidi->AdjustOffsetsForBidi(runEnd,
1044 contentOffset + fragmentLength);
1045 frame = nextBidi;
1046 frameInfo.mFrame = frame;
1047 contentOffset = runEnd;
1049 aBpd->mCurrentResolveLine.AdvanceToFrame(frame);
1050 } // if (runLength < fragmentLength)
1051 else {
1052 if (contentOffset + fragmentLength == contentTextLength) {
1054 * We have finished all the text in this content node. Convert any
1055 * further non-fluid continuations to fluid continuations and
1056 * advance frameIndex to the last frame in the content node
1058 int32_t newIndex = aBpd->GetLastFrameForContent(content);
1059 if (newIndex > frameIndex) {
1060 currentLine->MarkDirty();
1061 RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
1062 frameIndex = newIndex;
1063 frameInfo = aBpd->FrameInfoAt(frameIndex);
1064 frame = frameInfo.mFrame;
1066 } else if (fragmentLength > 0 && runLength > fragmentLength) {
1068 * There is more text that belongs to this directional run in the
1069 * next text frame: make sure it is a fluid continuation of the
1070 * current frame. Do not advance frameIndex, because the next frame
1071 * may contain multi-directional text and need to be split
1073 int32_t newIndex = frameIndex;
1074 do {
1075 } while (++newIndex < frameCount &&
1076 aBpd->FrameAt(newIndex) == NS_BIDI_CONTROL_FRAME);
1077 if (newIndex < frameCount) {
1078 currentLine->MarkDirty();
1079 RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
1081 } else if (runLength == fragmentLength) {
1083 * If the directional run ends at the end of the frame, make sure
1084 * that any continuation is non-fluid, and do the same up the
1085 * parent chain
1087 nsIFrame* next = frame->GetNextInFlow();
1088 if (next) {
1089 currentLine->MarkDirty();
1090 MakeContinuationsNonFluidUpParentChain(frame, next);
1093 frame->AdjustOffsetsForBidi(contentOffset,
1094 contentOffset + fragmentLength);
1096 } // isTextFrame
1097 } // not bidi control frame
1098 int32_t temp = runLength;
1099 runLength -= fragmentLength;
1100 fragmentLength -= temp;
1102 // Record last real frame so that we can do splitting properly even
1103 // if a run ends after a virtual bidi control frame.
1104 if (frame != NS_BIDI_CONTROL_FRAME) {
1105 lastRealFrame = frameInfo;
1107 if (lastRealFrame.mFrame && fragmentLength <= 0) {
1108 // If the frame is at the end of a run, and this is not the end of our
1109 // paragraph, split all ancestor inlines that need splitting.
1110 // To determine whether we're at the end of the run, we check that we've
1111 // finished processing the current run, and that the current frame
1112 // doesn't have a fluid continuation (it could have a fluid continuation
1113 // of zero length, so testing runLength alone is not sufficient).
1114 if (runLength <= 0 && !lastRealFrame.mFrame->GetNextInFlow()) {
1115 if (numRun + 1 < runCount) {
1116 nsIFrame* child = lastRealFrame.mFrame;
1117 nsContainerFrame* parent = child->GetParent();
1118 // As long as we're on the last sibling, the parent doesn't have to
1119 // be split.
1120 // However, if the parent has a fluid continuation, we do have to make
1121 // it non-fluid. This can happen e.g. when we have a first-letter
1122 // frame and the end of the first-letter coincides with the end of a
1123 // directional run.
1124 while (parent && IsBidiSplittable(parent) &&
1125 !child->GetNextSibling()) {
1126 nsIFrame* next = parent->GetNextInFlow();
1127 if (next) {
1128 parent->SetNextContinuation(next);
1129 next->SetPrevContinuation(parent);
1131 child = parent;
1132 parent = child->GetParent();
1134 if (parent && IsBidiSplittable(parent)) {
1135 aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
1136 SplitInlineAncestors(parent, aBpd->mCurrentResolveLine.GetLine(),
1137 child);
1139 aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
1142 } else if (frame != NS_BIDI_CONTROL_FRAME) {
1143 // We're not at an end of a run. If |frame| is the last child of its
1144 // parent, and its ancestors happen to have bidi continuations, convert
1145 // them into fluid continuations.
1146 JoinInlineAncestors(frame);
1149 } // for
1151 #ifdef DEBUG
1152 # ifdef REALLY_NOISY_BIDI
1153 printf("---\nAfter Resolve(), frameTree =:\n");
1154 aBpd->mCurrentBlock->List(stdout);
1155 printf("===\n");
1156 # endif
1157 #endif
1159 return rv;
1162 void nsBidiPresUtils::TraverseFrames(nsIFrame* aCurrentFrame,
1163 BidiParagraphData* aBpd) {
1164 if (!aCurrentFrame) return;
1166 #ifdef DEBUG
1167 nsBlockFrame* initialLineContainer =
1168 aBpd->mCurrentTraverseLine.mLineIterator.GetContainer();
1169 #endif
1171 nsIFrame* childFrame = aCurrentFrame;
1172 do {
1174 * It's important to get the next sibling and next continuation *before*
1175 * handling the frame: If we encounter a forced paragraph break and call
1176 * ResolveParagraph within this loop, doing GetNextSibling and
1177 * GetNextContinuation after that could return a bidi continuation that had
1178 * just been split from the original childFrame and we would process it
1179 * twice.
1181 nsIFrame* nextSibling = childFrame->GetNextSibling();
1183 // If the real frame for a placeholder is a first letter frame, we need to
1184 // drill down into it and include its contents in Bidi resolution.
1185 // If not, we just use the placeholder.
1186 nsIFrame* frame = childFrame;
1187 if (childFrame->IsPlaceholderFrame()) {
1188 nsIFrame* realFrame =
1189 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
1190 if (realFrame->IsLetterFrame()) {
1191 frame = realFrame;
1195 auto DifferentBidiValues = [](ComputedStyle* aSC1, nsIFrame* aFrame2) {
1196 ComputedStyle* sc2 = aFrame2->Style();
1197 return GetBidiControl(aSC1) != GetBidiControl(sc2) ||
1198 GetBidiOverride(aSC1) != GetBidiOverride(sc2);
1201 ComputedStyle* sc = frame->Style();
1202 nsIFrame* nextContinuation = frame->GetNextContinuation();
1203 nsIFrame* prevContinuation = frame->GetPrevContinuation();
1204 bool isLastFrame =
1205 !nextContinuation || DifferentBidiValues(sc, nextContinuation);
1206 bool isFirstFrame =
1207 !prevContinuation || DifferentBidiValues(sc, prevContinuation);
1209 char16_t controlChar = 0;
1210 char16_t overrideChar = 0;
1211 LayoutFrameType frameType = frame->Type();
1212 if (frame->IsFrameOfType(nsIFrame::eBidiInlineContainer) ||
1213 RubyUtils::IsRubyBox(frameType)) {
1214 if (!frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
1215 nsContainerFrame* c = static_cast<nsContainerFrame*>(frame);
1216 MOZ_ASSERT(c == do_QueryFrame(frame),
1217 "eBidiInlineContainer and ruby frame must be"
1218 " a nsContainerFrame subclass");
1219 c->DrainSelfOverflowList();
1222 controlChar = GetBidiControl(sc);
1223 overrideChar = GetBidiOverride(sc);
1225 // Add dummy frame pointers representing bidi control codes before
1226 // the first frames of elements specifying override, isolation, or
1227 // plaintext.
1228 if (isFirstFrame) {
1229 if (controlChar != 0) {
1230 aBpd->PushBidiControl(controlChar);
1232 if (overrideChar != 0) {
1233 aBpd->PushBidiControl(overrideChar);
1238 if (IsBidiLeaf(frame)) {
1239 /* Bidi leaf frame: add the frame to the mLogicalFrames array,
1240 * and add its index to the mContentToFrameIndex hashtable. This
1241 * will be used in RemoveBidiContinuation() to identify the last
1242 * frame in the array with a given content.
1244 nsIContent* content = frame->GetContent();
1245 aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine, content);
1247 // Append the content of the frame to the paragraph buffer
1248 if (LayoutFrameType::Text == frameType) {
1249 if (content != aBpd->mPrevContent) {
1250 aBpd->mPrevContent = content;
1251 if (!frame->StyleText()->NewlineIsSignificant(
1252 static_cast<nsTextFrame*>(frame))) {
1253 content->GetAsText()->AppendTextTo(aBpd->mBuffer);
1254 } else {
1256 * For preformatted text we have to do bidi resolution on each line
1257 * separately.
1259 nsAutoString text;
1260 content->GetAsText()->AppendTextTo(text);
1261 nsIFrame* next;
1262 do {
1263 next = nullptr;
1265 auto [start, end] = frame->GetOffsets();
1266 int32_t endLine = text.FindChar('\n', start);
1267 if (endLine == -1) {
1269 * If there is no newline in the text content, just save the
1270 * text from this frame and its continuations, and do bidi
1271 * resolution later
1273 aBpd->AppendString(Substring(text, start));
1274 while (frame && nextSibling) {
1275 aBpd->AdvanceAndAppendFrame(
1276 &frame, aBpd->mCurrentTraverseLine, &nextSibling);
1278 break;
1282 * If there is a newline in the frame, break the frame after the
1283 * newline, do bidi resolution and repeat until the last sibling
1285 ++endLine;
1288 * If the frame ends before the new line, save the text and move
1289 * into the next continuation
1291 aBpd->AppendString(
1292 Substring(text, start, std::min(end, endLine) - start));
1293 while (end < endLine && nextSibling) {
1294 aBpd->AdvanceAndAppendFrame(&frame, aBpd->mCurrentTraverseLine,
1295 &nextSibling);
1296 NS_ASSERTION(frame, "Premature end of continuation chain");
1297 std::tie(start, end) = frame->GetOffsets();
1298 aBpd->AppendString(
1299 Substring(text, start, std::min(end, endLine) - start));
1302 if (end < endLine) {
1303 aBpd->mPrevContent = nullptr;
1304 break;
1307 bool createdContinuation = false;
1308 if (uint32_t(endLine) < text.Length()) {
1310 * Timing is everything here: if the frame already has a bidi
1311 * continuation, we need to make the continuation fluid *before*
1312 * resetting the length of the current frame. Otherwise
1313 * nsTextFrame::SetLength won't set the continuation frame's
1314 * text offsets correctly.
1316 * On the other hand, if the frame doesn't have a continuation,
1317 * we need to create one *after* resetting the length, or
1318 * CreateContinuingFrame will complain that there is no more
1319 * content for the continuation.
1321 next = frame->GetNextInFlow();
1322 if (!next) {
1323 // If the frame already has a bidi continuation, make it fluid
1324 next = frame->GetNextContinuation();
1325 if (next) {
1326 MakeContinuationFluid(frame, next);
1327 JoinInlineAncestors(frame);
1331 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
1332 textFrame->SetLength(endLine - start, nullptr);
1334 // If it weren't for CreateContinuation needing this to
1335 // be current, we could restructure the marking dirty
1336 // below to use mCurrentResolveLine and eliminate
1337 // mCurrentTraverseLine entirely.
1338 aBpd->mCurrentTraverseLine.AdvanceToFrame(frame);
1340 if (!next) {
1341 // If the frame has no next in flow, create one.
1342 CreateContinuation(
1343 frame, aBpd->mCurrentTraverseLine.GetLine(), &next, true);
1344 createdContinuation = true;
1346 // Mark the line before the newline as dirty.
1347 aBpd->mCurrentTraverseLine.GetLine()->MarkDirty();
1349 ResolveParagraphWithinBlock(aBpd);
1351 if (!nextSibling && !createdContinuation) {
1352 break;
1354 if (next) {
1355 frame = next;
1356 aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine);
1357 // Mark the line after the newline as dirty.
1358 aBpd->mCurrentTraverseLine.AdvanceToFrame(frame);
1359 aBpd->mCurrentTraverseLine.GetLine()->MarkDirty();
1363 * If we have already overshot the saved next-sibling while
1364 * scanning the frame's continuations, advance it.
1366 if (frame && frame == nextSibling) {
1367 nextSibling = frame->GetNextSibling();
1370 } while (next);
1373 } else if (LayoutFrameType::Br == frameType) {
1374 // break frame -- append line separator
1375 aBpd->AppendUnichar(kLineSeparator);
1376 ResolveParagraphWithinBlock(aBpd);
1377 } else {
1378 // other frame type -- see the Unicode Bidi Algorithm:
1379 // "...inline objects (such as graphics) are treated as if they are ...
1380 // U+FFFC"
1381 // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
1382 // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1
1383 aBpd->AppendUnichar(
1384 content->IsHTMLElement(nsGkAtoms::wbr) ? kZWSP : kObjectSubstitute);
1385 if (!frame->IsInlineOutside()) {
1386 // if it is not inline, end the paragraph
1387 ResolveParagraphWithinBlock(aBpd);
1390 } else {
1391 // For a non-leaf frame, recurse into TraverseFrames
1392 nsIFrame* kid = frame->PrincipalChildList().FirstChild();
1393 MOZ_ASSERT(!frame->GetChildList(FrameChildListID::Overflow).FirstChild(),
1394 "should have drained the overflow list above");
1395 if (kid) {
1396 TraverseFrames(kid, aBpd);
1400 // If the element is attributed by dir, indicate direction pop (add PDF
1401 // frame)
1402 if (isLastFrame) {
1403 // Add a dummy frame pointer representing a bidi control code after the
1404 // last frame of an element specifying embedding or override
1405 if (overrideChar != 0) {
1406 aBpd->PopBidiControl(overrideChar);
1408 if (controlChar != 0) {
1409 aBpd->PopBidiControl(controlChar);
1412 childFrame = nextSibling;
1413 } while (childFrame);
1415 MOZ_ASSERT(initialLineContainer ==
1416 aBpd->mCurrentTraverseLine.mLineIterator.GetContainer());
1419 bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame* aFirstChild,
1420 nsIContent** aCurrContent) {
1421 MOZ_ASSERT(!aFirstChild || !aFirstChild->GetPrevSibling(),
1422 "Expecting to traverse from the start of a child list");
1424 for (nsIFrame* childFrame = aFirstChild; childFrame;
1425 childFrame = childFrame->GetNextSibling()) {
1426 nsIFrame* frame = childFrame;
1428 // If the real frame for a placeholder is a first-letter frame, we need to
1429 // consider its contents for potential Bidi resolution.
1430 if (childFrame->IsPlaceholderFrame()) {
1431 nsIFrame* realFrame =
1432 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
1433 if (realFrame->IsLetterFrame()) {
1434 frame = realFrame;
1438 // If unicode-bidi properties are present, we should do bidi resolution.
1439 ComputedStyle* sc = frame->Style();
1440 if (GetBidiControl(sc) || GetBidiOverride(sc)) {
1441 return true;
1444 if (IsBidiLeaf(frame)) {
1445 if (frame->IsTextFrame()) {
1446 // If the frame already has a BidiDataProperty, we know we need to
1447 // perform bidi resolution (even if no bidi content is NOW present --
1448 // we might need to remove the property set by a previous reflow, if
1449 // content has changed; see bug 1366623).
1450 if (frame->HasProperty(nsIFrame::BidiDataProperty())) {
1451 return true;
1454 // Check whether the text frame has any RTL characters; if so, bidi
1455 // resolution will be needed.
1456 dom::Text* content = frame->GetContent()->AsText();
1457 if (content != *aCurrContent) {
1458 *aCurrContent = content;
1459 const nsTextFragment* txt = &content->TextFragment();
1460 if (txt->Is2b() &&
1461 HasRTLChars(Span(txt->Get2b(), txt->GetLength()))) {
1462 return true;
1466 } else if (ChildListMayRequireBidi(frame->PrincipalChildList().FirstChild(),
1467 aCurrContent)) {
1468 return true;
1472 return false;
1475 void nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData* aBpd) {
1476 aBpd->ClearBidiControls();
1477 ResolveParagraph(aBpd);
1478 aBpd->ResetData();
1481 /* static */
1482 nscoord nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine,
1483 int32_t aNumFramesOnLine,
1484 WritingMode aLineWM,
1485 const nsSize& aContainerSize,
1486 nscoord aStart) {
1487 nsSize containerSize(aContainerSize);
1489 // If this line consists of a line frame, reorder the line frame's children.
1490 if (aFirstFrameOnLine->IsLineFrame()) {
1491 // The line frame is positioned at the start-edge, so use its size
1492 // as the container size.
1493 containerSize = aFirstFrameOnLine->GetSize();
1495 aFirstFrameOnLine = aFirstFrameOnLine->PrincipalChildList().FirstChild();
1496 if (!aFirstFrameOnLine) {
1497 return 0;
1499 // All children of the line frame are on the first line. Setting
1500 // aNumFramesOnLine to -1 makes InitLogicalArrayFromLine look at all of
1501 // them.
1502 aNumFramesOnLine = -1;
1503 // As the line frame itself has been adjusted at its inline-start position
1504 // by the caller, we do not want to apply this to its children.
1505 aStart = 0;
1508 BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
1509 return RepositionInlineFrames(&bld, aLineWM, containerSize, aStart);
1512 nsIFrame* nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame) {
1513 nsIFrame* firstLeaf = aFrame;
1514 while (!IsBidiLeaf(firstLeaf)) {
1515 nsIFrame* firstChild = firstLeaf->PrincipalChildList().FirstChild();
1516 nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(firstChild);
1517 firstLeaf = (realFrame->IsLetterFrame()) ? realFrame : firstChild;
1519 return firstLeaf;
1522 FrameBidiData nsBidiPresUtils::GetFrameBidiData(nsIFrame* aFrame) {
1523 return GetFirstLeaf(aFrame)->GetBidiData();
1526 BidiEmbeddingLevel nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) {
1527 return GetFirstLeaf(aFrame)->GetEmbeddingLevel();
1530 BidiEmbeddingLevel nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame* aFrame) {
1531 const nsIFrame* firstLeaf = aFrame;
1532 while (!IsBidiLeaf(firstLeaf)) {
1533 firstLeaf = firstLeaf->PrincipalChildList().FirstChild();
1535 return firstLeaf->GetBaseLevel();
1538 void nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame,
1539 nsContinuationStates* aContinuationStates,
1540 bool aSpanDirMatchesLineDir,
1541 bool& aIsFirst /* out */,
1542 bool& aIsLast /* out */) {
1544 * Since we lay out frames in the line's direction, visiting a frame with
1545 * 'mFirstVisualFrame == nullptr', means it's the first appearance of one
1546 * of its continuation chain frames on the line.
1547 * To determine if it's the last visual frame of its continuation chain on
1548 * the line or not, we count the number of frames of the chain on the line,
1549 * and then reduce it when we lay out a frame of the chain. If this value
1550 * becomes 1 it means that it's the last visual frame of its continuation
1551 * chain on this line.
1554 bool firstInLineOrder, lastInLineOrder;
1555 nsFrameContinuationState* frameState = aContinuationStates->Get(aFrame);
1556 nsFrameContinuationState* firstFrameState;
1558 if (!frameState->mFirstVisualFrame) {
1559 // aFrame is the first visual frame of its continuation chain
1560 nsFrameContinuationState* contState;
1561 nsIFrame* frame;
1563 frameState->mFrameCount = 1;
1564 frameState->mFirstVisualFrame = aFrame;
1567 * Traverse continuation chain of aFrame in both backward and forward
1568 * directions while the frames are on this line. Count the frames and
1569 * set their mFirstVisualFrame to aFrame.
1571 // Traverse continuation chain backward
1572 for (frame = aFrame->GetPrevContinuation();
1573 frame && (contState = aContinuationStates->Get(frame));
1574 frame = frame->GetPrevContinuation()) {
1575 frameState->mFrameCount++;
1576 contState->mFirstVisualFrame = aFrame;
1578 frameState->mHasContOnPrevLines = (frame != nullptr);
1580 // Traverse continuation chain forward
1581 for (frame = aFrame->GetNextContinuation();
1582 frame && (contState = aContinuationStates->Get(frame));
1583 frame = frame->GetNextContinuation()) {
1584 frameState->mFrameCount++;
1585 contState->mFirstVisualFrame = aFrame;
1587 frameState->mHasContOnNextLines = (frame != nullptr);
1589 firstInLineOrder = true;
1590 firstFrameState = frameState;
1591 } else {
1592 // aFrame is not the first visual frame of its continuation chain
1593 firstInLineOrder = false;
1594 firstFrameState = aContinuationStates->Get(frameState->mFirstVisualFrame);
1597 lastInLineOrder = (firstFrameState->mFrameCount == 1);
1599 if (aSpanDirMatchesLineDir) {
1600 aIsFirst = firstInLineOrder;
1601 aIsLast = lastInLineOrder;
1602 } else {
1603 aIsFirst = lastInLineOrder;
1604 aIsLast = firstInLineOrder;
1607 if (frameState->mHasContOnPrevLines) {
1608 aIsFirst = false;
1610 if (firstFrameState->mHasContOnNextLines) {
1611 aIsLast = false;
1614 if ((aIsFirst || aIsLast) &&
1615 aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
1616 // For ib splits, don't treat anything except the last part as
1617 // endmost or anything except the first part as startmost.
1618 // As an optimization, only get the first continuation once.
1619 nsIFrame* firstContinuation = aFrame->FirstContinuation();
1620 if (firstContinuation->FrameIsNonLastInIBSplit()) {
1621 // We are not endmost
1622 aIsLast = false;
1624 if (firstContinuation->FrameIsNonFirstInIBSplit()) {
1625 // We are not startmost
1626 aIsFirst = false;
1630 // Reduce number of remaining frames of the continuation chain on the line.
1631 firstFrameState->mFrameCount--;
1633 nsInlineFrame* testFrame = do_QueryFrame(aFrame);
1635 if (testFrame) {
1636 aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET);
1638 if (aIsFirst) {
1639 aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
1640 } else {
1641 aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
1644 if (aIsLast) {
1645 aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
1646 } else {
1647 aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
1652 /* static */
1653 void nsBidiPresUtils::RepositionRubyContentFrame(
1654 nsIFrame* aFrame, WritingMode aFrameWM,
1655 const LogicalMargin& aBorderPadding) {
1656 const nsFrameList& childList = aFrame->PrincipalChildList();
1657 if (childList.IsEmpty()) {
1658 return;
1661 // Reorder the children.
1662 nscoord isize =
1663 ReorderFrames(childList.FirstChild(), childList.GetLength(), aFrameWM,
1664 aFrame->GetSize(), aBorderPadding.IStart(aFrameWM));
1665 isize += aBorderPadding.IEnd(aFrameWM);
1667 if (aFrame->StyleText()->mRubyAlign == StyleRubyAlign::Start) {
1668 return;
1670 nscoord residualISize = aFrame->ISize(aFrameWM) - isize;
1671 if (residualISize <= 0) {
1672 return;
1675 // When ruby-align is not "start", if the content does not fill this
1676 // frame, we need to center the children.
1677 const nsSize dummyContainerSize;
1678 for (nsIFrame* child : childList) {
1679 LogicalRect rect = child->GetLogicalRect(aFrameWM, dummyContainerSize);
1680 rect.IStart(aFrameWM) += residualISize / 2;
1681 child->SetRect(aFrameWM, rect, dummyContainerSize);
1685 /* static */
1686 nscoord nsBidiPresUtils::RepositionRubyFrame(
1687 nsIFrame* aFrame, nsContinuationStates* aContinuationStates,
1688 const WritingMode aContainerWM, const LogicalMargin& aBorderPadding) {
1689 LayoutFrameType frameType = aFrame->Type();
1690 MOZ_ASSERT(RubyUtils::IsRubyBox(frameType));
1692 nscoord icoord = 0;
1693 WritingMode frameWM = aFrame->GetWritingMode();
1694 bool isLTR = frameWM.IsBidiLTR();
1695 nsSize frameSize = aFrame->GetSize();
1696 if (frameType == LayoutFrameType::Ruby) {
1697 icoord += aBorderPadding.IStart(frameWM);
1698 // Reposition ruby segments in a ruby container
1699 for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(aFrame)); !e.AtEnd();
1700 e.Next()) {
1701 nsRubyBaseContainerFrame* rbc = e.GetBaseContainer();
1702 AutoRubyTextContainerArray textContainers(rbc);
1704 nscoord segmentISize = RepositionFrame(
1705 rbc, isLTR, icoord, aContinuationStates, frameWM, false, frameSize);
1706 for (nsRubyTextContainerFrame* rtc : textContainers) {
1707 nscoord isize = RepositionFrame(rtc, isLTR, icoord, aContinuationStates,
1708 frameWM, false, frameSize);
1709 segmentISize = std::max(segmentISize, isize);
1711 icoord += segmentISize;
1713 icoord += aBorderPadding.IEnd(frameWM);
1714 } else if (frameType == LayoutFrameType::RubyBaseContainer) {
1715 // Reposition ruby columns in a ruby segment
1716 auto rbc = static_cast<nsRubyBaseContainerFrame*>(aFrame);
1717 AutoRubyTextContainerArray textContainers(rbc);
1719 for (RubyColumnEnumerator e(rbc, textContainers); !e.AtEnd(); e.Next()) {
1720 RubyColumn column;
1721 e.GetColumn(column);
1723 nscoord columnISize =
1724 RepositionFrame(column.mBaseFrame, isLTR, icoord, aContinuationStates,
1725 frameWM, false, frameSize);
1726 for (nsRubyTextFrame* rt : column.mTextFrames) {
1727 nscoord isize = RepositionFrame(rt, isLTR, icoord, aContinuationStates,
1728 frameWM, false, frameSize);
1729 columnISize = std::max(columnISize, isize);
1731 icoord += columnISize;
1733 } else {
1734 if (frameType == LayoutFrameType::RubyBase ||
1735 frameType == LayoutFrameType::RubyText) {
1736 RepositionRubyContentFrame(aFrame, frameWM, aBorderPadding);
1738 // Note that, ruby text container is not present in all conditions
1739 // above. It is intended, because the children of rtc are reordered
1740 // with the children of ruby base container simultaneously. We only
1741 // need to return its isize here, as it should not be changed.
1742 icoord += aFrame->ISize(aContainerWM);
1744 return icoord;
1747 /* static */
1748 nscoord nsBidiPresUtils::RepositionFrame(
1749 nsIFrame* aFrame, bool aIsEvenLevel, nscoord aStartOrEnd,
1750 nsContinuationStates* aContinuationStates, WritingMode aContainerWM,
1751 bool aContainerReverseDir, const nsSize& aContainerSize) {
1752 nscoord lineSize =
1753 aContainerWM.IsVertical() ? aContainerSize.height : aContainerSize.width;
1754 NS_ASSERTION(lineSize != NS_UNCONSTRAINEDSIZE,
1755 "Unconstrained inline line size in bidi frame reordering");
1756 if (!aFrame) return 0;
1758 bool isFirst, isLast;
1759 WritingMode frameWM = aFrame->GetWritingMode();
1760 IsFirstOrLast(aFrame, aContinuationStates,
1761 aContainerWM.IsBidiLTR() == frameWM.IsBidiLTR(),
1762 isFirst /* out */, isLast /* out */);
1764 // We only need the margin if the frame is first or last in its own
1765 // writing mode, but we're traversing the frames in the order of the
1766 // container's writing mode. To get the right values, we set start and
1767 // end margins on a logical margin in the frame's writing mode, and
1768 // then convert the margin to the container's writing mode to set the
1769 // coordinates.
1771 // This method is called from nsBlockFrame::PlaceLine via the call to
1772 // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
1773 // have been reflowed, which is required for GetUsedMargin/Border/Padding
1774 nscoord frameISize = aFrame->ISize();
1775 LogicalMargin frameMargin = aFrame->GetLogicalUsedMargin(frameWM);
1776 LogicalMargin borderPadding = aFrame->GetLogicalUsedBorderAndPadding(frameWM);
1777 // Since the visual order of frame could be different from the continuation
1778 // order, we need to remove any inline border/padding [that is already applied
1779 // based on continuation order] and then add it back based on the visual order
1780 // (i.e. isFirst/isLast) to get the correct isize for the current frame.
1781 // We don't need to do that for 'box-decoration-break:clone' because then all
1782 // continuations have border/padding/margin applied.
1783 if (aFrame->StyleBorder()->mBoxDecorationBreak ==
1784 StyleBoxDecorationBreak::Slice) {
1785 // First remove the border/padding that was applied based on logical order.
1786 if (!aFrame->GetPrevContinuation()) {
1787 frameISize -= borderPadding.IStart(frameWM);
1789 if (!aFrame->GetNextContinuation()) {
1790 frameISize -= borderPadding.IEnd(frameWM);
1792 // Set margin/border/padding based on visual order.
1793 if (!isFirst) {
1794 frameMargin.IStart(frameWM) = 0;
1795 borderPadding.IStart(frameWM) = 0;
1797 if (!isLast) {
1798 frameMargin.IEnd(frameWM) = 0;
1799 borderPadding.IEnd(frameWM) = 0;
1801 // Add the border/padding which is now based on visual order.
1802 frameISize += borderPadding.IStartEnd(frameWM);
1805 nscoord icoord = 0;
1806 if (IsBidiLeaf(aFrame)) {
1807 icoord +=
1808 frameWM.IsOrthogonalTo(aContainerWM) ? aFrame->BSize() : frameISize;
1809 } else if (RubyUtils::IsRubyBox(aFrame->Type())) {
1810 icoord += RepositionRubyFrame(aFrame, aContinuationStates, aContainerWM,
1811 borderPadding);
1812 } else {
1813 bool reverseDir = aIsEvenLevel != frameWM.IsBidiLTR();
1814 icoord += reverseDir ? borderPadding.IEnd(frameWM)
1815 : borderPadding.IStart(frameWM);
1816 LogicalSize logicalSize(frameWM, frameISize, aFrame->BSize());
1817 nsSize frameSize = logicalSize.GetPhysicalSize(frameWM);
1818 // Reposition the child frames
1819 for (nsIFrame* f : aFrame->PrincipalChildList()) {
1820 icoord += RepositionFrame(f, aIsEvenLevel, icoord, aContinuationStates,
1821 frameWM, reverseDir, frameSize);
1823 icoord += reverseDir ? borderPadding.IStart(frameWM)
1824 : borderPadding.IEnd(frameWM);
1827 // In the following variables, if aContainerReverseDir is true, i.e.
1828 // the container is positioning its children in reverse of its logical
1829 // direction, the "StartOrEnd" refers to the distance from the frame
1830 // to the inline end edge of the container, elsewise, it refers to the
1831 // distance to the inline start edge.
1832 const LogicalMargin margin = frameMargin.ConvertTo(aContainerWM, frameWM);
1833 nscoord marginStartOrEnd = aContainerReverseDir ? margin.IEnd(aContainerWM)
1834 : margin.IStart(aContainerWM);
1835 nscoord frameStartOrEnd = aStartOrEnd + marginStartOrEnd;
1837 LogicalRect rect = aFrame->GetLogicalRect(aContainerWM, aContainerSize);
1838 rect.ISize(aContainerWM) = icoord;
1839 rect.IStart(aContainerWM) = aContainerReverseDir
1840 ? lineSize - frameStartOrEnd - icoord
1841 : frameStartOrEnd;
1842 aFrame->SetRect(aContainerWM, rect, aContainerSize);
1844 return icoord + margin.IStartEnd(aContainerWM);
1847 void nsBidiPresUtils::InitContinuationStates(
1848 nsIFrame* aFrame, nsContinuationStates* aContinuationStates) {
1849 aContinuationStates->Insert(aFrame);
1850 if (!IsBidiLeaf(aFrame)) {
1851 // Continue for child frames
1852 for (nsIFrame* frame : aFrame->PrincipalChildList()) {
1853 InitContinuationStates(frame, aContinuationStates);
1858 /* static */
1859 nscoord nsBidiPresUtils::RepositionInlineFrames(BidiLineData* aBld,
1860 WritingMode aLineWM,
1861 const nsSize& aContainerSize,
1862 nscoord aStart) {
1863 nscoord start = aStart;
1864 nsIFrame* frame;
1865 int32_t count = aBld->mVisualFrames.Length();
1866 int32_t index;
1867 nsContinuationStates continuationStates;
1869 // Initialize continuation states to (nullptr, 0) for
1870 // each frame on the line.
1871 for (index = 0; index < count; index++) {
1872 InitContinuationStates(aBld->VisualFrameAt(index), &continuationStates);
1875 // Reposition frames in visual order
1876 int32_t step, limit;
1877 if (aLineWM.IsBidiLTR()) {
1878 index = 0;
1879 step = 1;
1880 limit = count;
1881 } else {
1882 index = count - 1;
1883 step = -1;
1884 limit = -1;
1886 for (; index != limit; index += step) {
1887 frame = aBld->VisualFrameAt(index);
1888 start += RepositionFrame(
1889 frame, !(aBld->mLevels[aBld->mIndexMap[index]].IsRTL()), start,
1890 &continuationStates, aLineWM, false, aContainerSize);
1892 return start;
1895 bool nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine,
1896 int32_t aNumFramesOnLine,
1897 nsIFrame** aFirstVisual,
1898 nsIFrame** aLastVisual) {
1899 BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
1900 int32_t count = bld.FrameCount();
1902 if (aFirstVisual) {
1903 *aFirstVisual = bld.VisualFrameAt(0);
1905 if (aLastVisual) {
1906 *aLastVisual = bld.VisualFrameAt(count - 1);
1909 return bld.mIsReordered;
1912 nsIFrame* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame,
1913 nsIFrame* aFirstFrameOnLine,
1914 int32_t aNumFramesOnLine) {
1915 BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
1917 int32_t count = bld.mVisualFrames.Length();
1919 if (aFrame == nullptr && count) return bld.VisualFrameAt(0);
1921 for (int32_t i = 0; i < count - 1; i++) {
1922 if (bld.VisualFrameAt(i) == aFrame) {
1923 return bld.VisualFrameAt(i + 1);
1927 return nullptr;
1930 nsIFrame* nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame* aFrame,
1931 nsIFrame* aFirstFrameOnLine,
1932 int32_t aNumFramesOnLine) {
1933 BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
1935 int32_t count = bld.mVisualFrames.Length();
1937 if (aFrame == nullptr && count) return bld.VisualFrameAt(count - 1);
1939 for (int32_t i = 1; i < count; i++) {
1940 if (bld.VisualFrameAt(i) == aFrame) {
1941 return bld.VisualFrameAt(i - 1);
1945 return nullptr;
1948 inline void nsBidiPresUtils::EnsureBidiContinuation(
1949 nsIFrame* aFrame, const nsLineList::iterator aLine, nsIFrame** aNewFrame,
1950 int32_t aStart, int32_t aEnd) {
1951 MOZ_ASSERT(aNewFrame, "null OUT ptr");
1952 MOZ_ASSERT(aFrame, "aFrame is null");
1954 aFrame->AdjustOffsetsForBidi(aStart, aEnd);
1955 CreateContinuation(aFrame, aLine, aNewFrame, false);
1958 void nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData* aBpd,
1959 nsIFrame* aFrame,
1960 int32_t aFirstIndex,
1961 int32_t aLastIndex) {
1962 FrameBidiData bidiData = aFrame->GetBidiData();
1963 bidiData.precedingControl = kBidiLevelNone;
1964 for (int32_t index = aFirstIndex + 1; index <= aLastIndex; index++) {
1965 nsIFrame* frame = aBpd->FrameAt(index);
1966 if (frame != NS_BIDI_CONTROL_FRAME) {
1967 // Make the frame and its continuation ancestors fluid,
1968 // so they can be reused or deleted by normal reflow code
1969 frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData);
1970 frame->AddStateBits(NS_FRAME_IS_BIDI);
1971 while (frame && IsBidiSplittable(frame)) {
1972 nsIFrame* prev = frame->GetPrevContinuation();
1973 if (prev) {
1974 MakeContinuationFluid(prev, frame);
1975 frame = frame->GetParent();
1976 } else {
1977 break;
1983 // Make sure that the last continuation we made fluid does not itself have a
1984 // fluid continuation (this can happen when re-resolving after dynamic changes
1985 // to content)
1986 nsIFrame* lastFrame = aBpd->FrameAt(aLastIndex);
1987 MakeContinuationsNonFluidUpParentChain(lastFrame, lastFrame->GetNextInFlow());
1990 nsresult nsBidiPresUtils::FormatUnicodeText(nsPresContext* aPresContext,
1991 char16_t* aText,
1992 int32_t& aTextLength,
1993 BidiClass aBidiClass) {
1994 nsresult rv = NS_OK;
1995 // ahmed
1996 // adjusted for correct numeral shaping
1997 uint32_t bidiOptions = aPresContext->GetBidi();
1998 switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) {
1999 case IBMBIDI_NUMERAL_HINDI:
2000 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
2001 break;
2003 case IBMBIDI_NUMERAL_ARABIC:
2004 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
2005 break;
2007 case IBMBIDI_NUMERAL_PERSIAN:
2008 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN);
2009 break;
2011 case IBMBIDI_NUMERAL_REGULAR:
2013 switch (aBidiClass) {
2014 case BidiClass::EuropeanNumber:
2015 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
2016 break;
2018 case BidiClass::ArabicNumber:
2019 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
2020 break;
2022 default:
2023 break;
2025 break;
2027 case IBMBIDI_NUMERAL_HINDICONTEXT:
2028 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions) ==
2029 IBMBIDI_TEXTDIRECTION_RTL) &&
2030 (IS_ARABIC_DIGIT(aText[0]))) ||
2031 (BidiClass::ArabicNumber == aBidiClass)) {
2032 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
2033 } else if (BidiClass::EuropeanNumber == aBidiClass) {
2034 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
2036 break;
2038 case IBMBIDI_NUMERAL_PERSIANCONTEXT:
2039 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions) ==
2040 IBMBIDI_TEXTDIRECTION_RTL) &&
2041 (IS_ARABIC_DIGIT(aText[0]))) ||
2042 (BidiClass::ArabicNumber == aBidiClass)) {
2043 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN);
2044 } else if (BidiClass::EuropeanNumber == aBidiClass) {
2045 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
2047 break;
2049 case IBMBIDI_NUMERAL_NOMINAL:
2050 default:
2051 break;
2054 StripBidiControlCharacters(aText, aTextLength);
2055 return rv;
2058 void nsBidiPresUtils::StripBidiControlCharacters(char16_t* aText,
2059 int32_t& aTextLength) {
2060 if ((nullptr == aText) || (aTextLength < 1)) {
2061 return;
2064 int32_t stripLen = 0;
2066 for (int32_t i = 0; i < aTextLength; i++) {
2067 // XXX: This silently ignores surrogate characters.
2068 // As of Unicode 4.0, all Bidi control characters are within the BMP.
2069 if (IsBidiControl((uint32_t)aText[i])) {
2070 ++stripLen;
2071 } else {
2072 aText[i - stripLen] = aText[i];
2075 aTextLength -= stripLen;
2078 void nsBidiPresUtils::CalculateBidiClass(
2079 const char16_t* aText, int32_t& aOffset, int32_t aBidiClassLimit,
2080 int32_t& aRunLimit, int32_t& aRunLength, int32_t& aRunCount,
2081 BidiClass& aBidiClass, BidiClass& aPrevBidiClass) {
2082 bool strongTypeFound = false;
2083 int32_t offset;
2084 BidiClass bidiClass;
2086 aBidiClass = BidiClass::OtherNeutral;
2088 int32_t charLen;
2089 for (offset = aOffset; offset < aBidiClassLimit; offset += charLen) {
2090 // Make sure we give RTL chartype to all characters that would be classified
2091 // as Right-To-Left by a bidi platform.
2092 // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
2093 charLen = 1;
2094 uint32_t ch = aText[offset];
2095 if (IS_HEBREW_CHAR(ch)) {
2096 bidiClass = BidiClass::RightToLeft;
2097 } else if (IS_ARABIC_ALPHABETIC(ch)) {
2098 bidiClass = BidiClass::RightToLeftArabic;
2099 } else {
2100 if (offset + 1 < aBidiClassLimit &&
2101 NS_IS_SURROGATE_PAIR(ch, aText[offset + 1])) {
2102 ch = SURROGATE_TO_UCS4(ch, aText[offset + 1]);
2103 charLen = 2;
2105 bidiClass = intl::UnicodeProperties::GetBidiClass(ch);
2108 if (!BIDICLASS_IS_WEAK(bidiClass)) {
2109 if (strongTypeFound && (bidiClass != aPrevBidiClass) &&
2110 (BIDICLASS_IS_RTL(bidiClass) || BIDICLASS_IS_RTL(aPrevBidiClass))) {
2111 // Stop at this point to ensure uni-directionality of the text
2112 // (from platform's point of view).
2113 // Also, don't mix Arabic and Hebrew content (since platform may
2114 // provide BIDI support to one of them only).
2115 aRunLength = offset - aOffset;
2116 aRunLimit = offset;
2117 ++aRunCount;
2118 break;
2121 if ((BidiClass::RightToLeftArabic == aPrevBidiClass ||
2122 BidiClass::ArabicNumber == aPrevBidiClass) &&
2123 BidiClass::EuropeanNumber == bidiClass) {
2124 bidiClass = BidiClass::ArabicNumber;
2127 // Set PrevBidiClass to the last strong type in this frame
2128 // (for correct numeric shaping)
2129 aPrevBidiClass = bidiClass;
2131 strongTypeFound = true;
2132 aBidiClass = bidiClass;
2135 aOffset = offset;
2138 nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, size_t aLength,
2139 BidiEmbeddingLevel aBaseLevel,
2140 nsPresContext* aPresContext,
2141 BidiProcessor& aprocessor, Mode aMode,
2142 nsBidiPositionResolve* aPosResolve,
2143 int32_t aPosResolveCount, nscoord* aWidth,
2144 BidiEngine& aBidiEngine) {
2145 MOZ_ASSERT((aPosResolve == nullptr) != (aPosResolveCount > 0),
2146 "Incorrect aPosResolve / aPosResolveCount arguments");
2148 // Caller should have already replaced any separators in the original text
2149 // with <space> characters.
2150 MOZ_ASSERT(nsDependentSubstring(aText, aLength).FindCharInSet(kSeparators) ==
2151 kNotFound);
2153 for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
2154 aPosResolve[nPosResolve].visualIndex = kNotFound;
2155 aPosResolve[nPosResolve].visualLeftTwips = kNotFound;
2156 aPosResolve[nPosResolve].visualWidth = kNotFound;
2159 // For a single-char string, or a string that is purely LTR, use a simplified
2160 // path as it cannot have multiple direction or bidi-class runs.
2161 if (aLength == 1 ||
2162 (aLength == 2 && NS_IS_SURROGATE_PAIR(aText[0], aText[1])) ||
2163 (aBaseLevel.Direction() == BidiDirection::LTR &&
2164 !encoding_mem_is_utf16_bidi(aText, aLength))) {
2165 ProcessSimpleRun(aText, aLength, aBaseLevel, aPresContext, aprocessor,
2166 aMode, aPosResolve, aPosResolveCount, aWidth);
2167 return NS_OK;
2170 if (aBidiEngine.SetParagraph(Span(aText, aLength), aBaseLevel).isErr()) {
2171 return NS_ERROR_FAILURE;
2174 auto result = aBidiEngine.CountRuns();
2175 if (result.isErr()) {
2176 return NS_ERROR_FAILURE;
2178 int32_t runCount = result.unwrap();
2180 nscoord xOffset = 0;
2181 nscoord width, xEndRun = 0;
2182 nscoord totalWidth = 0;
2183 int32_t i, start, limit, length;
2184 uint32_t visualStart = 0;
2185 BidiClass bidiClass;
2186 BidiClass prevClass = BidiClass::LeftToRight;
2188 for (i = 0; i < runCount; i++) {
2189 aBidiEngine.GetVisualRun(i, &start, &length);
2191 BidiEmbeddingLevel level;
2192 aBidiEngine.GetLogicalRun(start, &limit, &level);
2194 BidiDirection dir = level.Direction();
2195 int32_t subRunLength = limit - start;
2196 int32_t lineOffset = start;
2197 int32_t typeLimit = std::min(limit, AssertedCast<int32_t>(aLength));
2198 int32_t subRunCount = 1;
2199 int32_t subRunLimit = typeLimit;
2202 * If |level| is even, i.e. the direction of the run is left-to-right, we
2203 * render the subruns from left to right and increment the x-coordinate
2204 * |xOffset| by the width of each subrun after rendering.
2206 * If |level| is odd, i.e. the direction of the run is right-to-left, we
2207 * render the subruns from right to left. We begin by incrementing |xOffset|
2208 * by the width of the whole run, and then decrement it by the width of each
2209 * subrun before rendering. After rendering all the subruns, we restore the
2210 * x-coordinate of the end of the run for the start of the next run.
2213 if (dir == BidiDirection::RTL) {
2214 aprocessor.SetText(aText + start, subRunLength, BidiDirection::RTL);
2215 width = aprocessor.GetWidth();
2216 xOffset += width;
2217 xEndRun = xOffset;
2220 while (subRunCount > 0) {
2221 // CalculateBidiClass can increment subRunCount if the run
2222 // contains mixed character types
2223 CalculateBidiClass(aText, lineOffset, typeLimit, subRunLimit,
2224 subRunLength, subRunCount, bidiClass, prevClass);
2226 nsAutoString runVisualText(aText + start, subRunLength);
2227 if (aPresContext) {
2228 FormatUnicodeText(aPresContext, runVisualText.BeginWriting(),
2229 subRunLength, bidiClass);
2232 aprocessor.SetText(runVisualText.get(), subRunLength, dir);
2233 width = aprocessor.GetWidth();
2234 totalWidth += width;
2235 if (dir == BidiDirection::RTL) {
2236 xOffset -= width;
2238 if (aMode == MODE_DRAW) {
2239 aprocessor.DrawText(xOffset);
2243 * The caller may request to calculate the visual position of one
2244 * or more characters.
2246 for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
2247 nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
2249 * Did we already resolve this position's visual metric? If so, skip.
2251 if (posResolve->visualLeftTwips != kNotFound) continue;
2254 * First find out if the logical position is within this run.
2256 if (start <= posResolve->logicalIndex &&
2257 start + subRunLength > posResolve->logicalIndex) {
2259 * If this run is only one character long, we have an easy case:
2260 * the visual position is the x-coord of the start of the run
2261 * less the x-coord of the start of the whole text.
2263 if (subRunLength == 1) {
2264 posResolve->visualIndex = visualStart;
2265 posResolve->visualLeftTwips = xOffset;
2266 posResolve->visualWidth = width;
2269 * Otherwise, we need to measure the width of the run's part
2270 * which is to the visual left of the index.
2271 * In other words, the run is broken in two, around the logical index,
2272 * and we measure the part which is visually left.
2273 * If the run is right-to-left, this part will span from after the
2274 * index up to the end of the run; if it is left-to-right, this part
2275 * will span from the start of the run up to (and inclduing) the
2276 * character before the index.
2278 else {
2280 * Here is a description of how the width of the current character
2281 * (posResolve->visualWidth) is calculated:
2283 * LTR (current char: "P"):
2284 * S A M P L E (logical index: 3, visual index: 3)
2285 * ^ (visualLeftPart)
2286 * ^ (visualRightSide)
2287 * visualLeftLength == 3
2288 * ^^^^^^ (subWidth)
2289 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2290 * ^^ (posResolve->visualWidth)
2292 * RTL (current char: "M"):
2293 * E L P M A S (logical index: 2, visual index: 3)
2294 * ^ (visualLeftPart)
2295 * ^ (visualRightSide)
2296 * visualLeftLength == 3
2297 * ^^^^^^ (subWidth)
2298 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
2299 * ^^ (posResolve->visualWidth)
2301 nscoord subWidth;
2302 // The position in the text where this run's "left part" begins.
2303 const char16_t* visualLeftPart;
2304 const char16_t* visualRightSide;
2305 if (dir == BidiDirection::RTL) {
2306 // One day, son, this could all be replaced with
2307 // mPresContext->BidiEngine().GetVisualIndex() ...
2308 posResolve->visualIndex =
2309 visualStart +
2310 (subRunLength - (posResolve->logicalIndex + 1 - start));
2311 // Skipping to the "left part".
2312 visualLeftPart = aText + posResolve->logicalIndex + 1;
2313 // Skipping to the right side of the current character
2314 visualRightSide = visualLeftPart - 1;
2315 } else {
2316 posResolve->visualIndex =
2317 visualStart + (posResolve->logicalIndex - start);
2318 // Skipping to the "left part".
2319 visualLeftPart = aText + start;
2320 // In LTR mode this is the same as visualLeftPart
2321 visualRightSide = visualLeftPart;
2323 // The delta between the start of the run and the left part's end.
2324 int32_t visualLeftLength = posResolve->visualIndex - visualStart;
2325 aprocessor.SetText(visualLeftPart, visualLeftLength, dir);
2326 subWidth = aprocessor.GetWidth();
2327 aprocessor.SetText(visualRightSide, visualLeftLength + 1, dir);
2328 posResolve->visualLeftTwips = xOffset + subWidth;
2329 posResolve->visualWidth = aprocessor.GetWidth() - subWidth;
2334 if (dir == BidiDirection::LTR) {
2335 xOffset += width;
2338 --subRunCount;
2339 start = lineOffset;
2340 subRunLimit = typeLimit;
2341 subRunLength = typeLimit - lineOffset;
2342 } // while
2343 if (dir == BidiDirection::RTL) {
2344 xOffset = xEndRun;
2347 visualStart += length;
2348 } // for
2350 if (aWidth) {
2351 *aWidth = totalWidth;
2353 return NS_OK;
2356 // This is called either for a single character (one code unit, or a surrogate
2357 // pair), or for a run that is known to be purely LTR.
2358 void nsBidiPresUtils::ProcessSimpleRun(const char16_t* aText, size_t aLength,
2359 BidiEmbeddingLevel aBaseLevel,
2360 nsPresContext* aPresContext,
2361 BidiProcessor& aprocessor, Mode aMode,
2362 nsBidiPositionResolve* aPosResolve,
2363 int32_t aPosResolveCount,
2364 nscoord* aWidth) {
2365 // Get bidi class from the first (or only) character.
2366 uint32_t ch = aText[0];
2367 if (aLength > 1 && NS_IS_HIGH_SURROGATE(ch) &&
2368 NS_IS_LOW_SURROGATE(aText[1])) {
2369 ch = SURROGATE_TO_UCS4(aText[0], aText[1]);
2371 BidiClass bidiClass = intl::UnicodeProperties::GetBidiClass(ch);
2373 nsAutoString runVisualText(aText, aLength);
2374 int32_t length = aLength;
2375 if (aPresContext) {
2376 FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), length,
2377 bidiClass);
2380 BidiDirection dir = bidiClass == BidiClass::RightToLeft ||
2381 bidiClass == BidiClass::RightToLeftArabic
2382 ? BidiDirection::RTL
2383 : BidiDirection::LTR;
2384 aprocessor.SetText(runVisualText.get(), length, dir);
2386 if (aMode == MODE_DRAW) {
2387 aprocessor.DrawText(0);
2390 if (!aWidth && !aPosResolve) {
2391 return;
2394 nscoord width = aprocessor.GetWidth();
2396 for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
2397 nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
2398 if (posResolve->visualLeftTwips != kNotFound) {
2399 continue;
2401 if (0 <= posResolve->logicalIndex && length > posResolve->logicalIndex) {
2402 posResolve->visualIndex = 0;
2403 posResolve->visualLeftTwips = 0;
2404 posResolve->visualWidth = width;
2408 if (aWidth) {
2409 *aWidth = width;
2413 class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final
2414 : public nsBidiPresUtils::BidiProcessor {
2415 public:
2416 typedef gfx::DrawTarget DrawTarget;
2418 nsIRenderingContextBidiProcessor(gfxContext* aCtx,
2419 DrawTarget* aTextRunConstructionDrawTarget,
2420 nsFontMetrics* aFontMetrics,
2421 const nsPoint& aPt)
2422 : mCtx(aCtx),
2423 mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget),
2424 mFontMetrics(aFontMetrics),
2425 mPt(aPt),
2426 mText(nullptr),
2427 mLength(0) {}
2429 ~nsIRenderingContextBidiProcessor() { mFontMetrics->SetTextRunRTL(false); }
2431 virtual void SetText(const char16_t* aText, int32_t aLength,
2432 BidiDirection aDirection) override {
2433 mFontMetrics->SetTextRunRTL(aDirection == BidiDirection::RTL);
2434 mText = aText;
2435 mLength = aLength;
2438 virtual nscoord GetWidth() override {
2439 return nsLayoutUtils::AppUnitWidthOfString(mText, mLength, *mFontMetrics,
2440 mTextRunConstructionDrawTarget);
2443 virtual void DrawText(nscoord aIOffset) override {
2444 nsPoint pt(mPt);
2445 if (mFontMetrics->GetVertical()) {
2446 pt.y += aIOffset;
2447 } else {
2448 pt.x += aIOffset;
2450 mFontMetrics->DrawString(mText, mLength, pt.x, pt.y, mCtx,
2451 mTextRunConstructionDrawTarget);
2454 private:
2455 gfxContext* mCtx;
2456 DrawTarget* mTextRunConstructionDrawTarget;
2457 nsFontMetrics* mFontMetrics;
2458 nsPoint mPt;
2459 const char16_t* mText;
2460 int32_t mLength;
2463 nsresult nsBidiPresUtils::ProcessTextForRenderingContext(
2464 const char16_t* aText, int32_t aLength, BidiEmbeddingLevel aBaseLevel,
2465 nsPresContext* aPresContext, gfxContext& aRenderingContext,
2466 DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics& aFontMetrics,
2467 Mode aMode, nscoord aX, nscoord aY, nsBidiPositionResolve* aPosResolve,
2468 int32_t aPosResolveCount, nscoord* aWidth) {
2469 nsIRenderingContextBidiProcessor processor(&aRenderingContext,
2470 aTextRunConstructionDrawTarget,
2471 &aFontMetrics, nsPoint(aX, aY));
2472 nsAutoString text(aText, aLength);
2473 text.ReplaceChar(kSeparators, ' ');
2474 return ProcessText(text.BeginReading(), text.Length(), aBaseLevel,
2475 aPresContext, processor, aMode, aPosResolve,
2476 aPosResolveCount, aWidth, aPresContext->BidiEngine());
2479 /* static */
2480 BidiEmbeddingLevel nsBidiPresUtils::BidiLevelFromStyle(
2481 ComputedStyle* aComputedStyle) {
2482 if (aComputedStyle->StyleTextReset()->mUnicodeBidi ==
2483 StyleUnicodeBidi::Plaintext) {
2484 return BidiEmbeddingLevel::DefaultLTR();
2487 if (aComputedStyle->StyleVisibility()->mDirection == StyleDirection::Rtl) {
2488 return BidiEmbeddingLevel::RTL();
2491 return BidiEmbeddingLevel::LTR();