diff -r 000000000000 -r 6474c204b198 layout/base/nsBidiPresUtils.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layout/base/nsBidiPresUtils.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,2246 @@ +/* -*- 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 "nsBidiPresUtils.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 + +#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 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) + +struct BidiParagraphData { + nsString mBuffer; + nsAutoTArray mEmbeddingStack; + nsTArray mLogicalFrames; + nsTArray mLinePerFrame; + nsDataHashtable mContentToFrameIndex; + bool mIsVisual; + bool mReset; + nsBidiLevel mParaLevel; + nsIContent* mPrevContent; + nsAutoPtr mBidiEngine; + nsIFrame* mPrevFrame; + nsAutoPtr mSubParagraph; + uint8_t mParagraphDepth; + + void Init(nsBlockFrame *aBlockFrame) + { + mBidiEngine = new nsBidi(); + mPrevContent = nullptr; + mParagraphDepth = 0; + + 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->IsXUL()) { + mIsVisual = false; + break; + } + } + } + } + + BidiParagraphData* GetSubParagraph() + { + if (!mSubParagraph) { + mSubParagraph = new BidiParagraphData(); + mSubParagraph->Init(this); + } + + return mSubParagraph; + } + + // Initialise a sub-paragraph from its containing paragraph + void Init(BidiParagraphData *aBpd) + { + mBidiEngine = new nsBidi(); + mPrevContent = nullptr; + mIsVisual = aBpd->mIsVisual; + mReset = false; + } + + void Reset(nsIFrame* aBDIFrame, BidiParagraphData *aBpd) + { + mReset = true; + mLogicalFrames.Clear(); + mLinePerFrame.Clear(); + mContentToFrameIndex.Clear(); + mBuffer.SetLength(0); + mPrevFrame = aBpd->mPrevFrame; + mParagraphDepth = aBpd->mParagraphDepth + 1; + + const nsStyleTextReset* text = aBDIFrame->StyleTextReset(); + bool isRTL = (NS_STYLE_DIRECTION_RTL == + aBDIFrame->StyleVisibility()->mDirection); + + if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) { + mParaLevel = NSBIDI_DEFAULT_LTR; + } else { + mParaLevel = mParagraphDepth * 2; + if (isRTL) ++mParaLevel; + } + + if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_OVERRIDE) { + PushBidiControl(isRTL ? kRLO : kLRO); + } + } + + void EmptyBuffer() + { + mBuffer.SetLength(0); + } + + nsresult SetPara() + { + return mBidiEngine->SetPara(mBuffer.get(), BufferLength(), + mParaLevel, nullptr); + } + + /** + * 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 ResetForNewBlock() + { + for (BidiParagraphData* bpd = this; bpd; bpd = bpd->mSubParagraph) { + bpd->mPrevFrame = 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 PopBidiControl() + { + AppendControlChar(kPDF); + NS_ASSERTION(mEmbeddingStack.Length(), "embedding/override underflow"); + mEmbeddingStack.TruncateLength(mEmbeddingStack.Length() - 1); + } + + void ClearBidiControls() + { + for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) { + AppendControlChar(kPDF); + } + } + + 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; + nsAutoTArray 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; + + for (nsIFrame* frame = aFirstFrameOnLine; + frame && aNumFramesOnLine--; + frame = frame->GetNextSibling()) { + AppendFrame(frame); + uint8_t level = nsBidiPresUtils::GetFrameEmbeddingLevel(frame); + mLevels.AppendElement(level); + mIndexMap.AppendElement(0); + if (level & 1) { + hasRTLFrames = true; + } + } + + // Reorder the line + nsBidi::ReorderVisual(mLevels.Elements(), FrameCount(), + mIndexMap.Elements()); + + 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; + } + + void AppendFrame(nsIFrame* aFrame) + { + mLogicalFrames.AppendElement(aFrame); + } + + int32_t FrameCount(){ return mLogicalFrames.Length(); } + + nsIFrame* LogicalFrameAt(int32_t aIndex){ return mLogicalFrames[aIndex]; } + + nsIFrame* VisualFrameAt(int32_t aIndex){ return mVisualFrames[aIndex]; } +}; + +/* 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->GetFirstPrincipalChild(); + 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(nsIFrame* aParent, + nsIFrame* aFrame) +{ + nsPresContext *presContext = aParent->PresContext(); + nsIPresShell *presShell = presContext->PresShell(); + nsIFrame* frame = aFrame; + nsIFrame* parent = aParent; + nsIFrame* newParent; + + while (IsBidiSplittable(parent)) { + nsIFrame* 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 = presShell->FrameConstructor()-> + CreateContinuingFrame(presContext, parent, grandparent, false); + + nsContainerFrame* container = do_QueryFrame(parent); + nsFrameList tail = container->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. + rv = newParent->InsertFrames(nsIFrame::kNoReflowPrincipalList, nullptr, tail); + if (NS_FAILED(rv)) { + return rv; + } + + // The list name kNoReflowPrincipalList would indicate we don't want reflow + nsFrameList temp(newParent, newParent); + rv = grandparent->InsertFrames(nsIFrame::kNoReflowPrincipalList, parent, temp); + if (NS_FAILED(rv)) { + return rv; + } + } + + 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) { + // Don't join frames if they come from different paragraph depths (i.e. + // one is bidi isolated relative to the other + if (nsBidiPresUtils::GetParagraphDepth(frame) == + nsBidiPresUtils::GetParagraphDepth(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"); + + nsIFrame* 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); + rv = parent->InsertFrames(nsIFrame::kNoReflowPrincipalList, aFrame, temp); + if (NS_FAILED(rv)) { + return rv; + } + + 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 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. + const nsStyleTextReset* text = aBlockFrame->StyleTextReset(); + char16_t ch = 0; + if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_OVERRIDE) { + const nsStyleVisibility* vis = aBlockFrame->StyleVisibility(); + if (NS_STYLE_DIRECTION_RTL == vis->mDirection) { + ch = kRLO; + } + else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) { + ch = kLRO; + } + 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.ResetForNewBlock(); + TraverseFrames(aBlockFrame, &lineIter, block->GetFirstPrincipalChild(), &bpd); + // XXX what about overflow lines? + } + + if (ch != 0) { + bpd.PopBidiControl(); + } + + BidiParagraphData* subParagraph = bpd.GetSubParagraph(); + if (subParagraph->BufferLength()) { + ResolveParagraph(aBlockFrame, subParagraph); + subParagraph->EmptyBuffer(); + } + 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); + + uint8_t 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 lineOffset = 0; // the start of the current run + 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->mParagraphDepth == 0 && 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 && + !frame->Properties().Get(nsIFrame::EmbeddingLevelProperty()) && + !frame->Properties().Get(nsIFrame::BaseLevelProperty())) { +#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* firstFrame = nullptr; + nsIFrame* lastFrame = nullptr; + + 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 { + if (!firstFrame) { + firstFrame = frame; + } + lastFrame = frame; + 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. + propTable->Set(frame, nsIFrame::EmbeddingLevelProperty(), + NS_INT32_TO_PTR(embeddingLevel)); + propTable->Set(frame, nsIFrame::BaseLevelProperty(), + NS_INT32_TO_PTR(aBpd->GetParaLevel())); + propTable->Set(frame, nsIFrame::ParagraphDepthProperty(), + NS_INT32_TO_PTR(aBpd->mParagraphDepth)); + 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; + } + lineOffset = logicalLimit; + if (NS_FAILED(aBpd->GetLogicalRun( + lineOffset, &logicalLimit, &embeddingLevel) ) ) { + break; + } + runLength = logicalLimit - lineOffset; + } // if (runLength <= 0) + + if (frame == NS_BIDI_CONTROL_FRAME) { + frame = nullptr; + ++lineOffset; + } + else { + propTable->Set(frame, nsIFrame::EmbeddingLevelProperty(), + NS_INT32_TO_PTR(embeddingLevel)); + propTable->Set(frame, nsIFrame::BaseLevelProperty(), + NS_INT32_TO_PTR(aBpd->GetParaLevel())); + propTable->Set(frame, nsIFrame::ParagraphDepthProperty(), + NS_INT32_TO_PTR(aBpd->mParagraphDepth)); + 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, frameIndex, + contentOffset, + runEnd); + if (NS_FAILED(rv)) { + break; + } + nextBidi->AdjustOffsetsForBidi(runEnd, + contentOffset + fragmentLength); + lastFrame = 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, lineOffset); + frameIndex = newIndex; + lastFrame = 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, lineOffset); + } + } 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); + currentLine->MarkDirty(); + } + } // isTextFrame + else { + ++lineOffset; + } + } // not bidi control frame + int32_t temp = runLength; + runLength -= fragmentLength; + fragmentLength -= temp; + + if (frame && 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 && !frame->GetNextInFlow()) { + if (numRun + 1 < runCount) { + nsIFrame* child = frame; + nsIFrame* parent = frame->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 { + // 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 + + if (aBpd->mParagraphDepth > 0) { + nsIFrame* child; + nsIFrame* parent; + if (firstFrame) { + child = firstFrame->GetParent(); + if (child) { + parent = child->GetParent(); + if (parent && IsBidiSplittable(parent)) { + nsIFrame* prev = child->GetPrevSibling(); + if (prev) { + SplitInlineAncestors(parent, prev); + } + } + } + } + if (lastFrame) { + child = lastFrame->GetParent(); + if (child) { + parent = child->GetParent(); + if (parent && IsBidiSplittable(parent)) { + SplitInlineAncestors(parent, child); + } + } + } + } + +#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 ch = 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(); + } + + const nsStyleVisibility* vis = frame->StyleVisibility(); + const nsStyleTextReset* text = frame->StyleTextReset(); + if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_OVERRIDE) { + if (NS_STYLE_DIRECTION_RTL == vis->mDirection) { + ch = kRLO; + } + else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) { + ch = kLRO; + } + } else if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_EMBED) { + if (NS_STYLE_DIRECTION_RTL == vis->mDirection) { + ch = kRLE; + } + else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) { + ch = kLRE; + } + } + + // Add a dummy frame pointer representing a bidi control code before the + // first frame of an element specifying embedding or override + if (ch != 0 && isFirstFrame) { + aBpd->PushBidiControl(ch); + } + } + + 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()) { + 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; + } + aBpd->GetLineForFrameAt(aBpd->FrameCount() - 1)->MarkDirty(); + } + ResolveParagraphWithinBlock(aBlockFrame, aBpd); + + if (!nextSibling && !createdContinuation) { + break; + } else if (next) { + frame = next; + aBpd->AppendFrame(frame, aLineIter); + } + + /* + * 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->IsHTML(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->GetFirstPrincipalChild(); + MOZ_ASSERT(!frame->GetFirstChild(nsIFrame::kOverflowList), + "should have drained the overflow list above"); + if (kid) { + const nsStyleTextReset* text = frame->StyleTextReset(); + if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_ISOLATE || + text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) { + // css "unicode-bidi: isolate" and html5 bdi: + // resolve the element as a separate paragraph + BidiParagraphData* subParagraph = aBpd->GetSubParagraph(); + + /* + * As at the beginning of the loop, it's important to check for + * next-continuations before handling the frame. If we do + * TraverseFrames and *then* do GetNextContinuation on the original + * first frame, it could return a bidi continuation that had only + * just been created, and we would skip doing bidi resolution on the + * last part of the sub-paragraph. + */ + bool isLastContinuation = !frame->GetNextContinuation(); + if (!frame->GetPrevContinuation() || !subParagraph->mReset) { + if (subParagraph->BufferLength()) { + ResolveParagraph(aBlockFrame, subParagraph); + } + subParagraph->Reset(frame, aBpd); + } + TraverseFrames(aBlockFrame, aLineIter, kid, subParagraph); + if (isLastContinuation) { + ResolveParagraph(aBlockFrame, subParagraph); + subParagraph->EmptyBuffer(); + } + + // Treat the element as a neutral character within its containing + // paragraph. + aBpd->AppendControlChar(kObjectSubstitute); + } else { + TraverseFrames(aBlockFrame, aLineIter, kid, aBpd); + } + } + } + + // If the element is attributed by dir, indicate direction pop (add PDF frame) + if (isLastFrame) { + if (ch) { + // Add a dummy frame pointer representing a bidi control code after the + // last frame of an element specifying embedding or override + aBpd->PopBidiControl(); + } + } + childFrame = nextSibling; + } while (childFrame); + + MOZ_ASSERT(initialLineContainer == aLineIter->GetContainer()); +} + +void +nsBidiPresUtils::ResolveParagraphWithinBlock(nsBlockFrame* aBlockFrame, + BidiParagraphData* aBpd) +{ + aBpd->ClearBidiControls(); + ResolveParagraph(aBlockFrame, aBpd); + aBpd->ResetData(); +} + +void +nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine, + int32_t aNumFramesOnLine, + WritingMode aLineWM, + nscoord& aLineWidth) +{ + // If this line consists of a line frame, reorder the line frame's children. + if (aFirstFrameOnLine->GetType() == nsGkAtoms::lineFrame) { + aFirstFrameOnLine = aFirstFrameOnLine->GetFirstPrincipalChild(); + if (!aFirstFrameOnLine) + return; + // 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); + RepositionInlineFrames(&bld, aFirstFrameOnLine, aLineWM, aLineWidth); +} + +nsIFrame* +nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame) +{ + nsIFrame* firstLeaf = aFrame; + while (!IsBidiLeaf(firstLeaf)) { + nsIFrame* firstChild = firstLeaf->GetFirstPrincipalChild(); + nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(firstChild); + firstLeaf = (realFrame->GetType() == nsGkAtoms::letterFrame) ? + realFrame : firstChild; + } + return firstLeaf; +} + +nsBidiLevel +nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) +{ + return NS_GET_EMBEDDING_LEVEL(nsBidiPresUtils::GetFirstLeaf(aFrame)); +} + +uint8_t +nsBidiPresUtils::GetParagraphDepth(nsIFrame* aFrame) +{ + return NS_GET_PARAGRAPH_DEPTH(nsBidiPresUtils::GetFirstLeaf(aFrame)); +} + + +nsBidiLevel +nsBidiPresUtils::GetFrameBaseLevel(nsIFrame* aFrame) +{ + nsIFrame* firstLeaf = aFrame; + while (!IsBidiLeaf(firstLeaf)) { + firstLeaf = firstLeaf->GetFirstPrincipalChild(); + } + return NS_GET_BASE_LEVEL(firstLeaf); +} + +void +nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame, + nsContinuationStates* aContinuationStates, + 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. + */ + + 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); + + aIsFirst = !frameState->mHasContOnPrevLines; + firstFrameState = frameState; + } else { + // aFrame is not the first visual frame of its continuation chain + aIsFirst = false; + firstFrameState = aContinuationStates->GetEntry(frameState->mFirstVisualFrame); + } + + aIsLast = (firstFrameState->mFrameCount == 1 && + !firstFrameState->mHasContOnNextLines); + + 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--; +} + +void +nsBidiPresUtils::RepositionFrame(nsIFrame* aFrame, + bool aIsEvenLevel, + nscoord& aStart, + nsContinuationStates* aContinuationStates, + WritingMode aLineWM, + nscoord& aLineWidth) +{ + if (!aFrame) + return; + + bool isFirst, isLast; + IsFirstOrLast(aFrame, + aContinuationStates, + isFirst /* out */, + isLast /* out */); + + WritingMode frameWM = aFrame->GetWritingMode(); + nsInlineFrame* testFrame = do_QueryFrame(aFrame); + + if (testFrame) { + aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET); + + if (isFirst) { + aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST); + } else { + aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST); + } + + if (isLast) { + aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST); + } else { + aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST); + } + } + // 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 + LogicalMargin margin(frameWM, aFrame->GetUsedMargin()); + if (isFirst) { + aStart += margin.IStart(frameWM); + } + + nscoord start = aStart; + nscoord frameWidth = aFrame->GetSize().width; + + if (!IsBidiLeaf(aFrame)) + { + nscoord iCoord = 0; + LogicalMargin borderPadding(frameWM, aFrame->GetUsedBorderAndPadding()); + if (isFirst) { + iCoord += borderPadding.IStart(frameWM); + } + + // If the resolved direction of the container is different from the + // direction of the frame, we need to traverse the child list in reverse + // order, to make it O(n) we store the list locally and iterate the list + // in reverse + bool reverseOrder = aIsEvenLevel != frameWM.IsBidiLTR(); + nsTArray childList; + nsIFrame *frame = aFrame->GetFirstPrincipalChild(); + if (frame && reverseOrder) { + childList.AppendElement((nsIFrame*)nullptr); + while (frame) { + childList.AppendElement(frame); + frame = frame->GetNextSibling(); + } + frame = childList[childList.Length() - 1]; + } + + // Reposition the child frames + int32_t index = 0; + while (frame) { + RepositionFrame(frame, + aIsEvenLevel, + iCoord, + aContinuationStates, + frameWM, + frameWidth); + index++; + frame = reverseOrder ? + childList[childList.Length() - index - 1] : + frame->GetNextSibling(); + } + + if (isLast) { + iCoord += borderPadding.IEnd(frameWM); + } + aStart += iCoord; + } else { + aStart += frameWidth; + } + + LogicalRect logicalRect(aLineWM, aFrame->GetRect(), aLineWidth); + logicalRect.IStart(aLineWM) = start; + logicalRect.ISize(aLineWM) = aStart - start; + aFrame->SetRect(aLineWM, logicalRect, aLineWidth); + + if (isLast) { + aStart += margin.IEnd(frameWM); + } +} + +void +nsBidiPresUtils::InitContinuationStates(nsIFrame* aFrame, + nsContinuationStates* aContinuationStates) +{ + nsFrameContinuationState* state = aContinuationStates->PutEntry(aFrame); + state->mFirstVisualFrame = nullptr; + state->mFrameCount = 0; + + if (!IsBidiLeaf(aFrame)) { + // Continue for child frames + nsIFrame* frame; + for (frame = aFrame->GetFirstPrincipalChild(); + frame; + frame = frame->GetNextSibling()) { + InitContinuationStates(frame, + aContinuationStates); + } + } +} + +void +nsBidiPresUtils::RepositionInlineFrames(BidiLineData *aBld, + nsIFrame* aFirstChild, + WritingMode aLineWM, + nscoord& aLineWidth) +{ + nscoord startSpace = 0; + + // 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 + WritingMode frameWM = aFirstChild->GetWritingMode(); + LogicalMargin margin(frameWM, aFirstChild->GetUsedMargin()); + if (!aFirstChild->GetPrevContinuation() && + !aFirstChild->FrameIsNonFirstInIBSplit()) + startSpace = margin.IStart(frameWM); + + nscoord start = LogicalRect(aLineWM, aFirstChild->GetRect(), + aLineWidth).IStart(aLineWM) - startSpace; + 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); + RepositionFrame(frame, + !(aBld->mLevels[aBld->mIndexMap[index]] & 1), + start, + &continuationStates, + aLineWM, + aLineWidth); + } +} + +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& aFrameIndex, + 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, + int32_t& aOffset) +{ + FrameProperties props = aFrame->Properties(); + nsBidiLevel embeddingLevel = + (nsBidiLevel)NS_PTR_TO_INT32(props.Get(nsIFrame::EmbeddingLevelProperty())); + nsBidiLevel baseLevel = + (nsBidiLevel)NS_PTR_TO_INT32(props.Get(nsIFrame::BaseLevelProperty())); + uint8_t paragraphDepth = + NS_PTR_TO_INT32(props.Get(nsIFrame::ParagraphDepthProperty())); + + for (int32_t index = aFirstIndex + 1; index <= aLastIndex; index++) { + nsIFrame* frame = aBpd->FrameAt(index); + if (frame == NS_BIDI_CONTROL_FRAME) { + ++aOffset; + } + else { + // Make the frame and its continuation ancestors fluid, + // so they can be reused or deleted by normal reflow code + FrameProperties frameProps = frame->Properties(); + frameProps.Set(nsIFrame::EmbeddingLevelProperty(), + NS_INT32_TO_PTR(embeddingLevel)); + frameProps.Set(nsIFrame::BaseLevelProperty(), + NS_INT32_TO_PTR(baseLevel)); + frameProps.Set(nsIFrame::ParagraphDepthProperty(), + NS_INT32_TO_PTR(paragraphDepth)); + 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, + bool aIsOddLevel) +{ + 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); + + nsresult rv = aBidiEngine->SetPara(aText, aLength, aBaseLevel, nullptr); + 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; + nsBidiLevel level; + + 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; + + rv = aBidiEngine->GetLogicalRun(start, &limit, &level); + if (NS_FAILED(rv)) + return rv; + + 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 (level & 1) { + aprocessor.SetText(aText + start, subRunLength, nsBidiDirection(level & 1)); + width = aprocessor.GetWidth(); + xOffset += width; + xEndRun = xOffset; + } + + while (subRunCount > 0) { + // CalculateCharType can increment subRunCount if the run + // contains mixed character types + CalculateCharType(aBidiEngine, aText, lineOffset, typeLimit, subRunLimit, subRunLength, subRunCount, charType, prevType); + + nsAutoString runVisualText; + runVisualText.Assign(aText + start, subRunLength); + if (int32_t(runVisualText.Length()) < subRunLength) + return NS_ERROR_OUT_OF_MEMORY; + FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), subRunLength, + (nsCharType)charType, level & 1); + + aprocessor.SetText(runVisualText.get(), subRunLength, nsBidiDirection(level & 1)); + width = aprocessor.GetWidth(); + totalWidth += width; + if (level & 1) { + 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, *visualRightSide; + if (level & 1) { + // 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 = 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, nsBidiDirection(level & 1)); + subWidth = aprocessor.GetWidth(); + aprocessor.SetText(visualRightSide, visualLeftLength + 1, nsBidiDirection(level & 1)); + posResolve->visualLeftTwips = xOffset + subWidth; + posResolve->visualWidth = aprocessor.GetWidth() - subWidth; + } + } + } + + if (!(level & 1)) { + xOffset += width; + } + + --subRunCount; + start = lineOffset; + subRunLimit = typeLimit; + subRunLength = typeLimit - lineOffset; + } // while + if (level & 1) { + xOffset = xEndRun; + } + + visualStart += length; + } // for + + if (aWidth) { + *aWidth = totalWidth; + } + return NS_OK; +} + +class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor : public nsBidiPresUtils::BidiProcessor { +public: + nsIRenderingContextBidiProcessor(nsRenderingContext* aCtx, + nsRenderingContext* aTextRunConstructionContext, + const nsPoint& aPt) + : mCtx(aCtx), mTextRunConstructionContext(aTextRunConstructionContext), mPt(aPt) { } + + ~nsIRenderingContextBidiProcessor() + { + mCtx->SetTextRunRTL(false); + } + + virtual void SetText(const char16_t* aText, + int32_t aLength, + nsBidiDirection aDirection) MOZ_OVERRIDE + { + mTextRunConstructionContext->SetTextRunRTL(aDirection==NSBIDI_RTL); + mText = aText; + mLength = aLength; + } + + virtual nscoord GetWidth() MOZ_OVERRIDE + { + return mTextRunConstructionContext->GetWidth(mText, mLength); + } + + virtual void DrawText(nscoord aXOffset, + nscoord) MOZ_OVERRIDE + { + mCtx->FontMetrics()->DrawString(mText, mLength, mPt.x + aXOffset, mPt.y, + mCtx, mTextRunConstructionContext); + } + +private: + nsRenderingContext* mCtx; + nsRenderingContext* mTextRunConstructionContext; + 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, + nsRenderingContext& aTextRunConstructionContext, + Mode aMode, + nscoord aX, + nscoord aY, + nsBidiPositionResolve* aPosResolve, + int32_t aPosResolveCount, + nscoord* aWidth) +{ + nsIRenderingContextBidiProcessor processor(&aRenderingContext, &aTextRunConstructionContext, nsPoint(aX, aY)); + nsBidi bidiEngine; + return ProcessText(aText, aLength, aBaseLevel, aPresContext, processor, + aMode, aPosResolve, aPosResolveCount, aWidth, &bidiEngine); +} + +/* static */ +void nsBidiPresUtils::WriteReverse(const char16_t* aSrc, + uint32_t aSrcLength, + char16_t* aDest) +{ + char16_t* dest = aDest + aSrcLength; + mozilla::unicode::ClusterIterator iter(aSrc, aSrcLength); + + while (!iter.AtEnd()) { + iter.Next(); + for (const char16_t *cp = iter; cp > aSrc; ) { + // Here we rely on the fact that there are no non-BMP mirrored pairs + // currently in Unicode, so we don't need to look for surrogates + *--dest = mozilla::unicode::GetMirroredChar(*--cp); + } + aSrc = iter; + } + + NS_ASSERTION(dest == aDest, "Whole string not copied"); +} + +/* static */ +bool nsBidiPresUtils::WriteLogicalToVisual(const char16_t* aSrc, + uint32_t aSrcLength, + char16_t* aDest, + nsBidiLevel aBaseDirection, + nsBidi* aBidiEngine) +{ + const char16_t* src = aSrc; + nsresult rv = aBidiEngine->SetPara(src, aSrcLength, aBaseDirection, nullptr); + if (NS_FAILED(rv)) { + return false; + } + + nsBidiDirection dir; + rv = aBidiEngine->GetDirection(&dir); + // NSBIDI_LTR returned from GetDirection means the whole text is LTR + if (NS_FAILED(rv) || dir == NSBIDI_LTR) { + return false; + } + + int32_t runCount; + rv = aBidiEngine->CountRuns(&runCount); + if (NS_FAILED(rv)) { + return false; + } + + int32_t runIndex, start, length; + char16_t* dest = aDest; + + for (runIndex = 0; runIndex < runCount; ++runIndex) { + rv = aBidiEngine->GetVisualRun(runIndex, &start, &length, &dir); + if (NS_FAILED(rv)) { + return false; + } + + src = aSrc + start; + + if (dir == NSBIDI_RTL) { + WriteReverse(src, length, dest); + dest += length; + } else { + do { + NS_ASSERTION(src >= aSrc && src < aSrc + aSrcLength, + "logical index out of range"); + NS_ASSERTION(dest < aDest + aSrcLength, "visual index out of range"); + *(dest++) = *(src++); + } while (--length); + } + } + + NS_ASSERTION(static_cast(dest - aDest) == aSrcLength, + "whole string not copied"); + return true; +} + +void nsBidiPresUtils::CopyLogicalToVisual(const nsAString& aSource, + nsAString& aDest, + nsBidiLevel aBaseDirection, + bool aOverride) +{ + aDest.SetLength(0); + uint32_t srcLength = aSource.Length(); + if (srcLength == 0) + return; + if (!aDest.SetLength(srcLength, fallible_t())) { + return; + } + nsAString::const_iterator fromBegin, fromEnd; + nsAString::iterator toBegin; + aSource.BeginReading(fromBegin); + aSource.EndReading(fromEnd); + aDest.BeginWriting(toBegin); + + if (aOverride) { + if (aBaseDirection == NSBIDI_RTL) { + // no need to use the converter -- just copy the string in reverse order + WriteReverse(fromBegin.get(), srcLength, toBegin.get()); + } else { + // if aOverride && aBaseDirection == NSBIDI_LTR, fall through to the + // simple copy + aDest.SetLength(0); + } + } else { + nsBidi bidiEngine; + if (!WriteLogicalToVisual(fromBegin.get(), srcLength, toBegin.get(), + aBaseDirection, &bidiEngine)) { + aDest.SetLength(0); + } + } + + if (aDest.IsEmpty()) { + // Either there was an error or the source is unidirectional + // left-to-right. In either case, just copy source to dest. + CopyUnicodeTo(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd), + aDest); + } +} + +/* 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; +}