diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
commit | 9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /layout/base/nsBidiPresUtils.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/base/nsBidiPresUtils.cpp')
-rw-r--r-- | layout/base/nsBidiPresUtils.cpp | 2492 |
1 files changed, 2492 insertions, 0 deletions
diff --git a/layout/base/nsBidiPresUtils.cpp b/layout/base/nsBidiPresUtils.cpp new file mode 100644 index 0000000000..0bd480a0aa --- /dev/null +++ b/layout/base/nsBidiPresUtils.cpp @@ -0,0 +1,2492 @@ +/* -*- 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->IsFrameOfType(nsIFrame::eBidiInlineContainer) && + 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->IsFrameOfType(nsIFrame::eBidiInlineContainer) || + 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). + 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->IsFrameOfType(nsIFrame::eBidiInlineContainer) || + 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) { + // 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(); +} |