/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "mozilla/IntegerRange.h" #include "nsAutoPtr.h" #include "nsBidiPresUtils.h" #include "nsFontMetrics.h" #include "nsGkAtoms.h" #include "nsPresContext.h" #include "nsRenderingContext.h" #include "nsBidiUtils.h" #include "nsCSSFrameConstructor.h" #include "nsContainerFrame.h" #include "nsInlineFrame.h" #include "nsPlaceholderFrame.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 #undef NOISY_BIDI #undef REALLY_NOISY_BIDI using namespace mozilla; 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; static const char16_t kSeparators[] = { // All characters with Bidi type Segment Separator or Block Separator 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) static bool IsIsolateControl(char16_t aChar) { return aChar == kLRI || aChar == kRLI || aChar == kFSI; } // Given a style context, 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(nsStyleContext* aStyleContext) { const nsStyleVisibility* vis = aStyleContext->StyleVisibility(); if ((vis->mWritingMode == NS_STYLE_WRITING_MODE_VERTICAL_RL || vis->mWritingMode == NS_STYLE_WRITING_MODE_VERTICAL_LR) && vis->mTextOrientation == NS_STYLE_TEXT_ORIENTATION_UPRIGHT) { return kLRO; } const nsStyleTextReset* text = aStyleContext->StyleTextReset(); if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_BIDI_OVERRIDE) { return NS_STYLE_DIRECTION_RTL == vis->mDirection ? kRLO : kLRO; } return 0; } // Given a style context, 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(nsStyleContext* aStyleContext) { const nsStyleVisibility* vis = aStyleContext->StyleVisibility(); const nsStyleTextReset* text = aStyleContext->StyleTextReset(); if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_EMBED) { return NS_STYLE_DIRECTION_RTL == vis->mDirection ? kRLE : kLRE; } if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_ISOLATE) { if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_BIDI_OVERRIDE) { // isolate-override return kFSI; } // element already has its directionality set from content so // we never need to return kFSI. return NS_STYLE_DIRECTION_RTL == vis->mDirection ? kRLI : kLRI; } if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) { return kFSI; } return 0; } struct BidiParagraphData { nsString mBuffer; AutoTArray mEmbeddingStack; nsTArray mLogicalFrames; nsTArray mLinePerFrame; nsDataHashtable mContentToFrameIndex; bool mIsVisual; nsBidiLevel mParaLevel; nsIContent* mPrevContent; nsAutoPtr mBidiEngine; nsIFrame* mPrevFrame; void Init(nsBlockFrame *aBlockFrame) { mBidiEngine = new nsBidi(); mPrevContent = nullptr; mParaLevel = nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame->StyleContext()); mIsVisual = aBlockFrame->PresContext()->IsVisualMode(); 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->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) || content->IsXULElement()) { mIsVisual = false; break; } } } } nsresult SetPara() { return mBidiEngine->SetPara(mBuffer.get(), BufferLength(), mParaLevel); } /** * mParaLevel can be NSBIDI_DEFAULT_LTR as well as NSBIDI_LTR or NSBIDI_RTL. * GetParaLevel() returns the actual (resolved) paragraph level which is * always either NSBIDI_LTR or NSBIDI_RTL */ nsBidiLevel GetParaLevel() { nsBidiLevel paraLevel = mParaLevel; if (IS_DEFAULT_LEVEL(paraLevel)) { mBidiEngine->GetParaLevel(¶Level); } return paraLevel; } nsBidiDirection GetDirection() { nsBidiDirection dir; mBidiEngine->GetDirection(&dir); return dir; } nsresult CountRuns(int32_t *runCount){ return mBidiEngine->CountRuns(runCount); } nsresult GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit, nsBidiLevel* aLevel) { nsresult rv = mBidiEngine->GetLogicalRun(aLogicalStart, aLogicalLimit, aLevel); if (mIsVisual || NS_FAILED(rv)) *aLevel = GetParaLevel(); return rv; } void ResetData() { mLogicalFrames.Clear(); mLinePerFrame.Clear(); mContentToFrameIndex.Clear(); mBuffer.SetLength(0); mPrevContent = nullptr; for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) { mBuffer.Append(mEmbeddingStack[i]); mLogicalFrames.AppendElement(NS_BIDI_CONTROL_FRAME); mLinePerFrame.AppendElement((nsLineBox*)nullptr); } } void AppendFrame(nsIFrame* aFrame, nsBlockInFlowLineIterator* aLineIter, nsIContent* aContent = nullptr) { if (aContent) { mContentToFrameIndex.Put(aContent, FrameCount()); } mLogicalFrames.AppendElement(aFrame); AdvanceLineIteratorToFrame(aFrame, aLineIter, mPrevFrame); mLinePerFrame.AppendElement(aLineIter->GetLine().get()); } void AdvanceAndAppendFrame(nsIFrame** aFrame, nsBlockInFlowLineIterator* 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) { int32_t index = 0; mContentToFrameIndex.Get(aContent, &index); return index; } int32_t FrameCount(){ return mLogicalFrames.Length(); } int32_t BufferLength(){ return mBuffer.Length(); } nsIFrame* FrameAt(int32_t aIndex){ return mLogicalFrames[aIndex]; } nsLineBox* GetLineForFrameAt(int32_t aIndex){ return mLinePerFrame[aIndex]; } void AppendUnichar(char16_t aCh){ mBuffer.Append(aCh); } void AppendString(const nsDependentSubstring& aString){ mBuffer.Append(aString); } void AppendControlChar(char16_t aCh) { mLogicalFrames.AppendElement(NS_BIDI_CONTROL_FRAME); mLinePerFrame.AppendElement((nsLineBox*)nullptr); 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.TruncateLength(mEmbeddingStack.Length() - 1); } void ClearBidiControls() { for (char16_t c : Reversed(mEmbeddingStack)) { AppendPopChar(c); } } static bool IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter, nsIFrame* aPrevFrame, nsIFrame* aFrame) { 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 void AdvanceLineIteratorToFrame(nsIFrame* aFrame, nsBlockInFlowLineIterator* aLineIter, nsIFrame*& aPrevFrame) { // Advance aLine to the line containing aFrame nsIFrame* child = aFrame; nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(child); while (parent && !nsLayoutUtils::GetAsBlock(parent)) { child = parent; parent = nsLayoutUtils::GetParentOrPlaceholderFor(child); } NS_ASSERTION (parent, "aFrame is not a descendent of aBlockFrame"); while (!IsFrameInCurrentLine(aLineIter, aPrevFrame, child)) { #ifdef DEBUG bool hasNext = #endif aLineIter->Next(); NS_ASSERTION(hasNext, "Can't find frame in lines!"); aPrevFrame = nullptr; } aPrevFrame = child; } }; struct BidiLineData { nsTArray mLogicalFrames; nsTArray mVisualFrames; nsTArray mIndexMap; AutoTArray 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 */ mLogicalFrames.Clear(); bool isReordered = false; bool hasRTLFrames = false; bool hasVirtualControls = false; auto appendFrame = [&](nsIFrame* frame, nsBidiLevel level) { mLogicalFrames.AppendElement(frame); mLevels.AppendElement(level); mIndexMap.AppendElement(0); if (IS_LEVEL_RTL(level)) { 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 nsBidi::ReorderVisual(mLevels.Elements(), FrameCount(), mIndexMap.Elements()); // Strip virtual frames if (hasVirtualControls) { auto originalCount = mLogicalFrames.Length(); nsTArray realFrameMap(originalCount); size_t count = 0; for (auto i : MakeRange(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(){ return mLogicalFrames.Length(); } nsIFrame* LogicalFrameAt(int32_t aIndex){ return mLogicalFrames[aIndex]; } nsIFrame* VisualFrameAt(int32_t aIndex){ return mVisualFrames[aIndex]; } }; #ifdef DEBUG extern "C" { void MOZ_EXPORT DumpFrameArray(const nsTArray& 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) { // Bidi inline containers should be split, unless they're line frames. nsIAtom* frameType = aFrame->GetType(); return (aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer) && frameType != nsGkAtoms::lineFrame) || frameType == nsGkAtoms::textFrame; } // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)? static bool IsBidiLeaf(nsIFrame* aFrame) { nsIFrame* kid = aFrame->PrincipalChildList().FirstChild(); return !kid || !aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer); } /** * 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 nsresult SplitInlineAncestors(nsContainerFrame* aParent, nsIFrame* aFrame) { nsPresContext* presContext = aParent->PresContext(); nsIPresShell* presShell = presContext->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(presShell->FrameConstructor()-> CreateContinuingFrame(presContext, parent, grandparent, false)); nsFrameList tail = parent->StealFramesAfter(frame); // Reparent views as necessary nsresult rv; rv = nsContainerFrame::ReparentFrameViewList(tail, parent, newParent); if (NS_FAILED(rv)) { return rv; } // The parent's continuation adopts the siblings after the split. newParent->InsertFrames(nsIFrame::kNoReflowPrincipalList, nullptr, tail); // The list name kNoReflowPrincipalList would indicate we don't want reflow nsFrameList temp(newParent, newParent); grandparent->InsertFrames(nsIFrame::kNoReflowPrincipalList, parent, temp); } frame = parent; parent = grandparent; } return NS_OK; } 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; do { 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(); } while (frame && IsBidiSplittable(frame)); } static nsresult CreateContinuation(nsIFrame* aFrame, nsIFrame** aNewFrame, bool aIsFluid) { NS_PRECONDITION(aNewFrame, "null OUT ptr"); NS_PRECONDITION(aFrame, "null ptr"); *aNewFrame = nullptr; nsPresContext *presContext = aFrame->PresContext(); nsIPresShell *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"); nsresult rv = NS_OK; // 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->GetType() == nsGkAtoms::letterFrame && parent->IsFloating()) { nsFirstLetterFrame* letterFrame = do_QueryFrame(parent); rv = letterFrame->CreateContinuationForFloatingParent(presContext, aFrame, aNewFrame, aIsFluid); return rv; } *aNewFrame = presShell->FrameConstructor()-> CreateContinuingFrame(presContext, aFrame, parent, aIsFluid); // The list name kNoReflowPrincipalList would indicate we don't want reflow // XXXbz this needs higher-level framelist love nsFrameList temp(*aNewFrame, *aNewFrame); parent->InsertFrames(nsIFrame::kNoReflowPrincipalList, aFrame, temp); if (!aIsFluid) { // Split inline ancestor frames rv = SplitInlineAncestors(parent, aFrame); if (NS_FAILED(rv)) { return rv; } } return NS_OK; } /* * 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, , or * elements are represented by the corresponding Unicode control characters. *
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; bpd.Init(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->StyleContext()); if (ch != 0) { bpd.PushBidiControl(ch); } for (nsBlockFrame* block = aBlockFrame; block; block = static_cast(block->GetNextContinuation())) { block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); nsBlockInFlowLineIterator lineIter(block, block->begin_lines()); bpd.mPrevFrame = nullptr; TraverseFrames(aBlockFrame, &lineIter, block->PrincipalChildList().FirstChild(), &bpd); // XXX what about overflow lines? } if (ch != 0) { bpd.PopBidiControl(ch); } return ResolveParagraph(aBlockFrame, &bpd); } nsresult nsBidiPresUtils::ResolveParagraph(nsBlockFrame* aBlockFrame, BidiParagraphData* aBpd) { nsPresContext *presContext = aBlockFrame->PresContext(); if (aBpd->BufferLength() < 1) { return NS_OK; } aBpd->mBuffer.ReplaceChar(kSeparators, kSpace); int32_t runCount; nsresult rv = aBpd->SetPara(); NS_ENSURE_SUCCESS(rv, rv); nsBidiLevel embeddingLevel = aBpd->GetParaLevel(); 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; nsIContent* content = nullptr; int32_t contentTextLength = 0; FramePropertyTable *propTable = presContext->PropertyTable(); nsLineBox* currentLine = nullptr; #ifdef DEBUG #ifdef NOISY_BIDI printf("Before Resolve(), aBlockFrame=0x%p, mBuffer='%s', frameCount=%d, runCount=%d\n", (void*)aBlockFrame, NS_ConvertUTF16toUTF8(aBpd->mBuffer).get(), frameCount, runCount); #ifdef REALLY_NOISY_BIDI printf(" block frame tree=:\n"); aBlockFrame->List(stdout, 0); #endif #endif #endif if (runCount == 1 && frameCount == 1 && aBpd->GetDirection() == NSBIDI_LTR && aBpd->GetParaLevel() == 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 = nsBidi::GetBidiData(frame); 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; } } } nsIFrame* lastRealFrame = nullptr; nsBidiLevel lastEmbedingLevel = kBidiLevelNone; nsBidiLevel precedingControl = kBidiLevelNone; auto storeBidiDataToFrame = [&]() { FrameBidiData bidiData; bidiData.embeddingLevel = embeddingLevel; bidiData.baseLevel = aBpd->GetParaLevel(); // 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 >= lastEmbedingLevel) { bidiData.precedingControl = kBidiLevelNone; } else { bidiData.precedingControl = precedingControl; } precedingControl = kBidiLevelNone; lastEmbedingLevel = embeddingLevel; propTable->Set(frame, nsBidi::BidiDataProperty(), bidiData); }; for (; ;) { if (fragmentLength <= 0) { // Get the next frame from mLogicalFrames if (++frameIndex >= frameCount) { break; } frame = aBpd->FrameAt(frameIndex); if (frame == NS_BIDI_CONTROL_FRAME || nsGkAtoms::textFrame != frame->GetType()) { /* * 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 { currentLine = aBpd->GetLineForFrameAt(frameIndex); content = frame->GetContent(); if (!content) { rv = NS_OK; break; } contentTextLength = content->TextLength(); if (contentTextLength == 0) { frame->AdjustOffsetsForBidi(0, 0); // Set the base level and embedding level of the current run even // on an empty frame. Otherwise frame reordering will not be correct. storeBidiDataToFrame(); continue; } int32_t start, end; frame->GetOffsets(start, end); 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) { break; } int32_t lineOffset = logicalLimit; if (NS_FAILED(aBpd->GetLogicalRun( lineOffset, &logicalLimit, &embeddingLevel) ) ) { break; } 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 ( (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; rv = EnsureBidiContinuation(frame, &nextBidi, contentOffset, runEnd); if (NS_FAILED(rv)) { break; } nextBidi->AdjustOffsetsForBidi(runEnd, contentOffset + fragmentLength); frame = nextBidi; contentOffset = runEnd; } // 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; frame = aBpd->FrameAt(frameIndex); } } 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 spliting properly even // if a run ends after a virtual bidi control frame. if (frame != NS_BIDI_CONTROL_FRAME) { lastRealFrame = frame; } if (lastRealFrame && fragmentLength <= 0) { // If the frame is at the end of a run, and this is not the end of our // paragrah, 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->GetNextInFlow()) { if (numRun + 1 < runCount) { nsIFrame* child = lastRealFrame; 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)) { SplitInlineAncestors(parent, child); } } } 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"); aBlockFrame->List(stdout, 0); printf("===\n"); #endif #endif return rv; } void nsBidiPresUtils::TraverseFrames(nsBlockFrame* aBlockFrame, nsBlockInFlowLineIterator* aLineIter, nsIFrame* aCurrentFrame, BidiParagraphData* aBpd) { if (!aCurrentFrame) return; #ifdef DEBUG nsBlockFrame* initialLineContainer = aLineIter->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(); bool isLastFrame = !childFrame->GetNextContinuation(); bool isFirstFrame = !childFrame->GetPrevContinuation(); // 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 (nsGkAtoms::placeholderFrame == childFrame->GetType()) { nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame); if (realFrame->GetType() == nsGkAtoms::letterFrame) { frame = realFrame; } } char16_t controlChar = 0; char16_t overrideChar = 0; if (frame->IsFrameOfType(nsIFrame::eBidiInlineContainer)) { if (!(frame->GetStateBits() & NS_FRAME_FIRST_REFLOW)) { nsContainerFrame* c = static_cast(frame); MOZ_ASSERT(c == do_QueryFrame(frame), "eBidiInlineContainer must be a nsContainerFrame subclass"); c->DrainSelfOverflowList(); } controlChar = GetBidiControl(frame->StyleContext()); overrideChar = GetBidiOverride(frame->StyleContext()); // 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, aLineIter, content); // Append the content of the frame to the paragraph buffer nsIAtom* frameType = frame->GetType(); if (nsGkAtoms::textFrame == frameType) { if (content != aBpd->mPrevContent) { aBpd->mPrevContent = content; if (!frame->StyleText()->NewlineIsSignificant( static_cast(frame))) { content->AppendTextTo(aBpd->mBuffer); } else { /* * For preformatted text we have to do bidi resolution on each line * separately. */ nsAutoString text; content->AppendTextTo(text); nsIFrame* next; do { next = nullptr; int32_t start, end; frame->GetOffsets(start, end); 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, aLineIter, &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, aLineIter, &nextSibling); NS_ASSERTION(frame, "Premature end of continuation chain"); frame->GetOffsets(start, end); 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(frame); textFrame->SetLength(endLine - start, nullptr); if (!next) { // If the frame has no next in flow, create one. CreateContinuation(frame, &next, true); createdContinuation = true; } // Mark the line before the newline as dirty. aBpd->GetLineForFrameAt(aBpd->FrameCount() - 1)->MarkDirty(); } ResolveParagraphWithinBlock(aBlockFrame, aBpd); if (!nextSibling && !createdContinuation) { break; } else if (next) { frame = next; aBpd->AppendFrame(frame, aLineIter); // Mark the line after the newline as dirty. aBpd->GetLineForFrameAt(aBpd->FrameCount() - 1)->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 (nsGkAtoms::brFrame == frameType) { // break frame -- append line separator aBpd->AppendUnichar(kLineSeparator); ResolveParagraphWithinBlock(aBlockFrame, aBpd); } else { // other frame type -- see the Unicode Bidi Algorithm: // "...inline objects (such as graphics) are treated as if they are ... // U+FFFC" // , 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(aBlockFrame, aBpd); } } } else { // For a non-leaf frame, recurse into TraverseFrames nsIFrame* kid = frame->PrincipalChildList().FirstChild(); MOZ_ASSERT(!frame->GetChildList(nsIFrame::kOverflowList).FirstChild(), "should have drained the overflow list above"); if (kid) { TraverseFrames(aBlockFrame, aLineIter, 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 == aLineIter->GetContainer()); } void nsBidiPresUtils::ResolveParagraphWithinBlock(nsBlockFrame* aBlockFrame, BidiParagraphData* aBpd) { aBpd->ClearBidiControls(); ResolveParagraph(aBlockFrame, 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->GetType() == nsGkAtoms::lineFrame) { // 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; } 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->GetType() == nsGkAtoms::letterFrame) ? realFrame : firstChild; } return firstLeaf; } FrameBidiData nsBidiPresUtils::GetFrameBidiData(nsIFrame* aFrame) { return nsBidi::GetBidiData(GetFirstLeaf(aFrame)); } nsBidiLevel nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) { return nsBidi::GetEmbeddingLevel(GetFirstLeaf(aFrame)); } nsBidiLevel nsBidiPresUtils::GetFrameBaseLevel(nsIFrame* aFrame) { nsIFrame* firstLeaf = aFrame; while (!IsBidiLeaf(firstLeaf)) { firstLeaf = firstLeaf->PrincipalChildList().FirstChild(); } return nsBidi::GetBaseLevel(firstLeaf); } void nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame, const 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->GetEntry(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->GetEntry(frame)); frame = frame->GetPrevContinuation()) { frameState->mFrameCount++; contState->mFirstVisualFrame = aFrame; } frameState->mHasContOnPrevLines = (frame != nullptr); // Traverse continuation chain forward for (frame = aFrame->GetNextContinuation(); frame && (contState = aContinuationStates->GetEntry(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->GetEntry(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->GetStateBits() & 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. // XXX It currently doesn't work properly because we do not // resolve frames inside ruby content frames. nscoord isize = ReorderFrames(childList.FirstChild(), childList.GetLength(), aFrameWM, aFrame->GetSize(), aBorderPadding.IStart(aFrameWM)); isize += aBorderPadding.IEnd(aFrameWM); if (aFrame->StyleText()->mRubyAlign == NS_STYLE_RUBY_ALIGN_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, const nsContinuationStates* aContinuationStates, const WritingMode aContainerWM, const LogicalMargin& aBorderPadding) { nsIAtom* frameType = aFrame->GetType(); MOZ_ASSERT(RubyUtils::IsRubyBox(frameType)); nscoord icoord = 0; WritingMode frameWM = aFrame->GetWritingMode(); bool isLTR = frameWM.IsBidiLTR(); nsSize frameSize = aFrame->GetSize(); if (frameType == nsGkAtoms::rubyFrame) { icoord += aBorderPadding.IStart(frameWM); // Reposition ruby segments in a ruby container for (RubySegmentEnumerator e(static_cast(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 == nsGkAtoms::rubyBaseContainerFrame) { // Reposition ruby columns in a ruby segment auto rbc = static_cast(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 == nsGkAtoms::rubyBaseFrame || frameType == nsGkAtoms::rubyTextFrame) { 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, const 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)) { 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 (nsFrameList::Enumerator e(aFrame->PrincipalChildList()); !e.AtEnd(); e.Next()) { icoord += RepositionFrame(e.get(), aIsEvenLevel, icoord, aContinuationStates, frameWM, reverseDir, frameSize); } icoord += reverseDir ? borderPadding.IStart(frameWM) : borderPadding.IEnd(frameWM); } else if (RubyUtils::IsRubyBox(aFrame->GetType())) { icoord += RepositionRubyFrame(aFrame, aContinuationStates, aContainerWM, borderPadding); } else { icoord += frameWM.IsOrthogonalTo(aContainerWM) ? aFrame->BSize() : frameISize; } // 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) { nsFrameContinuationState* state = aContinuationStates->PutEntry(aFrame); state->mFirstVisualFrame = nullptr; state->mFrameCount = 0; if (!IsBidiLeaf(aFrame) || RubyUtils::IsRubyBox(aFrame->GetType())) { // 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, !(IS_LEVEL_RTL(aBld->mLevels[aBld->mIndexMap[index]])), 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 nsresult nsBidiPresUtils::EnsureBidiContinuation(nsIFrame* aFrame, nsIFrame** aNewFrame, int32_t aStart, int32_t aEnd) { NS_PRECONDITION(aNewFrame, "null OUT ptr"); NS_PRECONDITION(aFrame, "aFrame is null"); aFrame->AdjustOffsetsForBidi(aStart, aEnd); return CreateContinuation(aFrame, aNewFrame, false); } void nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData *aBpd, nsIFrame* aFrame, int32_t aFirstIndex, int32_t aLastIndex) { FrameBidiData bidiData = nsBidi::GetBidiData(aFrame); 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->Properties().Set(nsBidi::BidiDataProperty(), bidiData); frame->AddStateBits(NS_FRAME_IS_BIDI); while (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, nsCharType aCharType) { 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 (aCharType) { case eCharType_EuropeanNumber: HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC); break; case eCharType_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])) ) || (eCharType_ArabicNumber == aCharType) ) HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI); else if (eCharType_EuropeanNumber == aCharType) 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])) ) || (eCharType_ArabicNumber == aCharType) ) HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_PERSIAN); else if (eCharType_EuropeanNumber == aCharType) 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; } #if 0 // XXX: for the future use ??? void RemoveDiacritics(char16_t* aText, int32_t& aTextLength) { if (aText && (aTextLength > 0) ) { int32_t offset = 0; for (int32_t i = 0; i < aTextLength && aText[i]; i++) { if (IS_BIDI_DIACRITIC(aText[i]) ) { ++offset; continue; } aText[i - offset] = aText[i]; } aTextLength = i - offset; aText[aTextLength] = 0; } } #endif void nsBidiPresUtils::CalculateCharType(nsBidi* aBidiEngine, const char16_t* aText, int32_t& aOffset, int32_t aCharTypeLimit, int32_t& aRunLimit, int32_t& aRunLength, int32_t& aRunCount, uint8_t& aCharType, uint8_t& aPrevCharType) { bool strongTypeFound = false; int32_t offset; nsCharType charType; aCharType = eCharType_OtherNeutral; for (offset = aOffset; offset < aCharTypeLimit; offset++) { // 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.) if (IS_HEBREW_CHAR(aText[offset]) ) { charType = eCharType_RightToLeft; } else if (IS_ARABIC_ALPHABETIC(aText[offset]) ) { charType = eCharType_RightToLeftArabic; } else { aBidiEngine->GetCharTypeAt(offset, &charType); } if (!CHARTYPE_IS_WEAK(charType) ) { if (strongTypeFound && (charType != aPrevCharType) && (CHARTYPE_IS_RTL(charType) || CHARTYPE_IS_RTL(aPrevCharType) ) ) { // 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 ( (eCharType_RightToLeftArabic == aPrevCharType || eCharType_ArabicNumber == aPrevCharType) && eCharType_EuropeanNumber == charType) { charType = eCharType_ArabicNumber; } // Set PrevCharType to the last strong type in this frame // (for correct numeric shaping) aPrevCharType = charType; strongTypeFound = true; aCharType = charType; } } aOffset = offset; } nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, int32_t aLength, nsBidiLevel aBaseLevel, nsPresContext* aPresContext, BidiProcessor& aprocessor, Mode aMode, nsBidiPositionResolve* aPosResolve, int32_t aPosResolveCount, nscoord* aWidth, nsBidi* aBidiEngine) { NS_ASSERTION((aPosResolve == nullptr) != (aPosResolveCount > 0), "Incorrect aPosResolve / aPosResolveCount arguments"); int32_t runCount; nsAutoString textBuffer(aText, aLength); textBuffer.ReplaceChar(kSeparators, kSpace); const char16_t* text = textBuffer.get(); nsresult rv = aBidiEngine->SetPara(text, aLength, aBaseLevel); if (NS_FAILED(rv)) return rv; rv = aBidiEngine->CountRuns(&runCount); if (NS_FAILED(rv)) return rv; nscoord xOffset = 0; nscoord width, xEndRun = 0; nscoord totalWidth = 0; int32_t i, start, limit, length; uint32_t visualStart = 0; uint8_t charType; uint8_t prevType = eCharType_LeftToRight; for(int nPosResolve=0; nPosResolve < aPosResolveCount; ++nPosResolve) { aPosResolve[nPosResolve].visualIndex = kNotFound; aPosResolve[nPosResolve].visualLeftTwips = kNotFound; aPosResolve[nPosResolve].visualWidth = kNotFound; } for (i = 0; i < runCount; i++) { nsBidiDirection dir; rv = aBidiEngine->GetVisualRun(i, &start, &length, &dir); if (NS_FAILED(rv)) return rv; nsBidiLevel level; rv = aBidiEngine->GetLogicalRun(start, &limit, &level); if (NS_FAILED(rv)) return rv; dir = DIRECTION_FROM_LEVEL(level); int32_t subRunLength = limit - start; int32_t lineOffset = start; int32_t typeLimit = std::min(limit, 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 == NSBIDI_RTL) { aprocessor.SetText(text + start, subRunLength, dir); width = aprocessor.GetWidth(); xOffset += width; xEndRun = xOffset; } while (subRunCount > 0) { // CalculateCharType can increment subRunCount if the run // contains mixed character types CalculateCharType(aBidiEngine, text, lineOffset, typeLimit, subRunLimit, subRunLength, subRunCount, charType, prevType); nsAutoString runVisualText; runVisualText.Assign(text + start, subRunLength); if (int32_t(runVisualText.Length()) < subRunLength) return NS_ERROR_OUT_OF_MEMORY; FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), subRunLength, (nsCharType)charType); aprocessor.SetText(runVisualText.get(), subRunLength, dir); width = aprocessor.GetWidth(); totalWidth += width; if (dir == NSBIDI_RTL) { xOffset -= width; } if (aMode == MODE_DRAW) { aprocessor.DrawText(xOffset, width); } /* * The caller may request to calculate the visual position of one * or more characters. */ for(int nPosResolve=0; nPosResolvevisualLeftTwips != 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 == NSBIDI_RTL) { // One day, son, this could all be replaced with mBidiEngine.GetVisualIndex ... posResolve->visualIndex = visualStart + (subRunLength - (posResolve->logicalIndex + 1 - start)); // Skipping to the "left part". visualLeftPart = text + 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 = text + 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 == NSBIDI_LTR) { xOffset += width; } --subRunCount; start = lineOffset; subRunLimit = typeLimit; subRunLength = typeLimit - lineOffset; } // while if (dir == NSBIDI_RTL) { xOffset = xEndRun; } visualStart += length; } // for if (aWidth) { *aWidth = totalWidth; } return NS_OK; } class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final : public nsBidiPresUtils::BidiProcessor { public: typedef mozilla::gfx::DrawTarget DrawTarget; nsIRenderingContextBidiProcessor(nsRenderingContext* aCtx, DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics* aFontMetrics, const nsPoint& aPt) : mCtx(aCtx) , mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget) , mFontMetrics(aFontMetrics) , mPt(aPt) {} ~nsIRenderingContextBidiProcessor() { mFontMetrics->SetTextRunRTL(false); } virtual void SetText(const char16_t* aText, int32_t aLength, nsBidiDirection aDirection) override { mFontMetrics->SetTextRunRTL(aDirection==NSBIDI_RTL); mText = aText; mLength = aLength; } virtual nscoord GetWidth() override { return nsLayoutUtils::AppUnitWidthOfString(mText, mLength, *mFontMetrics, mTextRunConstructionDrawTarget); } virtual void DrawText(nscoord aIOffset, nscoord) 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: nsRenderingContext* mCtx; DrawTarget* mTextRunConstructionDrawTarget; nsFontMetrics* mFontMetrics; nsPoint mPt; const char16_t* mText; int32_t mLength; }; nsresult nsBidiPresUtils::ProcessTextForRenderingContext(const char16_t* aText, int32_t aLength, nsBidiLevel aBaseLevel, nsPresContext* aPresContext, nsRenderingContext& 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)); nsBidi bidiEngine; return ProcessText(aText, aLength, aBaseLevel, aPresContext, processor, aMode, aPosResolve, aPosResolveCount, aWidth, &bidiEngine); } /* static */ nsBidiLevel nsBidiPresUtils::BidiLevelFromStyle(nsStyleContext* aStyleContext) { if (aStyleContext->StyleTextReset()->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) { return NSBIDI_DEFAULT_LTR; } if (aStyleContext->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { return NSBIDI_RTL; } return NSBIDI_LTR; }