summaryrefslogtreecommitdiffstats
path: root/layout/base/nsBidiPresUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base/nsBidiPresUtils.cpp')
-rw-r--r--layout/base/nsBidiPresUtils.cpp2499
1 files changed, 2499 insertions, 0 deletions
diff --git a/layout/base/nsBidiPresUtils.cpp b/layout/base/nsBidiPresUtils.cpp
new file mode 100644
index 0000000000..b1215972c2
--- /dev/null
+++ b/layout/base/nsBidiPresUtils.cpp
@@ -0,0 +1,2499 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBidiPresUtils.h"
+
+#include "mozilla/intl/Bidi.h"
+#include "mozilla/Casting.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Text.h"
+
+#include "gfxContext.h"
+#include "nsFontMetrics.h"
+#include "nsGkAtoms.h"
+#include "nsPresContext.h"
+#include "nsBidiUtils.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsContainerFrame.h"
+#include "nsInlineFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "nsPointerHashKeys.h"
+#include "nsFirstLetterFrame.h"
+#include "nsUnicodeProperties.h"
+#include "nsTextFrame.h"
+#include "nsBlockFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsStyleStructInlines.h"
+#include "RubyUtils.h"
+#include "nsRubyFrame.h"
+#include "nsRubyBaseFrame.h"
+#include "nsRubyTextFrame.h"
+#include "nsRubyBaseContainerFrame.h"
+#include "nsRubyTextContainerFrame.h"
+#include <algorithm>
+
+#undef NOISY_BIDI
+#undef REALLY_NOISY_BIDI
+
+using namespace mozilla;
+
+using BidiEngine = intl::Bidi;
+using BidiClass = intl::BidiClass;
+using BidiDirection = intl::BidiDirection;
+using BidiEmbeddingLevel = intl::BidiEmbeddingLevel;
+
+static const char16_t kSpace = 0x0020;
+static const char16_t kZWSP = 0x200B;
+static const char16_t kLineSeparator = 0x2028;
+static const char16_t kObjectSubstitute = 0xFFFC;
+static const char16_t kLRE = 0x202A;
+static const char16_t kRLE = 0x202B;
+static const char16_t kLRO = 0x202D;
+static const char16_t kRLO = 0x202E;
+static const char16_t kPDF = 0x202C;
+static const char16_t kLRI = 0x2066;
+static const char16_t kRLI = 0x2067;
+static const char16_t kFSI = 0x2068;
+static const char16_t kPDI = 0x2069;
+// All characters with Bidi type Segment Separator or Block Separator
+static const char16_t kSeparators[] = {
+ char16_t('\t'), char16_t('\r'), char16_t('\n'), char16_t(0xb),
+ char16_t(0x1c), char16_t(0x1d), char16_t(0x1e), char16_t(0x1f),
+ char16_t(0x85), char16_t(0x2029), char16_t(0)};
+
+#define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1)
+
+// This exists just to be a type; the value doesn't matter.
+enum class BidiControlFrameType { Value };
+
+static bool IsIsolateControl(char16_t aChar) {
+ return aChar == kLRI || aChar == kRLI || aChar == kFSI;
+}
+
+// Given a ComputedStyle, return any bidi control character necessary to
+// implement style properties that override directionality (i.e. if it has
+// unicode-bidi:bidi-override, or text-orientation:upright in vertical
+// writing mode) when applying the bidi algorithm.
+//
+// Returns 0 if no override control character is implied by this style.
+static char16_t GetBidiOverride(ComputedStyle* aComputedStyle) {
+ const nsStyleVisibility* vis = aComputedStyle->StyleVisibility();
+ if ((vis->mWritingMode == StyleWritingModeProperty::VerticalRl ||
+ vis->mWritingMode == StyleWritingModeProperty::VerticalLr) &&
+ vis->mTextOrientation == StyleTextOrientation::Upright) {
+ return kLRO;
+ }
+ const nsStyleTextReset* text = aComputedStyle->StyleTextReset();
+ if (text->mUnicodeBidi == StyleUnicodeBidi::BidiOverride ||
+ text->mUnicodeBidi == StyleUnicodeBidi::IsolateOverride) {
+ return StyleDirection::Rtl == vis->mDirection ? kRLO : kLRO;
+ }
+ return 0;
+}
+
+// Given a ComputedStyle, return any bidi control character necessary to
+// implement style properties that affect bidi resolution (i.e. if it
+// has unicode-bidiembed, isolate, or plaintext) when applying the bidi
+// algorithm.
+//
+// Returns 0 if no control character is implied by the style.
+//
+// Note that GetBidiOverride and GetBidiControl need to be separate
+// because in the case of unicode-bidi:isolate-override we need both
+// FSI and LRO/RLO.
+static char16_t GetBidiControl(ComputedStyle* aComputedStyle) {
+ const nsStyleVisibility* vis = aComputedStyle->StyleVisibility();
+ const nsStyleTextReset* text = aComputedStyle->StyleTextReset();
+ switch (text->mUnicodeBidi) {
+ case StyleUnicodeBidi::Embed:
+ return StyleDirection::Rtl == vis->mDirection ? kRLE : kLRE;
+ case StyleUnicodeBidi::Isolate:
+ // <bdi> element already has its directionality set from content so
+ // we never need to return kFSI.
+ return StyleDirection::Rtl == vis->mDirection ? kRLI : kLRI;
+ case StyleUnicodeBidi::IsolateOverride:
+ case StyleUnicodeBidi::Plaintext:
+ return kFSI;
+ case StyleUnicodeBidi::Normal:
+ case StyleUnicodeBidi::BidiOverride:
+ break;
+ }
+
+ return 0;
+}
+
+#ifdef DEBUG
+static inline bool AreContinuationsInOrder(nsIFrame* aFrame1,
+ nsIFrame* aFrame2) {
+ nsIFrame* f = aFrame1;
+ do {
+ f = f->GetNextContinuation();
+ } while (f && f != aFrame2);
+ return !!f;
+}
+#endif
+
+struct MOZ_STACK_CLASS BidiParagraphData {
+ struct FrameInfo {
+ FrameInfo(nsIFrame* aFrame, nsBlockInFlowLineIterator& aLineIter)
+ : mFrame(aFrame),
+ mBlockContainer(aLineIter.GetContainer()),
+ mInOverflow(aLineIter.GetInOverflow()) {}
+
+ explicit FrameInfo(BidiControlFrameType aValue)
+ : mFrame(NS_BIDI_CONTROL_FRAME),
+ mBlockContainer(nullptr),
+ mInOverflow(false) {}
+
+ FrameInfo()
+ : mFrame(nullptr), mBlockContainer(nullptr), mInOverflow(false) {}
+
+ nsIFrame* mFrame;
+
+ // The block containing mFrame (i.e., which continuation).
+ nsBlockFrame* mBlockContainer;
+
+ // true if mFrame is in mBlockContainer's overflow lines, false if
+ // in primary lines
+ bool mInOverflow;
+ };
+
+ nsAutoString mBuffer;
+ AutoTArray<char16_t, 16> mEmbeddingStack;
+ AutoTArray<FrameInfo, 16> mLogicalFrames;
+ nsTHashMap<nsPtrHashKey<const nsIContent>, int32_t> mContentToFrameIndex;
+ // Cached presentation context for the frames we're processing.
+ nsPresContext* mPresContext;
+ bool mIsVisual;
+ bool mRequiresBidi;
+ BidiEmbeddingLevel mParaLevel;
+ nsIContent* mPrevContent;
+
+ /**
+ * This class is designed to manage the process of mapping a frame to
+ * the line that it's in, when we know that (a) the frames we ask it
+ * about are always in the block's lines and (b) each successive frame
+ * we ask it about is the same as or after (in depth-first search
+ * order) the previous.
+ *
+ * Since we move through the lines at a different pace in Traverse and
+ * ResolveParagraph, we use one of these for each.
+ *
+ * The state of the mapping is also different between TraverseFrames
+ * and ResolveParagraph since since resolving can call functions
+ * (EnsureBidiContinuation or SplitInlineAncestors) that can create
+ * new frames and thus break lines.
+ *
+ * The TraverseFrames iterator is only used in some edge cases.
+ */
+ struct FastLineIterator {
+ FastLineIterator() : mPrevFrame(nullptr), mNextLineStart(nullptr) {}
+
+ // These iterators *and* mPrevFrame track the line list that we're
+ // iterating over.
+ //
+ // mPrevFrame, if non-null, should be either the frame we're currently
+ // handling (in ResolveParagraph or TraverseFrames, depending on the
+ // iterator) or a frame before it, and is also guaranteed to either be in
+ // mCurrentLine or have been in mCurrentLine until recently.
+ //
+ // In case the splitting causes block frames to break lines, however, we
+ // also track the first frame of the next line. If that changes, it means
+ // we've broken lines and we have to invalidate mPrevFrame.
+ nsBlockInFlowLineIterator mLineIterator;
+ nsIFrame* mPrevFrame;
+ nsIFrame* mNextLineStart;
+
+ nsLineList::iterator GetLine() { return mLineIterator.GetLine(); }
+
+ static bool IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter,
+ nsIFrame* aPrevFrame, nsIFrame* aFrame) {
+ MOZ_ASSERT(!aPrevFrame || aLineIter->GetLine()->Contains(aPrevFrame),
+ "aPrevFrame must be in aLineIter's current line");
+ nsIFrame* endFrame = aLineIter->IsLastLineInList()
+ ? nullptr
+ : aLineIter->GetLine().next()->mFirstChild;
+ nsIFrame* startFrame =
+ aPrevFrame ? aPrevFrame : aLineIter->GetLine()->mFirstChild;
+ for (nsIFrame* frame = startFrame; frame && frame != endFrame;
+ frame = frame->GetNextSibling()) {
+ if (frame == aFrame) return true;
+ }
+ return false;
+ }
+
+ static nsIFrame* FirstChildOfNextLine(
+ nsBlockInFlowLineIterator& aIterator) {
+ const nsLineList::iterator line = aIterator.GetLine();
+ const nsLineList::iterator lineEnd = aIterator.End();
+ MOZ_ASSERT(line != lineEnd, "iterator should start off valid");
+ const nsLineList::iterator nextLine = line.next();
+
+ return nextLine != lineEnd ? nextLine->mFirstChild : nullptr;
+ }
+
+ // Advance line iterator to the line containing aFrame, assuming
+ // that aFrame is already in the line list our iterator is iterating
+ // over.
+ void AdvanceToFrame(nsIFrame* aFrame) {
+ if (mPrevFrame && FirstChildOfNextLine(mLineIterator) != mNextLineStart) {
+ // Something has caused a line to split. We need to invalidate
+ // mPrevFrame since it may now be in a *later* line, though it may
+ // still be in this line, so we need to start searching for it from
+ // the start of this line.
+ mPrevFrame = nullptr;
+ }
+ nsIFrame* child = aFrame;
+ nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
+ while (parent && !parent->IsBlockFrameOrSubclass()) {
+ child = parent;
+ parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
+ }
+ MOZ_ASSERT(parent, "aFrame is not a descendent of a block frame");
+ while (!IsFrameInCurrentLine(&mLineIterator, mPrevFrame, child)) {
+#ifdef DEBUG
+ bool hasNext =
+#endif
+ mLineIterator.Next();
+ MOZ_ASSERT(hasNext, "Can't find frame in lines!");
+ mPrevFrame = nullptr;
+ }
+ mPrevFrame = child;
+ mNextLineStart = FirstChildOfNextLine(mLineIterator);
+ }
+
+ // Advance line iterator to the line containing aFrame, which may
+ // require moving forward into overflow lines or into a later
+ // continuation (or both).
+ void AdvanceToLinesAndFrame(const FrameInfo& aFrameInfo) {
+ if (mLineIterator.GetContainer() != aFrameInfo.mBlockContainer ||
+ mLineIterator.GetInOverflow() != aFrameInfo.mInOverflow) {
+ MOZ_ASSERT(
+ mLineIterator.GetContainer() == aFrameInfo.mBlockContainer
+ ? (!mLineIterator.GetInOverflow() && aFrameInfo.mInOverflow)
+ : (!mLineIterator.GetContainer() ||
+ AreContinuationsInOrder(mLineIterator.GetContainer(),
+ aFrameInfo.mBlockContainer)),
+ "must move forwards");
+ nsBlockFrame* block = aFrameInfo.mBlockContainer;
+ nsLineList::iterator lines =
+ aFrameInfo.mInOverflow ? block->GetOverflowLines()->mLines.begin()
+ : block->LinesBegin();
+ mLineIterator =
+ nsBlockInFlowLineIterator(block, lines, aFrameInfo.mInOverflow);
+ mPrevFrame = nullptr;
+ }
+ AdvanceToFrame(aFrameInfo.mFrame);
+ }
+ };
+
+ FastLineIterator mCurrentTraverseLine, mCurrentResolveLine;
+
+#ifdef DEBUG
+ // Only used for NOISY debug output.
+ // Matches the current TraverseFrames state, not the ResolveParagraph
+ // state.
+ nsBlockFrame* mCurrentBlock;
+#endif
+
+ explicit BidiParagraphData(nsBlockFrame* aBlockFrame)
+ : mPresContext(aBlockFrame->PresContext()),
+ mIsVisual(mPresContext->IsVisualMode()),
+ mRequiresBidi(false),
+ mParaLevel(nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame->Style())),
+ mPrevContent(nullptr)
+#ifdef DEBUG
+ ,
+ mCurrentBlock(aBlockFrame)
+#endif
+ {
+ if (mParaLevel > 0) {
+ mRequiresBidi = true;
+ }
+
+ if (mIsVisual) {
+ /**
+ * Drill up in content to detect whether this is an element that needs to
+ * be rendered with logical order even on visual pages.
+ *
+ * We always use logical order on form controls, firstly so that text
+ * entry will be in logical order, but also because visual pages were
+ * written with the assumption that even if the browser had no support
+ * for right-to-left text rendering, it would use native widgets with
+ * bidi support to display form controls.
+ *
+ * We also use logical order in XUL elements, since we expect that if a
+ * XUL element appears in a visual page, it will be generated by an XBL
+ * binding and contain localized text which will be in logical order.
+ */
+ for (nsIContent* content = aBlockFrame->GetContent(); content;
+ content = content->GetParent()) {
+ if (content->IsXULElement() || content->IsHTMLFormControlElement()) {
+ mIsVisual = false;
+ break;
+ }
+ }
+ }
+ }
+
+ nsresult SetPara() {
+ if (mPresContext->BidiEngine().SetParagraph(mBuffer, mParaLevel).isErr()) {
+ return NS_ERROR_FAILURE;
+ };
+ return NS_OK;
+ }
+
+ /**
+ * mParaLevel can be BidiDirection::LTR as well as
+ * BidiDirection::LTR or BidiDirection::RTL.
+ * GetParagraphEmbeddingLevel() returns the actual (resolved) paragraph level
+ * which is always either BidiDirection::LTR or
+ * BidiDirection::RTL
+ */
+ BidiEmbeddingLevel GetParagraphEmbeddingLevel() {
+ BidiEmbeddingLevel paraLevel = mParaLevel;
+ if (paraLevel == BidiEmbeddingLevel::DefaultLTR() ||
+ paraLevel == BidiEmbeddingLevel::DefaultRTL()) {
+ paraLevel = mPresContext->BidiEngine().GetParagraphEmbeddingLevel();
+ }
+ return paraLevel;
+ }
+
+ BidiEngine::ParagraphDirection GetParagraphDirection() {
+ return mPresContext->BidiEngine().GetParagraphDirection();
+ }
+
+ nsresult CountRuns(int32_t* runCount) {
+ auto result = mPresContext->BidiEngine().CountRuns();
+ if (result.isErr()) {
+ return NS_ERROR_FAILURE;
+ }
+ *runCount = result.unwrap();
+ return NS_OK;
+ }
+
+ void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit,
+ BidiEmbeddingLevel* aLevel) {
+ mPresContext->BidiEngine().GetLogicalRun(aLogicalStart, aLogicalLimit,
+ aLevel);
+ if (mIsVisual) {
+ *aLevel = GetParagraphEmbeddingLevel();
+ }
+ }
+
+ void ResetData() {
+ mLogicalFrames.Clear();
+ mContentToFrameIndex.Clear();
+ mBuffer.SetLength(0);
+ mPrevContent = nullptr;
+ for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) {
+ mBuffer.Append(mEmbeddingStack[i]);
+ mLogicalFrames.AppendElement(FrameInfo(BidiControlFrameType::Value));
+ }
+ }
+
+ void AppendFrame(nsIFrame* aFrame, FastLineIterator& aLineIter,
+ nsIContent* aContent = nullptr) {
+ if (aContent) {
+ mContentToFrameIndex.InsertOrUpdate(aContent, FrameCount());
+ }
+
+ // We don't actually need to advance aLineIter to aFrame, since all we use
+ // from it is the block and is-overflow state, which are correct already.
+ mLogicalFrames.AppendElement(FrameInfo(aFrame, aLineIter.mLineIterator));
+ }
+
+ void AdvanceAndAppendFrame(nsIFrame** aFrame, FastLineIterator& aLineIter,
+ nsIFrame** aNextSibling) {
+ nsIFrame* frame = *aFrame;
+ nsIFrame* nextSibling = *aNextSibling;
+
+ frame = frame->GetNextContinuation();
+ if (frame) {
+ AppendFrame(frame, aLineIter, nullptr);
+
+ /*
+ * If we have already overshot the saved next-sibling while
+ * scanning the frame's continuations, advance it.
+ */
+ if (frame == nextSibling) {
+ nextSibling = frame->GetNextSibling();
+ }
+ }
+
+ *aFrame = frame;
+ *aNextSibling = nextSibling;
+ }
+
+ int32_t GetLastFrameForContent(nsIContent* aContent) {
+ return mContentToFrameIndex.Get(aContent);
+ }
+
+ int32_t FrameCount() { return mLogicalFrames.Length(); }
+
+ int32_t BufferLength() { return mBuffer.Length(); }
+
+ nsIFrame* FrameAt(int32_t aIndex) { return mLogicalFrames[aIndex].mFrame; }
+
+ const FrameInfo& FrameInfoAt(int32_t aIndex) {
+ return mLogicalFrames[aIndex];
+ }
+
+ void AppendUnichar(char16_t aCh) { mBuffer.Append(aCh); }
+
+ void AppendString(const nsDependentSubstring& aString) {
+ mBuffer.Append(aString);
+ }
+
+ void AppendControlChar(char16_t aCh) {
+ mLogicalFrames.AppendElement(FrameInfo(BidiControlFrameType::Value));
+ AppendUnichar(aCh);
+ }
+
+ void PushBidiControl(char16_t aCh) {
+ AppendControlChar(aCh);
+ mEmbeddingStack.AppendElement(aCh);
+ }
+
+ void AppendPopChar(char16_t aCh) {
+ AppendControlChar(IsIsolateControl(aCh) ? kPDI : kPDF);
+ }
+
+ void PopBidiControl(char16_t aCh) {
+ MOZ_ASSERT(mEmbeddingStack.Length(), "embedding/override underflow");
+ MOZ_ASSERT(aCh == mEmbeddingStack.LastElement());
+ AppendPopChar(aCh);
+ mEmbeddingStack.RemoveLastElement();
+ }
+
+ void ClearBidiControls() {
+ for (char16_t c : Reversed(mEmbeddingStack)) {
+ AppendPopChar(c);
+ }
+ }
+};
+
+struct MOZ_STACK_CLASS BidiLineData {
+ AutoTArray<nsIFrame*, 16> mLogicalFrames;
+ AutoTArray<nsIFrame*, 16> mVisualFrames;
+ AutoTArray<int32_t, 16> mIndexMap;
+ AutoTArray<BidiEmbeddingLevel, 16> mLevels;
+ bool mIsReordered;
+
+ BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) {
+ /**
+ * Initialize the logically-ordered array of frames using the top-level
+ * frames of a single line
+ */
+ bool isReordered = false;
+ bool hasRTLFrames = false;
+ bool hasVirtualControls = false;
+
+ auto appendFrame = [&](nsIFrame* frame, BidiEmbeddingLevel level) {
+ mLogicalFrames.AppendElement(frame);
+ mLevels.AppendElement(level);
+ mIndexMap.AppendElement(0);
+ if (level.IsRTL()) {
+ hasRTLFrames = true;
+ }
+ };
+
+ bool firstFrame = true;
+ for (nsIFrame* frame = aFirstFrameOnLine; frame && aNumFramesOnLine--;
+ frame = frame->GetNextSibling()) {
+ FrameBidiData bidiData = nsBidiPresUtils::GetFrameBidiData(frame);
+ // Ignore virtual control before the first frame. Doing so should
+ // not affect the visual result, but could avoid running into the
+ // stripping code below for many cases.
+ if (!firstFrame && bidiData.precedingControl != kBidiLevelNone) {
+ appendFrame(NS_BIDI_CONTROL_FRAME, bidiData.precedingControl);
+ hasVirtualControls = true;
+ }
+ appendFrame(frame, bidiData.embeddingLevel);
+ firstFrame = false;
+ }
+
+ // Reorder the line
+ BidiEngine::ReorderVisual(mLevels.Elements(), FrameCount(),
+ mIndexMap.Elements());
+
+ // Strip virtual frames
+ if (hasVirtualControls) {
+ auto originalCount = mLogicalFrames.Length();
+ AutoTArray<int32_t, 16> realFrameMap;
+ realFrameMap.SetCapacity(originalCount);
+ size_t count = 0;
+ for (auto i : IntegerRange(originalCount)) {
+ if (mLogicalFrames[i] == NS_BIDI_CONTROL_FRAME) {
+ realFrameMap.AppendElement(-1);
+ } else {
+ mLogicalFrames[count] = mLogicalFrames[i];
+ mLevels[count] = mLevels[i];
+ realFrameMap.AppendElement(count);
+ count++;
+ }
+ }
+ // Only keep index map for real frames.
+ for (size_t i = 0, j = 0; i < originalCount; ++i) {
+ auto newIndex = realFrameMap[mIndexMap[i]];
+ if (newIndex != -1) {
+ mIndexMap[j] = newIndex;
+ j++;
+ }
+ }
+ mLogicalFrames.TruncateLength(count);
+ mLevels.TruncateLength(count);
+ mIndexMap.TruncateLength(count);
+ }
+
+ for (int32_t i = 0; i < FrameCount(); i++) {
+ mVisualFrames.AppendElement(LogicalFrameAt(mIndexMap[i]));
+ if (i != mIndexMap[i]) {
+ isReordered = true;
+ }
+ }
+
+ // If there's an RTL frame, assume the line is reordered
+ mIsReordered = isReordered || hasRTLFrames;
+ }
+
+ int32_t FrameCount() const { return mLogicalFrames.Length(); }
+
+ nsIFrame* LogicalFrameAt(int32_t aIndex) const {
+ return mLogicalFrames[aIndex];
+ }
+
+ nsIFrame* VisualFrameAt(int32_t aIndex) const {
+ return mVisualFrames[aIndex];
+ }
+};
+
+#ifdef DEBUG
+extern "C" {
+void MOZ_EXPORT DumpFrameArray(const nsTArray<nsIFrame*>& aFrames) {
+ for (nsIFrame* frame : aFrames) {
+ if (frame == NS_BIDI_CONTROL_FRAME) {
+ fprintf_stderr(stderr, "(Bidi control frame)\n");
+ } else {
+ frame->List();
+ }
+ }
+}
+
+void MOZ_EXPORT DumpBidiLine(BidiLineData* aData, bool aVisualOrder) {
+ DumpFrameArray(aVisualOrder ? aData->mVisualFrames : aData->mLogicalFrames);
+}
+}
+#endif
+
+/* Some helper methods for Resolve() */
+
+// Should this frame be split between text runs?
+static bool IsBidiSplittable(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ // Bidi inline containers should be split, unless they're line frames.
+ LayoutFrameType frameType = aFrame->Type();
+ return (aFrame->IsBidiInlineContainer() &&
+ frameType != LayoutFrameType::Line) ||
+ frameType == LayoutFrameType::Text;
+}
+
+// Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
+static bool IsBidiLeaf(const nsIFrame* aFrame) {
+ nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
+ if (kid) {
+ if (aFrame->IsBidiInlineContainer() ||
+ RubyUtils::IsRubyBox(aFrame->Type())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Create non-fluid continuations for the ancestors of a given frame all the way
+ * up the frame tree until we hit a non-splittable frame (a line or a block).
+ *
+ * @param aParent the first parent frame to be split
+ * @param aFrame the child frames after this frame are reparented to the
+ * newly-created continuation of aParent.
+ * If aFrame is null, all the children of aParent are reparented.
+ */
+static void SplitInlineAncestors(nsContainerFrame* aParent,
+ nsLineList::iterator aLine, nsIFrame* aFrame) {
+ PresShell* presShell = aParent->PresShell();
+ nsIFrame* frame = aFrame;
+ nsContainerFrame* parent = aParent;
+ nsContainerFrame* newParent;
+
+ while (IsBidiSplittable(parent)) {
+ nsContainerFrame* grandparent = parent->GetParent();
+ NS_ASSERTION(grandparent,
+ "Couldn't get parent's parent in "
+ "nsBidiPresUtils::SplitInlineAncestors");
+
+ // Split the child list after |frame|, unless it is the last child.
+ if (!frame || frame->GetNextSibling()) {
+ newParent = static_cast<nsContainerFrame*>(
+ presShell->FrameConstructor()->CreateContinuingFrame(
+ parent, grandparent, false));
+
+ nsFrameList tail = parent->StealFramesAfter(frame);
+
+ // Reparent views as necessary
+ nsContainerFrame::ReparentFrameViewList(tail, parent, newParent);
+
+ // The parent's continuation adopts the siblings after the split.
+ MOZ_ASSERT(!newParent->IsBlockFrameOrSubclass(),
+ "blocks should not be IsBidiSplittable");
+ newParent->InsertFrames(FrameChildListID::NoReflowPrincipal, nullptr,
+ nullptr, std::move(tail));
+
+ // While passing &aLine to InsertFrames for a non-block isn't harmful
+ // because it's a no-op, it doesn't really make sense. However, the
+ // MOZ_ASSERT() we need to guarantee that it's safe only works if the
+ // parent is actually the block.
+ const nsLineList::iterator* parentLine;
+ if (grandparent->IsBlockFrameOrSubclass()) {
+ MOZ_ASSERT(aLine->Contains(parent));
+ parentLine = &aLine;
+ } else {
+ parentLine = nullptr;
+ }
+
+ // The list name FrameChildListID::NoReflowPrincipal would indicate we
+ // don't want reflow
+ grandparent->InsertFrames(FrameChildListID::NoReflowPrincipal, parent,
+ parentLine, nsFrameList(newParent, newParent));
+ }
+
+ frame = parent;
+ parent = grandparent;
+ }
+}
+
+static void MakeContinuationFluid(nsIFrame* aFrame, nsIFrame* aNext) {
+ NS_ASSERTION(!aFrame->GetNextInFlow() || aFrame->GetNextInFlow() == aNext,
+ "next-in-flow is not next continuation!");
+ aFrame->SetNextInFlow(aNext);
+
+ NS_ASSERTION(!aNext->GetPrevInFlow() || aNext->GetPrevInFlow() == aFrame,
+ "prev-in-flow is not prev continuation!");
+ aNext->SetPrevInFlow(aFrame);
+}
+
+static void MakeContinuationsNonFluidUpParentChain(nsIFrame* aFrame,
+ nsIFrame* aNext) {
+ nsIFrame* frame;
+ nsIFrame* next;
+
+ for (frame = aFrame, next = aNext;
+ frame && next && next != frame && next == frame->GetNextInFlow() &&
+ IsBidiSplittable(frame);
+ frame = frame->GetParent(), next = next->GetParent()) {
+ frame->SetNextContinuation(next);
+ next->SetPrevContinuation(frame);
+ }
+}
+
+// If aFrame is the last child of its parent, convert bidi continuations to
+// fluid continuations for all of its inline ancestors.
+// If it isn't the last child, make sure that its continuation is fluid.
+static void JoinInlineAncestors(nsIFrame* aFrame) {
+ nsIFrame* frame = aFrame;
+ while (frame && IsBidiSplittable(frame)) {
+ nsIFrame* next = frame->GetNextContinuation();
+ if (next) {
+ MakeContinuationFluid(frame, next);
+ }
+ // Join the parent only as long as we're its last child.
+ if (frame->GetNextSibling()) break;
+ frame = frame->GetParent();
+ }
+}
+
+static void CreateContinuation(nsIFrame* aFrame,
+ const nsLineList::iterator aLine,
+ nsIFrame** aNewFrame, bool aIsFluid) {
+ MOZ_ASSERT(aNewFrame, "null OUT ptr");
+ MOZ_ASSERT(aFrame, "null ptr");
+
+ *aNewFrame = nullptr;
+
+ nsPresContext* presContext = aFrame->PresContext();
+ PresShell* presShell = presContext->PresShell();
+ NS_ASSERTION(presShell,
+ "PresShell must be set on PresContext before calling "
+ "nsBidiPresUtils::CreateContinuation");
+
+ nsContainerFrame* parent = aFrame->GetParent();
+ NS_ASSERTION(
+ parent,
+ "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
+
+ // While passing &aLine to InsertFrames for a non-block isn't harmful
+ // because it's a no-op, it doesn't really make sense. However, the
+ // MOZ_ASSERT() we need to guarantee that it's safe only works if the
+ // parent is actually the block.
+ const nsLineList::iterator* parentLine;
+ if (parent->IsBlockFrameOrSubclass()) {
+ MOZ_ASSERT(aLine->Contains(aFrame));
+ parentLine = &aLine;
+ } else {
+ parentLine = nullptr;
+ }
+
+ // Have to special case floating first letter frames because the continuation
+ // doesn't go in the first letter frame. The continuation goes with the rest
+ // of the text that the first letter frame was made out of.
+ if (parent->IsLetterFrame() && parent->IsFloating()) {
+ nsFirstLetterFrame* letterFrame = do_QueryFrame(parent);
+ letterFrame->CreateContinuationForFloatingParent(aFrame, aNewFrame,
+ aIsFluid);
+ return;
+ }
+
+ *aNewFrame = presShell->FrameConstructor()->CreateContinuingFrame(
+ aFrame, parent, aIsFluid);
+
+ // The list name FrameChildListID::NoReflowPrincipal would indicate we don't
+ // want reflow
+ // XXXbz this needs higher-level framelist love
+ parent->InsertFrames(FrameChildListID::NoReflowPrincipal, aFrame, parentLine,
+ nsFrameList(*aNewFrame, *aNewFrame));
+
+ if (!aIsFluid) {
+ // Split inline ancestor frames
+ SplitInlineAncestors(parent, aLine, aFrame);
+ }
+}
+
+/*
+ * Overview of the implementation of Resolve():
+ *
+ * Walk through the descendants of aBlockFrame and build:
+ * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
+ * * mBuffer: an nsString containing a representation of
+ * the content of the frames.
+ * In the case of text frames, this is the actual text context of the
+ * frames, but some other elements are represented in a symbolic form which
+ * will make the Unicode Bidi Algorithm give the correct results.
+ * Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo>
+ * elements are represented by the corresponding Unicode control characters.
+ * <br> elements are represented by U+2028 LINE SEPARATOR
+ * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
+ * CHARACTER
+ *
+ * Then pass mBuffer to the Bidi engine for resolving of embedding levels
+ * by nsBidi::SetPara() and division into directional runs by
+ * nsBidi::CountRuns().
+ *
+ * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
+ * correlate them with the frames indexed in mLogicalFrames, setting the
+ * baseLevel and embeddingLevel properties according to the results returned
+ * by the Bidi engine.
+ *
+ * The rendering layer requires each text frame to contain text in only one
+ * direction, so we may need to call EnsureBidiContinuation() to split frames.
+ * We may also need to call RemoveBidiContinuation() to convert frames created
+ * by EnsureBidiContinuation() in previous reflows into fluid continuations.
+ */
+nsresult nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame) {
+ BidiParagraphData bpd(aBlockFrame);
+
+ // Handle bidi-override being set on the block itself before calling
+ // TraverseFrames.
+ // No need to call GetBidiControl as well, because isolate and embed
+ // values of unicode-bidi property are redundant on block elements.
+ // unicode-bidi:plaintext on a block element is handled by block frame
+ // via using nsIFrame::GetWritingMode(nsIFrame*).
+ char16_t ch = GetBidiOverride(aBlockFrame->Style());
+ if (ch != 0) {
+ bpd.PushBidiControl(ch);
+ bpd.mRequiresBidi = true;
+ } else {
+ // If there are no unicode-bidi properties and no RTL characters in the
+ // block's content, then it is pure LTR and we can skip the rest of bidi
+ // resolution.
+ nsIContent* currContent = nullptr;
+ for (nsBlockFrame* block = aBlockFrame; block;
+ block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
+ block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
+ if (!bpd.mRequiresBidi &&
+ ChildListMayRequireBidi(block->PrincipalChildList().FirstChild(),
+ &currContent)) {
+ bpd.mRequiresBidi = true;
+ }
+ if (!bpd.mRequiresBidi) {
+ nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
+ if (overflowLines) {
+ if (ChildListMayRequireBidi(overflowLines->mFrames.FirstChild(),
+ &currContent)) {
+ bpd.mRequiresBidi = true;
+ }
+ }
+ }
+ }
+ if (!bpd.mRequiresBidi) {
+ return NS_OK;
+ }
+ }
+
+ for (nsBlockFrame* block = aBlockFrame; block;
+ block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
+#ifdef DEBUG
+ bpd.mCurrentBlock = block;
+#endif
+ block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
+ bpd.mCurrentTraverseLine.mLineIterator =
+ nsBlockInFlowLineIterator(block, block->LinesBegin());
+ bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
+ TraverseFrames(block->PrincipalChildList().FirstChild(), &bpd);
+ nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
+ if (overflowLines) {
+ bpd.mCurrentTraverseLine.mLineIterator =
+ nsBlockInFlowLineIterator(block, overflowLines->mLines.begin(), true);
+ bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
+ TraverseFrames(overflowLines->mFrames.FirstChild(), &bpd);
+ }
+ }
+
+ if (ch != 0) {
+ bpd.PopBidiControl(ch);
+ }
+
+ return ResolveParagraph(&bpd);
+}
+
+nsresult nsBidiPresUtils::ResolveParagraph(BidiParagraphData* aBpd) {
+ if (aBpd->BufferLength() < 1) {
+ return NS_OK;
+ }
+ aBpd->mBuffer.ReplaceChar(kSeparators, kSpace);
+
+ int32_t runCount;
+
+ nsresult rv = aBpd->SetPara();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ BidiEmbeddingLevel embeddingLevel = aBpd->GetParagraphEmbeddingLevel();
+
+ rv = aBpd->CountRuns(&runCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t runLength = 0; // the length of the current run of text
+ int32_t logicalLimit = 0; // the end of the current run + 1
+ int32_t numRun = -1;
+ int32_t fragmentLength = 0; // the length of the current text frame
+ int32_t frameIndex = -1; // index to the frames in mLogicalFrames
+ int32_t frameCount = aBpd->FrameCount();
+ int32_t contentOffset = 0; // offset of current frame in its content node
+ bool isTextFrame = false;
+ nsIFrame* frame = nullptr;
+ BidiParagraphData::FrameInfo frameInfo;
+ nsIContent* content = nullptr;
+ int32_t contentTextLength = 0;
+
+#ifdef DEBUG
+# ifdef NOISY_BIDI
+ printf(
+ "Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, "
+ "runCount=%d\n",
+ (void*)aBpd->mCurrentBlock, NS_ConvertUTF16toUTF8(aBpd->mBuffer).get(),
+ frameCount, runCount);
+# ifdef REALLY_NOISY_BIDI
+ printf(" block frame tree=:\n");
+ aBpd->mCurrentBlock->List(stdout);
+# endif
+# endif
+#endif
+
+ if (runCount == 1 && frameCount == 1 &&
+ aBpd->GetParagraphDirection() == BidiEngine::ParagraphDirection::LTR &&
+ aBpd->GetParagraphEmbeddingLevel() == 0) {
+ // We have a single left-to-right frame in a left-to-right paragraph,
+ // without bidi isolation from the surrounding text.
+ // Make sure that the embedding level and base level frame properties aren't
+ // set (because if they are this frame used to have some other direction,
+ // so we can't do this optimization), and we're done.
+ nsIFrame* frame = aBpd->FrameAt(0);
+ if (frame != NS_BIDI_CONTROL_FRAME) {
+ FrameBidiData bidiData = frame->GetBidiData();
+ if (!bidiData.embeddingLevel && !bidiData.baseLevel) {
+#ifdef DEBUG
+# ifdef NOISY_BIDI
+ printf("early return for single direction frame %p\n", (void*)frame);
+# endif
+#endif
+ frame->AddStateBits(NS_FRAME_IS_BIDI);
+ return NS_OK;
+ }
+ }
+ }
+
+ BidiParagraphData::FrameInfo lastRealFrame;
+ BidiEmbeddingLevel lastEmbeddingLevel = kBidiLevelNone;
+ BidiEmbeddingLevel precedingControl = kBidiLevelNone;
+
+ auto storeBidiDataToFrame = [&]() {
+ FrameBidiData bidiData;
+ bidiData.embeddingLevel = embeddingLevel;
+ bidiData.baseLevel = aBpd->GetParagraphEmbeddingLevel();
+ // If a control character doesn't have a lower embedding level than
+ // both the preceding and the following frame, it isn't something
+ // needed for getting the correct result. This optimization should
+ // remove almost all of embeds and overrides, and some of isolates.
+ if (precedingControl >= embeddingLevel ||
+ precedingControl >= lastEmbeddingLevel) {
+ bidiData.precedingControl = kBidiLevelNone;
+ } else {
+ bidiData.precedingControl = precedingControl;
+ }
+ precedingControl = kBidiLevelNone;
+ lastEmbeddingLevel = embeddingLevel;
+ frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData);
+ };
+
+ for (;;) {
+ if (fragmentLength <= 0) {
+ // Get the next frame from mLogicalFrames
+ if (++frameIndex >= frameCount) {
+ break;
+ }
+ frameInfo = aBpd->FrameInfoAt(frameIndex);
+ frame = frameInfo.mFrame;
+ if (frame == NS_BIDI_CONTROL_FRAME || !frame->IsTextFrame()) {
+ /*
+ * Any non-text frame corresponds to a single character in the text
+ * buffer (a bidi control character, LINE SEPARATOR, or OBJECT
+ * SUBSTITUTE)
+ */
+ isTextFrame = false;
+ fragmentLength = 1;
+ } else {
+ aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(frameInfo);
+ content = frame->GetContent();
+ if (!content) {
+ rv = NS_OK;
+ break;
+ }
+ contentTextLength = content->TextLength();
+ auto [start, end] = frame->GetOffsets();
+ NS_ASSERTION(!(contentTextLength < end - start),
+ "Frame offsets don't fit in content");
+ fragmentLength = std::min(contentTextLength, end - start);
+ contentOffset = start;
+ isTextFrame = true;
+ }
+ } // if (fragmentLength <= 0)
+
+ if (runLength <= 0) {
+ // Get the next run of text from the Bidi engine
+ if (++numRun >= runCount) {
+ // We've run out of runs of text; but don't forget to store bidi data
+ // to the frame before breaking out of the loop (bug 1426042).
+ if (frame != NS_BIDI_CONTROL_FRAME) {
+ storeBidiDataToFrame();
+ if (isTextFrame) {
+ frame->AdjustOffsetsForBidi(contentOffset,
+ contentOffset + fragmentLength);
+ }
+ }
+ break;
+ }
+ int32_t lineOffset = logicalLimit;
+ aBpd->GetLogicalRun(lineOffset, &logicalLimit, &embeddingLevel);
+ runLength = logicalLimit - lineOffset;
+ } // if (runLength <= 0)
+
+ if (frame == NS_BIDI_CONTROL_FRAME) {
+ // In theory, we only need to do this for isolates. However, it is
+ // easier to do this for all here because we do not maintain the
+ // index to get corresponding character from buffer. Since we do
+ // have proper embedding level for all those characters, including
+ // them wouldn't affect the final result.
+ precedingControl = std::min(precedingControl, embeddingLevel);
+ } else {
+ storeBidiDataToFrame();
+ if (isTextFrame) {
+ if (contentTextLength == 0) {
+ // Set the base level and embedding level of the current run even
+ // on an empty frame. Otherwise frame reordering will not be correct.
+ frame->AdjustOffsetsForBidi(0, 0);
+ // Nothing more to do for an empty frame, except update
+ // lastRealFrame like we do below.
+ lastRealFrame = frameInfo;
+ continue;
+ }
+ nsLineList::iterator currentLine = aBpd->mCurrentResolveLine.GetLine();
+ if ((runLength > 0) && (runLength < fragmentLength)) {
+ /*
+ * The text in this frame continues beyond the end of this directional
+ * run. Create a non-fluid continuation frame for the next directional
+ * run.
+ */
+ currentLine->MarkDirty();
+ nsIFrame* nextBidi;
+ int32_t runEnd = contentOffset + runLength;
+ EnsureBidiContinuation(frame, currentLine, &nextBidi, contentOffset,
+ runEnd);
+ nextBidi->AdjustOffsetsForBidi(runEnd,
+ contentOffset + fragmentLength);
+ frame = nextBidi;
+ frameInfo.mFrame = frame;
+ contentOffset = runEnd;
+
+ aBpd->mCurrentResolveLine.AdvanceToFrame(frame);
+ } // if (runLength < fragmentLength)
+ else {
+ if (contentOffset + fragmentLength == contentTextLength) {
+ /*
+ * We have finished all the text in this content node. Convert any
+ * further non-fluid continuations to fluid continuations and
+ * advance frameIndex to the last frame in the content node
+ */
+ int32_t newIndex = aBpd->GetLastFrameForContent(content);
+ if (newIndex > frameIndex) {
+ currentLine->MarkDirty();
+ RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
+ frameIndex = newIndex;
+ frameInfo = aBpd->FrameInfoAt(frameIndex);
+ frame = frameInfo.mFrame;
+ }
+ } else if (fragmentLength > 0 && runLength > fragmentLength) {
+ /*
+ * There is more text that belongs to this directional run in the
+ * next text frame: make sure it is a fluid continuation of the
+ * current frame. Do not advance frameIndex, because the next frame
+ * may contain multi-directional text and need to be split
+ */
+ int32_t newIndex = frameIndex;
+ do {
+ } while (++newIndex < frameCount &&
+ aBpd->FrameAt(newIndex) == NS_BIDI_CONTROL_FRAME);
+ if (newIndex < frameCount) {
+ currentLine->MarkDirty();
+ RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
+ }
+ } else if (runLength == fragmentLength) {
+ /*
+ * If the directional run ends at the end of the frame, make sure
+ * that any continuation is non-fluid, and do the same up the
+ * parent chain
+ */
+ nsIFrame* next = frame->GetNextInFlow();
+ if (next) {
+ currentLine->MarkDirty();
+ MakeContinuationsNonFluidUpParentChain(frame, next);
+ }
+ }
+ frame->AdjustOffsetsForBidi(contentOffset,
+ contentOffset + fragmentLength);
+ }
+ } // isTextFrame
+ } // not bidi control frame
+ int32_t temp = runLength;
+ runLength -= fragmentLength;
+ fragmentLength -= temp;
+
+ // Record last real frame so that we can do splitting properly even
+ // if a run ends after a virtual bidi control frame.
+ if (frame != NS_BIDI_CONTROL_FRAME) {
+ lastRealFrame = frameInfo;
+ }
+ if (lastRealFrame.mFrame && fragmentLength <= 0) {
+ // If the frame is at the end of a run, and this is not the end of our
+ // paragraph, split all ancestor inlines that need splitting.
+ // To determine whether we're at the end of the run, we check that we've
+ // finished processing the current run, and that the current frame
+ // doesn't have a fluid continuation (it could have a fluid continuation
+ // of zero length, so testing runLength alone is not sufficient).
+ if (runLength <= 0 && !lastRealFrame.mFrame->GetNextInFlow()) {
+ if (numRun + 1 < runCount) {
+ nsIFrame* child = lastRealFrame.mFrame;
+ nsContainerFrame* parent = child->GetParent();
+ // As long as we're on the last sibling, the parent doesn't have to
+ // be split.
+ // However, if the parent has a fluid continuation, we do have to make
+ // it non-fluid. This can happen e.g. when we have a first-letter
+ // frame and the end of the first-letter coincides with the end of a
+ // directional run.
+ while (parent && IsBidiSplittable(parent) &&
+ !child->GetNextSibling()) {
+ nsIFrame* next = parent->GetNextInFlow();
+ if (next) {
+ parent->SetNextContinuation(next);
+ next->SetPrevContinuation(parent);
+ }
+ child = parent;
+ parent = child->GetParent();
+ }
+ if (parent && IsBidiSplittable(parent)) {
+ aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
+ SplitInlineAncestors(parent, aBpd->mCurrentResolveLine.GetLine(),
+ child);
+
+ aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
+ }
+ }
+ } else if (frame != NS_BIDI_CONTROL_FRAME) {
+ // We're not at an end of a run. If |frame| is the last child of its
+ // parent, and its ancestors happen to have bidi continuations, convert
+ // them into fluid continuations.
+ JoinInlineAncestors(frame);
+ }
+ }
+ } // for
+
+#ifdef DEBUG
+# ifdef REALLY_NOISY_BIDI
+ printf("---\nAfter Resolve(), frameTree =:\n");
+ aBpd->mCurrentBlock->List(stdout);
+ printf("===\n");
+# endif
+#endif
+
+ return rv;
+}
+
+void nsBidiPresUtils::TraverseFrames(nsIFrame* aCurrentFrame,
+ BidiParagraphData* aBpd) {
+ if (!aCurrentFrame) return;
+
+#ifdef DEBUG
+ nsBlockFrame* initialLineContainer =
+ aBpd->mCurrentTraverseLine.mLineIterator.GetContainer();
+#endif
+
+ nsIFrame* childFrame = aCurrentFrame;
+ do {
+ /*
+ * It's important to get the next sibling and next continuation *before*
+ * handling the frame: If we encounter a forced paragraph break and call
+ * ResolveParagraph within this loop, doing GetNextSibling and
+ * GetNextContinuation after that could return a bidi continuation that had
+ * just been split from the original childFrame and we would process it
+ * twice.
+ */
+ nsIFrame* nextSibling = childFrame->GetNextSibling();
+
+ // If the real frame for a placeholder is a first letter frame, we need to
+ // drill down into it and include its contents in Bidi resolution.
+ // If not, we just use the placeholder.
+ nsIFrame* frame = childFrame;
+ if (childFrame->IsPlaceholderFrame()) {
+ nsIFrame* realFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
+ if (realFrame->IsLetterFrame()) {
+ frame = realFrame;
+ }
+ }
+
+ auto DifferentBidiValues = [](ComputedStyle* aSC1, nsIFrame* aFrame2) {
+ ComputedStyle* sc2 = aFrame2->Style();
+ return GetBidiControl(aSC1) != GetBidiControl(sc2) ||
+ GetBidiOverride(aSC1) != GetBidiOverride(sc2);
+ };
+
+ ComputedStyle* sc = frame->Style();
+ nsIFrame* nextContinuation = frame->GetNextContinuation();
+ nsIFrame* prevContinuation = frame->GetPrevContinuation();
+ bool isLastFrame =
+ !nextContinuation || DifferentBidiValues(sc, nextContinuation);
+ bool isFirstFrame =
+ !prevContinuation || DifferentBidiValues(sc, prevContinuation);
+
+ char16_t controlChar = 0;
+ char16_t overrideChar = 0;
+ LayoutFrameType frameType = frame->Type();
+ if (frame->IsBidiInlineContainer() || RubyUtils::IsRubyBox(frameType)) {
+ if (!frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ nsContainerFrame* c = static_cast<nsContainerFrame*>(frame);
+ MOZ_ASSERT(c == do_QueryFrame(frame),
+ "eBidiInlineContainer and ruby frame must be"
+ " a nsContainerFrame subclass");
+ c->DrainSelfOverflowList();
+ }
+
+ controlChar = GetBidiControl(sc);
+ overrideChar = GetBidiOverride(sc);
+
+ // Add dummy frame pointers representing bidi control codes before
+ // the first frames of elements specifying override, isolation, or
+ // plaintext.
+ if (isFirstFrame) {
+ if (controlChar != 0) {
+ aBpd->PushBidiControl(controlChar);
+ }
+ if (overrideChar != 0) {
+ aBpd->PushBidiControl(overrideChar);
+ }
+ }
+ }
+
+ if (IsBidiLeaf(frame)) {
+ /* Bidi leaf frame: add the frame to the mLogicalFrames array,
+ * and add its index to the mContentToFrameIndex hashtable. This
+ * will be used in RemoveBidiContinuation() to identify the last
+ * frame in the array with a given content.
+ */
+ nsIContent* content = frame->GetContent();
+ aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine, content);
+
+ // Append the content of the frame to the paragraph buffer
+ if (LayoutFrameType::Text == frameType) {
+ if (content != aBpd->mPrevContent) {
+ aBpd->mPrevContent = content;
+ if (!frame->StyleText()->NewlineIsSignificant(
+ static_cast<nsTextFrame*>(frame))) {
+ content->GetAsText()->AppendTextTo(aBpd->mBuffer);
+ } else {
+ /*
+ * For preformatted text we have to do bidi resolution on each line
+ * separately.
+ */
+ nsAutoString text;
+ content->GetAsText()->AppendTextTo(text);
+ nsIFrame* next;
+ do {
+ next = nullptr;
+
+ auto [start, end] = frame->GetOffsets();
+ int32_t endLine = text.FindChar('\n', start);
+ if (endLine == -1) {
+ /*
+ * If there is no newline in the text content, just save the
+ * text from this frame and its continuations, and do bidi
+ * resolution later
+ */
+ aBpd->AppendString(Substring(text, start));
+ while (frame && nextSibling) {
+ aBpd->AdvanceAndAppendFrame(
+ &frame, aBpd->mCurrentTraverseLine, &nextSibling);
+ }
+ break;
+ }
+
+ /*
+ * If there is a newline in the frame, break the frame after the
+ * newline, do bidi resolution and repeat until the last sibling
+ */
+ ++endLine;
+
+ /*
+ * If the frame ends before the new line, save the text and move
+ * into the next continuation
+ */
+ aBpd->AppendString(
+ Substring(text, start, std::min(end, endLine) - start));
+ while (end < endLine && nextSibling) {
+ aBpd->AdvanceAndAppendFrame(&frame, aBpd->mCurrentTraverseLine,
+ &nextSibling);
+ NS_ASSERTION(frame, "Premature end of continuation chain");
+ std::tie(start, end) = frame->GetOffsets();
+ aBpd->AppendString(
+ Substring(text, start, std::min(end, endLine) - start));
+ }
+
+ if (end < endLine) {
+ aBpd->mPrevContent = nullptr;
+ break;
+ }
+
+ bool createdContinuation = false;
+ if (uint32_t(endLine) < text.Length()) {
+ /*
+ * Timing is everything here: if the frame already has a bidi
+ * continuation, we need to make the continuation fluid *before*
+ * resetting the length of the current frame. Otherwise
+ * nsTextFrame::SetLength won't set the continuation frame's
+ * text offsets correctly.
+ *
+ * On the other hand, if the frame doesn't have a continuation,
+ * we need to create one *after* resetting the length, or
+ * CreateContinuingFrame will complain that there is no more
+ * content for the continuation.
+ */
+ next = frame->GetNextInFlow();
+ if (!next) {
+ // If the frame already has a bidi continuation, make it fluid
+ next = frame->GetNextContinuation();
+ if (next) {
+ MakeContinuationFluid(frame, next);
+ JoinInlineAncestors(frame);
+ }
+ }
+
+ nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
+ textFrame->SetLength(endLine - start, nullptr);
+
+ // If it weren't for CreateContinuation needing this to
+ // be current, we could restructure the marking dirty
+ // below to use mCurrentResolveLine and eliminate
+ // mCurrentTraverseLine entirely.
+ aBpd->mCurrentTraverseLine.AdvanceToFrame(frame);
+
+ if (!next) {
+ // If the frame has no next in flow, create one.
+ CreateContinuation(
+ frame, aBpd->mCurrentTraverseLine.GetLine(), &next, true);
+ createdContinuation = true;
+ }
+ // Mark the line before the newline as dirty.
+ aBpd->mCurrentTraverseLine.GetLine()->MarkDirty();
+ }
+ ResolveParagraphWithinBlock(aBpd);
+
+ if (!nextSibling && !createdContinuation) {
+ break;
+ }
+ if (next) {
+ frame = next;
+ aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine);
+ // Mark the line after the newline as dirty.
+ aBpd->mCurrentTraverseLine.AdvanceToFrame(frame);
+ aBpd->mCurrentTraverseLine.GetLine()->MarkDirty();
+ }
+
+ /*
+ * If we have already overshot the saved next-sibling while
+ * scanning the frame's continuations, advance it.
+ */
+ if (frame && frame == nextSibling) {
+ nextSibling = frame->GetNextSibling();
+ }
+
+ } while (next);
+ }
+ }
+ } else if (LayoutFrameType::Br == frameType) {
+ // break frame -- append line separator
+ aBpd->AppendUnichar(kLineSeparator);
+ ResolveParagraphWithinBlock(aBpd);
+ } else {
+ // other frame type -- see the Unicode Bidi Algorithm:
+ // "...inline objects (such as graphics) are treated as if they are ...
+ // U+FFFC"
+ // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
+ // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1
+ aBpd->AppendUnichar(
+ content->IsHTMLElement(nsGkAtoms::wbr) ? kZWSP : kObjectSubstitute);
+ if (!frame->IsInlineOutside()) {
+ // if it is not inline, end the paragraph
+ ResolveParagraphWithinBlock(aBpd);
+ }
+ }
+ } else {
+ // For a non-leaf frame, recurse into TraverseFrames
+ nsIFrame* kid = frame->PrincipalChildList().FirstChild();
+ MOZ_ASSERT(!frame->GetChildList(FrameChildListID::Overflow).FirstChild(),
+ "should have drained the overflow list above");
+ if (kid) {
+ TraverseFrames(kid, aBpd);
+ }
+ }
+
+ // If the element is attributed by dir, indicate direction pop (add PDF
+ // frame)
+ if (isLastFrame) {
+ // Add a dummy frame pointer representing a bidi control code after the
+ // last frame of an element specifying embedding or override
+ if (overrideChar != 0) {
+ aBpd->PopBidiControl(overrideChar);
+ }
+ if (controlChar != 0) {
+ aBpd->PopBidiControl(controlChar);
+ }
+ }
+ childFrame = nextSibling;
+ } while (childFrame);
+
+ MOZ_ASSERT(initialLineContainer ==
+ aBpd->mCurrentTraverseLine.mLineIterator.GetContainer());
+}
+
+bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame* aFirstChild,
+ nsIContent** aCurrContent) {
+ MOZ_ASSERT(!aFirstChild || !aFirstChild->GetPrevSibling(),
+ "Expecting to traverse from the start of a child list");
+
+ for (nsIFrame* childFrame = aFirstChild; childFrame;
+ childFrame = childFrame->GetNextSibling()) {
+ nsIFrame* frame = childFrame;
+
+ // If the real frame for a placeholder is a first-letter frame, we need to
+ // consider its contents for potential Bidi resolution.
+ if (childFrame->IsPlaceholderFrame()) {
+ nsIFrame* realFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
+ if (realFrame->IsLetterFrame()) {
+ frame = realFrame;
+ }
+ }
+
+ // If unicode-bidi properties are present, we should do bidi resolution.
+ ComputedStyle* sc = frame->Style();
+ if (GetBidiControl(sc) || GetBidiOverride(sc)) {
+ return true;
+ }
+
+ if (IsBidiLeaf(frame)) {
+ if (frame->IsTextFrame()) {
+ // If the frame already has a BidiDataProperty, we know we need to
+ // perform bidi resolution (even if no bidi content is NOW present --
+ // we might need to remove the property set by a previous reflow, if
+ // content has changed; see bug 1366623).
+ if (frame->HasProperty(nsIFrame::BidiDataProperty())) {
+ return true;
+ }
+
+ // Check whether the text frame has any RTL characters; if so, bidi
+ // resolution will be needed.
+ dom::Text* content = frame->GetContent()->AsText();
+ if (content != *aCurrContent) {
+ *aCurrContent = content;
+ const nsTextFragment* txt = &content->TextFragment();
+ if (txt->Is2b() &&
+ HasRTLChars(Span(txt->Get2b(), txt->GetLength()))) {
+ return true;
+ }
+ }
+ }
+ } else if (ChildListMayRequireBidi(frame->PrincipalChildList().FirstChild(),
+ aCurrContent)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData* aBpd) {
+ aBpd->ClearBidiControls();
+ ResolveParagraph(aBpd);
+ aBpd->ResetData();
+}
+
+/* static */
+nscoord nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine,
+ WritingMode aLineWM,
+ const nsSize& aContainerSize,
+ nscoord aStart) {
+ nsSize containerSize(aContainerSize);
+
+ // If this line consists of a line frame, reorder the line frame's children.
+ if (aFirstFrameOnLine->IsLineFrame()) {
+ // The line frame is positioned at the start-edge, so use its size
+ // as the container size.
+ containerSize = aFirstFrameOnLine->GetSize();
+
+ aFirstFrameOnLine = aFirstFrameOnLine->PrincipalChildList().FirstChild();
+ if (!aFirstFrameOnLine) {
+ return 0;
+ }
+ // All children of the line frame are on the first line. Setting
+ // aNumFramesOnLine to -1 makes InitLogicalArrayFromLine look at all of
+ // them.
+ aNumFramesOnLine = -1;
+ // As the line frame itself has been adjusted at its inline-start position
+ // by the caller, we do not want to apply this to its children.
+ aStart = 0;
+ }
+
+ BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
+ return RepositionInlineFrames(&bld, aLineWM, containerSize, aStart);
+}
+
+nsIFrame* nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame) {
+ nsIFrame* firstLeaf = aFrame;
+ while (!IsBidiLeaf(firstLeaf)) {
+ nsIFrame* firstChild = firstLeaf->PrincipalChildList().FirstChild();
+ nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(firstChild);
+ firstLeaf = (realFrame->IsLetterFrame()) ? realFrame : firstChild;
+ }
+ return firstLeaf;
+}
+
+FrameBidiData nsBidiPresUtils::GetFrameBidiData(nsIFrame* aFrame) {
+ return GetFirstLeaf(aFrame)->GetBidiData();
+}
+
+BidiEmbeddingLevel nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) {
+ return GetFirstLeaf(aFrame)->GetEmbeddingLevel();
+}
+
+BidiEmbeddingLevel nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame* aFrame) {
+ const nsIFrame* firstLeaf = aFrame;
+ while (!IsBidiLeaf(firstLeaf)) {
+ firstLeaf = firstLeaf->PrincipalChildList().FirstChild();
+ }
+ return firstLeaf->GetBaseLevel();
+}
+
+void nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame,
+ nsContinuationStates* aContinuationStates,
+ bool aSpanDirMatchesLineDir,
+ bool& aIsFirst /* out */,
+ bool& aIsLast /* out */) {
+ /*
+ * Since we lay out frames in the line's direction, visiting a frame with
+ * 'mFirstVisualFrame == nullptr', means it's the first appearance of one
+ * of its continuation chain frames on the line.
+ * To determine if it's the last visual frame of its continuation chain on
+ * the line or not, we count the number of frames of the chain on the line,
+ * and then reduce it when we lay out a frame of the chain. If this value
+ * becomes 1 it means that it's the last visual frame of its continuation
+ * chain on this line.
+ */
+
+ bool firstInLineOrder, lastInLineOrder;
+ nsFrameContinuationState* frameState = aContinuationStates->Get(aFrame);
+ nsFrameContinuationState* firstFrameState;
+
+ if (!frameState->mFirstVisualFrame) {
+ // aFrame is the first visual frame of its continuation chain
+ nsFrameContinuationState* contState;
+ nsIFrame* frame;
+
+ frameState->mFrameCount = 1;
+ frameState->mFirstVisualFrame = aFrame;
+
+ /**
+ * Traverse continuation chain of aFrame in both backward and forward
+ * directions while the frames are on this line. Count the frames and
+ * set their mFirstVisualFrame to aFrame.
+ */
+ // Traverse continuation chain backward
+ for (frame = aFrame->GetPrevContinuation();
+ frame && (contState = aContinuationStates->Get(frame));
+ frame = frame->GetPrevContinuation()) {
+ frameState->mFrameCount++;
+ contState->mFirstVisualFrame = aFrame;
+ }
+ frameState->mHasContOnPrevLines = (frame != nullptr);
+
+ // Traverse continuation chain forward
+ for (frame = aFrame->GetNextContinuation();
+ frame && (contState = aContinuationStates->Get(frame));
+ frame = frame->GetNextContinuation()) {
+ frameState->mFrameCount++;
+ contState->mFirstVisualFrame = aFrame;
+ }
+ frameState->mHasContOnNextLines = (frame != nullptr);
+
+ firstInLineOrder = true;
+ firstFrameState = frameState;
+ } else {
+ // aFrame is not the first visual frame of its continuation chain
+ firstInLineOrder = false;
+ firstFrameState = aContinuationStates->Get(frameState->mFirstVisualFrame);
+ }
+
+ lastInLineOrder = (firstFrameState->mFrameCount == 1);
+
+ if (aSpanDirMatchesLineDir) {
+ aIsFirst = firstInLineOrder;
+ aIsLast = lastInLineOrder;
+ } else {
+ aIsFirst = lastInLineOrder;
+ aIsLast = firstInLineOrder;
+ }
+
+ if (frameState->mHasContOnPrevLines) {
+ aIsFirst = false;
+ }
+ if (firstFrameState->mHasContOnNextLines) {
+ aIsLast = false;
+ }
+
+ if ((aIsFirst || aIsLast) &&
+ aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ // For ib splits, don't treat anything except the last part as
+ // endmost or anything except the first part as startmost.
+ // As an optimization, only get the first continuation once.
+ nsIFrame* firstContinuation = aFrame->FirstContinuation();
+ if (firstContinuation->FrameIsNonLastInIBSplit()) {
+ // We are not endmost
+ aIsLast = false;
+ }
+ if (firstContinuation->FrameIsNonFirstInIBSplit()) {
+ // We are not startmost
+ aIsFirst = false;
+ }
+ }
+
+ // Reduce number of remaining frames of the continuation chain on the line.
+ firstFrameState->mFrameCount--;
+
+ nsInlineFrame* testFrame = do_QueryFrame(aFrame);
+
+ if (testFrame) {
+ aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET);
+
+ if (aIsFirst) {
+ aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
+ } else {
+ aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
+ }
+
+ if (aIsLast) {
+ aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
+ } else {
+ aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
+ }
+ }
+}
+
+/* static */
+void nsBidiPresUtils::RepositionRubyContentFrame(
+ nsIFrame* aFrame, WritingMode aFrameWM,
+ const LogicalMargin& aBorderPadding) {
+ const nsFrameList& childList = aFrame->PrincipalChildList();
+ if (childList.IsEmpty()) {
+ return;
+ }
+
+ // Reorder the children.
+ nscoord isize =
+ ReorderFrames(childList.FirstChild(), childList.GetLength(), aFrameWM,
+ aFrame->GetSize(), aBorderPadding.IStart(aFrameWM));
+ isize += aBorderPadding.IEnd(aFrameWM);
+
+ if (aFrame->StyleText()->mRubyAlign == StyleRubyAlign::Start) {
+ return;
+ }
+ nscoord residualISize = aFrame->ISize(aFrameWM) - isize;
+ if (residualISize <= 0) {
+ return;
+ }
+
+ // When ruby-align is not "start", if the content does not fill this
+ // frame, we need to center the children.
+ const nsSize dummyContainerSize;
+ for (nsIFrame* child : childList) {
+ LogicalRect rect = child->GetLogicalRect(aFrameWM, dummyContainerSize);
+ rect.IStart(aFrameWM) += residualISize / 2;
+ child->SetRect(aFrameWM, rect, dummyContainerSize);
+ }
+}
+
+/* static */
+nscoord nsBidiPresUtils::RepositionRubyFrame(
+ nsIFrame* aFrame, nsContinuationStates* aContinuationStates,
+ const WritingMode aContainerWM, const LogicalMargin& aBorderPadding) {
+ LayoutFrameType frameType = aFrame->Type();
+ MOZ_ASSERT(RubyUtils::IsRubyBox(frameType));
+
+ nscoord icoord = 0;
+ WritingMode frameWM = aFrame->GetWritingMode();
+ bool isLTR = frameWM.IsBidiLTR();
+ nsSize frameSize = aFrame->GetSize();
+ if (frameType == LayoutFrameType::Ruby) {
+ icoord += aBorderPadding.IStart(frameWM);
+ // Reposition ruby segments in a ruby container
+ for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(aFrame)); !e.AtEnd();
+ e.Next()) {
+ nsRubyBaseContainerFrame* rbc = e.GetBaseContainer();
+ AutoRubyTextContainerArray textContainers(rbc);
+
+ nscoord segmentISize = RepositionFrame(
+ rbc, isLTR, icoord, aContinuationStates, frameWM, false, frameSize);
+ for (nsRubyTextContainerFrame* rtc : textContainers) {
+ nscoord isize = RepositionFrame(rtc, isLTR, icoord, aContinuationStates,
+ frameWM, false, frameSize);
+ segmentISize = std::max(segmentISize, isize);
+ }
+ icoord += segmentISize;
+ }
+ icoord += aBorderPadding.IEnd(frameWM);
+ } else if (frameType == LayoutFrameType::RubyBaseContainer) {
+ // Reposition ruby columns in a ruby segment
+ auto rbc = static_cast<nsRubyBaseContainerFrame*>(aFrame);
+ AutoRubyTextContainerArray textContainers(rbc);
+
+ for (RubyColumnEnumerator e(rbc, textContainers); !e.AtEnd(); e.Next()) {
+ RubyColumn column;
+ e.GetColumn(column);
+
+ nscoord columnISize =
+ RepositionFrame(column.mBaseFrame, isLTR, icoord, aContinuationStates,
+ frameWM, false, frameSize);
+ for (nsRubyTextFrame* rt : column.mTextFrames) {
+ nscoord isize = RepositionFrame(rt, isLTR, icoord, aContinuationStates,
+ frameWM, false, frameSize);
+ columnISize = std::max(columnISize, isize);
+ }
+ icoord += columnISize;
+ }
+ } else {
+ if (frameType == LayoutFrameType::RubyBase ||
+ frameType == LayoutFrameType::RubyText) {
+ RepositionRubyContentFrame(aFrame, frameWM, aBorderPadding);
+ }
+ // Note that, ruby text container is not present in all conditions
+ // above. It is intended, because the children of rtc are reordered
+ // with the children of ruby base container simultaneously. We only
+ // need to return its isize here, as it should not be changed.
+ icoord += aFrame->ISize(aContainerWM);
+ }
+ return icoord;
+}
+
+/* static */
+nscoord nsBidiPresUtils::RepositionFrame(
+ nsIFrame* aFrame, bool aIsEvenLevel, nscoord aStartOrEnd,
+ nsContinuationStates* aContinuationStates, WritingMode aContainerWM,
+ bool aContainerReverseDir, const nsSize& aContainerSize) {
+ nscoord lineSize =
+ aContainerWM.IsVertical() ? aContainerSize.height : aContainerSize.width;
+ NS_ASSERTION(lineSize != NS_UNCONSTRAINEDSIZE,
+ "Unconstrained inline line size in bidi frame reordering");
+ if (!aFrame) return 0;
+
+ bool isFirst, isLast;
+ WritingMode frameWM = aFrame->GetWritingMode();
+ IsFirstOrLast(aFrame, aContinuationStates,
+ aContainerWM.IsBidiLTR() == frameWM.IsBidiLTR(),
+ isFirst /* out */, isLast /* out */);
+
+ // We only need the margin if the frame is first or last in its own
+ // writing mode, but we're traversing the frames in the order of the
+ // container's writing mode. To get the right values, we set start and
+ // end margins on a logical margin in the frame's writing mode, and
+ // then convert the margin to the container's writing mode to set the
+ // coordinates.
+
+ // This method is called from nsBlockFrame::PlaceLine via the call to
+ // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
+ // have been reflowed, which is required for GetUsedMargin/Border/Padding
+ nscoord frameISize = aFrame->ISize();
+ LogicalMargin frameMargin = aFrame->GetLogicalUsedMargin(frameWM);
+ LogicalMargin borderPadding = aFrame->GetLogicalUsedBorderAndPadding(frameWM);
+ // Since the visual order of frame could be different from the continuation
+ // order, we need to remove any inline border/padding [that is already applied
+ // based on continuation order] and then add it back based on the visual order
+ // (i.e. isFirst/isLast) to get the correct isize for the current frame.
+ // We don't need to do that for 'box-decoration-break:clone' because then all
+ // continuations have border/padding/margin applied.
+ if (aFrame->StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Slice) {
+ // First remove the border/padding that was applied based on logical order.
+ if (!aFrame->GetPrevContinuation()) {
+ frameISize -= borderPadding.IStart(frameWM);
+ }
+ if (!aFrame->GetNextContinuation()) {
+ frameISize -= borderPadding.IEnd(frameWM);
+ }
+ // Set margin/border/padding based on visual order.
+ if (!isFirst) {
+ frameMargin.IStart(frameWM) = 0;
+ borderPadding.IStart(frameWM) = 0;
+ }
+ if (!isLast) {
+ frameMargin.IEnd(frameWM) = 0;
+ borderPadding.IEnd(frameWM) = 0;
+ }
+ // Add the border/padding which is now based on visual order.
+ frameISize += borderPadding.IStartEnd(frameWM);
+ }
+
+ nscoord icoord = 0;
+ if (IsBidiLeaf(aFrame)) {
+ icoord +=
+ frameWM.IsOrthogonalTo(aContainerWM) ? aFrame->BSize() : frameISize;
+ } else if (RubyUtils::IsRubyBox(aFrame->Type())) {
+ icoord += RepositionRubyFrame(aFrame, aContinuationStates, aContainerWM,
+ borderPadding);
+ } else {
+ bool reverseDir = aIsEvenLevel != frameWM.IsBidiLTR();
+ icoord += reverseDir ? borderPadding.IEnd(frameWM)
+ : borderPadding.IStart(frameWM);
+ LogicalSize logicalSize(frameWM, frameISize, aFrame->BSize());
+ nsSize frameSize = logicalSize.GetPhysicalSize(frameWM);
+ // Reposition the child frames
+ for (nsIFrame* f : aFrame->PrincipalChildList()) {
+ icoord += RepositionFrame(f, aIsEvenLevel, icoord, aContinuationStates,
+ frameWM, reverseDir, frameSize);
+ }
+ icoord += reverseDir ? borderPadding.IStart(frameWM)
+ : borderPadding.IEnd(frameWM);
+ }
+
+ // In the following variables, if aContainerReverseDir is true, i.e.
+ // the container is positioning its children in reverse of its logical
+ // direction, the "StartOrEnd" refers to the distance from the frame
+ // to the inline end edge of the container, elsewise, it refers to the
+ // distance to the inline start edge.
+ const LogicalMargin margin = frameMargin.ConvertTo(aContainerWM, frameWM);
+ nscoord marginStartOrEnd = aContainerReverseDir ? margin.IEnd(aContainerWM)
+ : margin.IStart(aContainerWM);
+ nscoord frameStartOrEnd = aStartOrEnd + marginStartOrEnd;
+
+ LogicalRect rect = aFrame->GetLogicalRect(aContainerWM, aContainerSize);
+ rect.ISize(aContainerWM) = icoord;
+ rect.IStart(aContainerWM) = aContainerReverseDir
+ ? lineSize - frameStartOrEnd - icoord
+ : frameStartOrEnd;
+ aFrame->SetRect(aContainerWM, rect, aContainerSize);
+
+ return icoord + margin.IStartEnd(aContainerWM);
+}
+
+void nsBidiPresUtils::InitContinuationStates(
+ nsIFrame* aFrame, nsContinuationStates* aContinuationStates) {
+ aContinuationStates->Insert(aFrame);
+ if (!IsBidiLeaf(aFrame)) {
+ // Continue for child frames
+ for (nsIFrame* frame : aFrame->PrincipalChildList()) {
+ InitContinuationStates(frame, aContinuationStates);
+ }
+ }
+}
+
+/* static */
+nscoord nsBidiPresUtils::RepositionInlineFrames(BidiLineData* aBld,
+ WritingMode aLineWM,
+ const nsSize& aContainerSize,
+ nscoord aStart) {
+ nscoord start = aStart;
+ nsIFrame* frame;
+ int32_t count = aBld->mVisualFrames.Length();
+ int32_t index;
+ nsContinuationStates continuationStates;
+
+ // Initialize continuation states to (nullptr, 0) for
+ // each frame on the line.
+ for (index = 0; index < count; index++) {
+ InitContinuationStates(aBld->VisualFrameAt(index), &continuationStates);
+ }
+
+ // Reposition frames in visual order
+ int32_t step, limit;
+ if (aLineWM.IsBidiLTR()) {
+ index = 0;
+ step = 1;
+ limit = count;
+ } else {
+ index = count - 1;
+ step = -1;
+ limit = -1;
+ }
+ for (; index != limit; index += step) {
+ frame = aBld->VisualFrameAt(index);
+ start += RepositionFrame(
+ frame, !(aBld->mLevels[aBld->mIndexMap[index]].IsRTL()), start,
+ &continuationStates, aLineWM, false, aContainerSize);
+ }
+ return start;
+}
+
+bool nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine,
+ nsIFrame** aFirstVisual,
+ nsIFrame** aLastVisual) {
+ BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
+ int32_t count = bld.FrameCount();
+
+ if (aFirstVisual) {
+ *aFirstVisual = bld.VisualFrameAt(0);
+ }
+ if (aLastVisual) {
+ *aLastVisual = bld.VisualFrameAt(count - 1);
+ }
+
+ return bld.mIsReordered;
+}
+
+nsIFrame* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame,
+ nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine) {
+ BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
+
+ int32_t count = bld.mVisualFrames.Length();
+
+ if (aFrame == nullptr && count) return bld.VisualFrameAt(0);
+
+ for (int32_t i = 0; i < count - 1; i++) {
+ if (bld.VisualFrameAt(i) == aFrame) {
+ return bld.VisualFrameAt(i + 1);
+ }
+ }
+
+ return nullptr;
+}
+
+nsIFrame* nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame* aFrame,
+ nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine) {
+ BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
+
+ int32_t count = bld.mVisualFrames.Length();
+
+ if (aFrame == nullptr && count) return bld.VisualFrameAt(count - 1);
+
+ for (int32_t i = 1; i < count; i++) {
+ if (bld.VisualFrameAt(i) == aFrame) {
+ return bld.VisualFrameAt(i - 1);
+ }
+ }
+
+ return nullptr;
+}
+
+inline void nsBidiPresUtils::EnsureBidiContinuation(
+ nsIFrame* aFrame, const nsLineList::iterator aLine, nsIFrame** aNewFrame,
+ int32_t aStart, int32_t aEnd) {
+ MOZ_ASSERT(aNewFrame, "null OUT ptr");
+ MOZ_ASSERT(aFrame, "aFrame is null");
+
+ aFrame->AdjustOffsetsForBidi(aStart, aEnd);
+ CreateContinuation(aFrame, aLine, aNewFrame, false);
+}
+
+void nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData* aBpd,
+ nsIFrame* aFrame,
+ int32_t aFirstIndex,
+ int32_t aLastIndex) {
+ FrameBidiData bidiData = aFrame->GetBidiData();
+ bidiData.precedingControl = kBidiLevelNone;
+ for (int32_t index = aFirstIndex + 1; index <= aLastIndex; index++) {
+ nsIFrame* frame = aBpd->FrameAt(index);
+ if (frame != NS_BIDI_CONTROL_FRAME) {
+ // Make the frame and its continuation ancestors fluid,
+ // so they can be reused or deleted by normal reflow code
+ frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData);
+ frame->AddStateBits(NS_FRAME_IS_BIDI);
+ while (frame && IsBidiSplittable(frame)) {
+ nsIFrame* prev = frame->GetPrevContinuation();
+ if (prev) {
+ MakeContinuationFluid(prev, frame);
+ frame = frame->GetParent();
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ // Make sure that the last continuation we made fluid does not itself have a
+ // fluid continuation (this can happen when re-resolving after dynamic changes
+ // to content)
+ nsIFrame* lastFrame = aBpd->FrameAt(aLastIndex);
+ MakeContinuationsNonFluidUpParentChain(lastFrame, lastFrame->GetNextInFlow());
+}
+
+nsresult nsBidiPresUtils::FormatUnicodeText(nsPresContext* aPresContext,
+ char16_t* aText,
+ int32_t& aTextLength,
+ BidiClass aBidiClass) {
+ nsresult rv = NS_OK;
+ // ahmed
+ // adjusted for correct numeral shaping
+ uint32_t bidiOptions = aPresContext->GetBidi();
+ switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) {
+ case IBMBIDI_NUMERAL_HINDI:
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
+ break;
+
+ case IBMBIDI_NUMERAL_ARABIC:
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
+ break;
+
+ case IBMBIDI_NUMERAL_PERSIAN:
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN);
+ break;
+
+ case IBMBIDI_NUMERAL_REGULAR:
+
+ switch (aBidiClass) {
+ case BidiClass::EuropeanNumber:
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
+ break;
+
+ case BidiClass::ArabicNumber:
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case IBMBIDI_NUMERAL_HINDICONTEXT:
+ if (((GET_BIDI_OPTION_DIRECTION(bidiOptions) ==
+ IBMBIDI_TEXTDIRECTION_RTL) &&
+ (IS_ARABIC_DIGIT(aText[0]))) ||
+ (BidiClass::ArabicNumber == aBidiClass)) {
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
+ } else if (BidiClass::EuropeanNumber == aBidiClass) {
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
+ }
+ break;
+
+ case IBMBIDI_NUMERAL_PERSIANCONTEXT:
+ if (((GET_BIDI_OPTION_DIRECTION(bidiOptions) ==
+ IBMBIDI_TEXTDIRECTION_RTL) &&
+ (IS_ARABIC_DIGIT(aText[0]))) ||
+ (BidiClass::ArabicNumber == aBidiClass)) {
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN);
+ } else if (BidiClass::EuropeanNumber == aBidiClass) {
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
+ }
+ break;
+
+ case IBMBIDI_NUMERAL_NOMINAL:
+ default:
+ break;
+ }
+
+ StripBidiControlCharacters(aText, aTextLength);
+ return rv;
+}
+
+void nsBidiPresUtils::StripBidiControlCharacters(char16_t* aText,
+ int32_t& aTextLength) {
+ if ((nullptr == aText) || (aTextLength < 1)) {
+ return;
+ }
+
+ int32_t stripLen = 0;
+
+ for (int32_t i = 0; i < aTextLength; i++) {
+ // XXX: This silently ignores surrogate characters.
+ // As of Unicode 4.0, all Bidi control characters are within the BMP.
+ if (IsBidiControl((uint32_t)aText[i])) {
+ ++stripLen;
+ } else {
+ aText[i - stripLen] = aText[i];
+ }
+ }
+ aTextLength -= stripLen;
+}
+
+void nsBidiPresUtils::CalculateBidiClass(
+ const char16_t* aText, int32_t& aOffset, int32_t aBidiClassLimit,
+ int32_t& aRunLimit, int32_t& aRunLength, int32_t& aRunCount,
+ BidiClass& aBidiClass, BidiClass& aPrevBidiClass) {
+ bool strongTypeFound = false;
+ int32_t offset;
+ BidiClass bidiClass;
+
+ aBidiClass = BidiClass::OtherNeutral;
+
+ int32_t charLen;
+ for (offset = aOffset; offset < aBidiClassLimit; offset += charLen) {
+ // Make sure we give RTL chartype to all characters that would be classified
+ // as Right-To-Left by a bidi platform.
+ // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
+ charLen = 1;
+ uint32_t ch = aText[offset];
+ if (IS_HEBREW_CHAR(ch)) {
+ bidiClass = BidiClass::RightToLeft;
+ } else if (IS_ARABIC_ALPHABETIC(ch)) {
+ bidiClass = BidiClass::RightToLeftArabic;
+ } else {
+ if (offset + 1 < aBidiClassLimit &&
+ NS_IS_SURROGATE_PAIR(ch, aText[offset + 1])) {
+ ch = SURROGATE_TO_UCS4(ch, aText[offset + 1]);
+ charLen = 2;
+ }
+ bidiClass = intl::UnicodeProperties::GetBidiClass(ch);
+ }
+
+ if (!BIDICLASS_IS_WEAK(bidiClass)) {
+ if (strongTypeFound && (bidiClass != aPrevBidiClass) &&
+ (BIDICLASS_IS_RTL(bidiClass) || BIDICLASS_IS_RTL(aPrevBidiClass))) {
+ // Stop at this point to ensure uni-directionality of the text
+ // (from platform's point of view).
+ // Also, don't mix Arabic and Hebrew content (since platform may
+ // provide BIDI support to one of them only).
+ aRunLength = offset - aOffset;
+ aRunLimit = offset;
+ ++aRunCount;
+ break;
+ }
+
+ if ((BidiClass::RightToLeftArabic == aPrevBidiClass ||
+ BidiClass::ArabicNumber == aPrevBidiClass) &&
+ BidiClass::EuropeanNumber == bidiClass) {
+ bidiClass = BidiClass::ArabicNumber;
+ }
+
+ // Set PrevBidiClass to the last strong type in this frame
+ // (for correct numeric shaping)
+ aPrevBidiClass = bidiClass;
+
+ strongTypeFound = true;
+ aBidiClass = bidiClass;
+ }
+ }
+ aOffset = offset;
+}
+
+nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, size_t aLength,
+ BidiEmbeddingLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ BidiProcessor& aprocessor, Mode aMode,
+ nsBidiPositionResolve* aPosResolve,
+ int32_t aPosResolveCount, nscoord* aWidth,
+ BidiEngine& aBidiEngine) {
+ MOZ_ASSERT((aPosResolve == nullptr) != (aPosResolveCount > 0),
+ "Incorrect aPosResolve / aPosResolveCount arguments");
+
+ // Caller should have already replaced any separators in the original text
+ // with <space> characters.
+ MOZ_ASSERT(nsDependentSubstring(aText, aLength).FindCharInSet(kSeparators) ==
+ kNotFound);
+
+ for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
+ aPosResolve[nPosResolve].visualIndex = kNotFound;
+ aPosResolve[nPosResolve].visualLeftTwips = kNotFound;
+ aPosResolve[nPosResolve].visualWidth = kNotFound;
+ }
+
+ // For a single-char string, or a string that is purely LTR, use a simplified
+ // path as it cannot have multiple direction or bidi-class runs.
+ if (aLength == 1 ||
+ (aLength == 2 && NS_IS_SURROGATE_PAIR(aText[0], aText[1])) ||
+ (aBaseLevel.Direction() == BidiDirection::LTR &&
+ !encoding_mem_is_utf16_bidi(aText, aLength))) {
+ ProcessSimpleRun(aText, aLength, aBaseLevel, aPresContext, aprocessor,
+ aMode, aPosResolve, aPosResolveCount, aWidth);
+ return NS_OK;
+ }
+
+ if (aBidiEngine.SetParagraph(Span(aText, aLength), aBaseLevel).isErr()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto result = aBidiEngine.CountRuns();
+ if (result.isErr()) {
+ return NS_ERROR_FAILURE;
+ }
+ int32_t runCount = result.unwrap();
+
+ nscoord xOffset = 0;
+ nscoord width, xEndRun = 0;
+ nscoord totalWidth = 0;
+ int32_t i, start, limit, length;
+ uint32_t visualStart = 0;
+ BidiClass bidiClass;
+ BidiClass prevClass = BidiClass::LeftToRight;
+
+ for (i = 0; i < runCount; i++) {
+ aBidiEngine.GetVisualRun(i, &start, &length);
+
+ BidiEmbeddingLevel level;
+ aBidiEngine.GetLogicalRun(start, &limit, &level);
+
+ BidiDirection dir = level.Direction();
+ int32_t subRunLength = limit - start;
+ int32_t lineOffset = start;
+ int32_t typeLimit = std::min(limit, AssertedCast<int32_t>(aLength));
+ int32_t subRunCount = 1;
+ int32_t subRunLimit = typeLimit;
+
+ /*
+ * If |level| is even, i.e. the direction of the run is left-to-right, we
+ * render the subruns from left to right and increment the x-coordinate
+ * |xOffset| by the width of each subrun after rendering.
+ *
+ * If |level| is odd, i.e. the direction of the run is right-to-left, we
+ * render the subruns from right to left. We begin by incrementing |xOffset|
+ * by the width of the whole run, and then decrement it by the width of each
+ * subrun before rendering. After rendering all the subruns, we restore the
+ * x-coordinate of the end of the run for the start of the next run.
+ */
+
+ if (dir == BidiDirection::RTL) {
+ aprocessor.SetText(aText + start, subRunLength, BidiDirection::RTL);
+ width = aprocessor.GetWidth();
+ xOffset += width;
+ xEndRun = xOffset;
+ }
+
+ while (subRunCount > 0) {
+ // CalculateBidiClass can increment subRunCount if the run
+ // contains mixed character types
+ CalculateBidiClass(aText, lineOffset, typeLimit, subRunLimit,
+ subRunLength, subRunCount, bidiClass, prevClass);
+
+ nsAutoString runVisualText(aText + start, subRunLength);
+ if (aPresContext) {
+ FormatUnicodeText(aPresContext, runVisualText.BeginWriting(),
+ subRunLength, bidiClass);
+ }
+
+ aprocessor.SetText(runVisualText.get(), subRunLength, dir);
+ width = aprocessor.GetWidth();
+ totalWidth += width;
+ if (dir == BidiDirection::RTL) {
+ xOffset -= width;
+ }
+ if (aMode == MODE_DRAW) {
+ aprocessor.DrawText(xOffset);
+ }
+
+ /*
+ * The caller may request to calculate the visual position of one
+ * or more characters.
+ */
+ for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
+ nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
+ /*
+ * Did we already resolve this position's visual metric? If so, skip.
+ */
+ if (posResolve->visualLeftTwips != kNotFound) continue;
+
+ /*
+ * First find out if the logical position is within this run.
+ */
+ if (start <= posResolve->logicalIndex &&
+ start + subRunLength > posResolve->logicalIndex) {
+ /*
+ * If this run is only one character long, we have an easy case:
+ * the visual position is the x-coord of the start of the run
+ * less the x-coord of the start of the whole text.
+ */
+ if (subRunLength == 1) {
+ posResolve->visualIndex = visualStart;
+ posResolve->visualLeftTwips = xOffset;
+ posResolve->visualWidth = width;
+ }
+ /*
+ * Otherwise, we need to measure the width of the run's part
+ * which is to the visual left of the index.
+ * In other words, the run is broken in two, around the logical index,
+ * and we measure the part which is visually left.
+ * If the run is right-to-left, this part will span from after the
+ * index up to the end of the run; if it is left-to-right, this part
+ * will span from the start of the run up to (and inclduing) the
+ * character before the index.
+ */
+ else {
+ /*
+ * Here is a description of how the width of the current character
+ * (posResolve->visualWidth) is calculated:
+ *
+ * LTR (current char: "P"):
+ * S A M P L E (logical index: 3, visual index: 3)
+ * ^ (visualLeftPart)
+ * ^ (visualRightSide)
+ * visualLeftLength == 3
+ * ^^^^^^ (subWidth)
+ * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
+ * ^^ (posResolve->visualWidth)
+ *
+ * RTL (current char: "M"):
+ * E L P M A S (logical index: 2, visual index: 3)
+ * ^ (visualLeftPart)
+ * ^ (visualRightSide)
+ * visualLeftLength == 3
+ * ^^^^^^ (subWidth)
+ * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
+ * ^^ (posResolve->visualWidth)
+ */
+ nscoord subWidth;
+ // The position in the text where this run's "left part" begins.
+ const char16_t* visualLeftPart;
+ const char16_t* visualRightSide;
+ if (dir == BidiDirection::RTL) {
+ // One day, son, this could all be replaced with
+ // mPresContext->BidiEngine().GetVisualIndex() ...
+ posResolve->visualIndex =
+ visualStart +
+ (subRunLength - (posResolve->logicalIndex + 1 - start));
+ // Skipping to the "left part".
+ visualLeftPart = aText + posResolve->logicalIndex + 1;
+ // Skipping to the right side of the current character
+ visualRightSide = visualLeftPart - 1;
+ } else {
+ posResolve->visualIndex =
+ visualStart + (posResolve->logicalIndex - start);
+ // Skipping to the "left part".
+ visualLeftPart = aText + start;
+ // In LTR mode this is the same as visualLeftPart
+ visualRightSide = visualLeftPart;
+ }
+ // The delta between the start of the run and the left part's end.
+ int32_t visualLeftLength = posResolve->visualIndex - visualStart;
+ aprocessor.SetText(visualLeftPart, visualLeftLength, dir);
+ subWidth = aprocessor.GetWidth();
+ aprocessor.SetText(visualRightSide, visualLeftLength + 1, dir);
+ posResolve->visualLeftTwips = xOffset + subWidth;
+ posResolve->visualWidth = aprocessor.GetWidth() - subWidth;
+ }
+ }
+ }
+
+ if (dir == BidiDirection::LTR) {
+ xOffset += width;
+ }
+
+ --subRunCount;
+ start = lineOffset;
+ subRunLimit = typeLimit;
+ subRunLength = typeLimit - lineOffset;
+ } // while
+ if (dir == BidiDirection::RTL) {
+ xOffset = xEndRun;
+ }
+
+ visualStart += length;
+ } // for
+
+ if (aWidth) {
+ *aWidth = totalWidth;
+ }
+ return NS_OK;
+}
+
+// This is called either for a single character (one code unit, or a surrogate
+// pair), or for a run that is known to be purely LTR.
+void nsBidiPresUtils::ProcessSimpleRun(const char16_t* aText, size_t aLength,
+ BidiEmbeddingLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ BidiProcessor& aprocessor, Mode aMode,
+ nsBidiPositionResolve* aPosResolve,
+ int32_t aPosResolveCount,
+ nscoord* aWidth) {
+ if (!aLength) {
+ if (aWidth) {
+ *aWidth = 0;
+ }
+ return;
+ }
+ // Get bidi class from the first (or only) character.
+ uint32_t ch = aText[0];
+ if (aLength > 1 && NS_IS_HIGH_SURROGATE(ch) &&
+ NS_IS_LOW_SURROGATE(aText[1])) {
+ ch = SURROGATE_TO_UCS4(aText[0], aText[1]);
+ }
+ BidiClass bidiClass = intl::UnicodeProperties::GetBidiClass(ch);
+
+ nsAutoString runVisualText(aText, aLength);
+ int32_t length = aLength;
+ if (aPresContext) {
+ FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), length,
+ bidiClass);
+ }
+
+ BidiDirection dir = bidiClass == BidiClass::RightToLeft ||
+ bidiClass == BidiClass::RightToLeftArabic
+ ? BidiDirection::RTL
+ : BidiDirection::LTR;
+ aprocessor.SetText(runVisualText.get(), length, dir);
+
+ if (aMode == MODE_DRAW) {
+ aprocessor.DrawText(0);
+ }
+
+ if (!aWidth && !aPosResolve) {
+ return;
+ }
+
+ nscoord width = aprocessor.GetWidth();
+
+ for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
+ nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
+ if (posResolve->visualLeftTwips != kNotFound) {
+ continue;
+ }
+ if (0 <= posResolve->logicalIndex && length > posResolve->logicalIndex) {
+ posResolve->visualIndex = 0;
+ posResolve->visualLeftTwips = 0;
+ posResolve->visualWidth = width;
+ }
+ }
+
+ if (aWidth) {
+ *aWidth = width;
+ }
+}
+
+class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final
+ : public nsBidiPresUtils::BidiProcessor {
+ public:
+ typedef gfx::DrawTarget DrawTarget;
+
+ nsIRenderingContextBidiProcessor(gfxContext* aCtx,
+ DrawTarget* aTextRunConstructionDrawTarget,
+ nsFontMetrics* aFontMetrics,
+ const nsPoint& aPt)
+ : mCtx(aCtx),
+ mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget),
+ mFontMetrics(aFontMetrics),
+ mPt(aPt),
+ mText(nullptr),
+ mLength(0) {}
+
+ ~nsIRenderingContextBidiProcessor() { mFontMetrics->SetTextRunRTL(false); }
+
+ virtual void SetText(const char16_t* aText, int32_t aLength,
+ BidiDirection aDirection) override {
+ mFontMetrics->SetTextRunRTL(aDirection == BidiDirection::RTL);
+ mText = aText;
+ mLength = aLength;
+ }
+
+ virtual nscoord GetWidth() override {
+ return nsLayoutUtils::AppUnitWidthOfString(mText, mLength, *mFontMetrics,
+ mTextRunConstructionDrawTarget);
+ }
+
+ virtual void DrawText(nscoord aIOffset) override {
+ nsPoint pt(mPt);
+ if (mFontMetrics->GetVertical()) {
+ pt.y += aIOffset;
+ } else {
+ pt.x += aIOffset;
+ }
+ mFontMetrics->DrawString(mText, mLength, pt.x, pt.y, mCtx,
+ mTextRunConstructionDrawTarget);
+ }
+
+ private:
+ gfxContext* mCtx;
+ DrawTarget* mTextRunConstructionDrawTarget;
+ nsFontMetrics* mFontMetrics;
+ nsPoint mPt;
+ const char16_t* mText;
+ int32_t mLength;
+};
+
+nsresult nsBidiPresUtils::ProcessTextForRenderingContext(
+ const char16_t* aText, int32_t aLength, BidiEmbeddingLevel aBaseLevel,
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics& aFontMetrics,
+ Mode aMode, nscoord aX, nscoord aY, nsBidiPositionResolve* aPosResolve,
+ int32_t aPosResolveCount, nscoord* aWidth) {
+ nsIRenderingContextBidiProcessor processor(&aRenderingContext,
+ aTextRunConstructionDrawTarget,
+ &aFontMetrics, nsPoint(aX, aY));
+ nsAutoString text(aText, aLength);
+ text.ReplaceChar(kSeparators, ' ');
+ return ProcessText(text.BeginReading(), text.Length(), aBaseLevel,
+ aPresContext, processor, aMode, aPosResolve,
+ aPosResolveCount, aWidth, aPresContext->BidiEngine());
+}
+
+/* static */
+BidiEmbeddingLevel nsBidiPresUtils::BidiLevelFromStyle(
+ ComputedStyle* aComputedStyle) {
+ if (aComputedStyle->StyleTextReset()->mUnicodeBidi ==
+ StyleUnicodeBidi::Plaintext) {
+ return BidiEmbeddingLevel::DefaultLTR();
+ }
+
+ if (aComputedStyle->StyleVisibility()->mDirection == StyleDirection::Rtl) {
+ return BidiEmbeddingLevel::RTL();
+ }
+
+ return BidiEmbeddingLevel::LTR();
+}