michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsBidiPresUtils.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsBidiUtils.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsContainerFrame.h" michael@0: #include "nsInlineFrame.h" michael@0: #include "nsPlaceholderFrame.h" michael@0: #include "nsFirstLetterFrame.h" michael@0: #include "nsUnicodeProperties.h" michael@0: #include "nsTextFrame.h" michael@0: #include "nsBlockFrame.h" michael@0: #include "nsIFrameInlines.h" michael@0: #include michael@0: michael@0: #undef NOISY_BIDI michael@0: #undef REALLY_NOISY_BIDI michael@0: michael@0: using namespace mozilla; michael@0: michael@0: static const char16_t kSpace = 0x0020; michael@0: static const char16_t kZWSP = 0x200B; michael@0: static const char16_t kLineSeparator = 0x2028; michael@0: static const char16_t kObjectSubstitute = 0xFFFC; michael@0: static const char16_t kLRE = 0x202A; michael@0: static const char16_t kRLE = 0x202B; michael@0: static const char16_t kLRO = 0x202D; michael@0: static const char16_t kRLO = 0x202E; michael@0: static const char16_t kPDF = 0x202C; michael@0: static const char16_t kSeparators[] = { michael@0: // All characters with Bidi type Segment Separator or Block Separator michael@0: char16_t('\t'), michael@0: char16_t('\r'), michael@0: char16_t('\n'), michael@0: char16_t(0xb), michael@0: char16_t(0x1c), michael@0: char16_t(0x1d), michael@0: char16_t(0x1e), michael@0: char16_t(0x1f), michael@0: char16_t(0x85), michael@0: char16_t(0x2029), michael@0: char16_t(0) michael@0: }; michael@0: michael@0: #define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1) michael@0: michael@0: struct BidiParagraphData { michael@0: nsString mBuffer; michael@0: nsAutoTArray mEmbeddingStack; michael@0: nsTArray mLogicalFrames; michael@0: nsTArray mLinePerFrame; michael@0: nsDataHashtable mContentToFrameIndex; michael@0: bool mIsVisual; michael@0: bool mReset; michael@0: nsBidiLevel mParaLevel; michael@0: nsIContent* mPrevContent; michael@0: nsAutoPtr mBidiEngine; michael@0: nsIFrame* mPrevFrame; michael@0: nsAutoPtr mSubParagraph; michael@0: uint8_t mParagraphDepth; michael@0: michael@0: void Init(nsBlockFrame *aBlockFrame) michael@0: { michael@0: mBidiEngine = new nsBidi(); michael@0: mPrevContent = nullptr; michael@0: mParagraphDepth = 0; michael@0: michael@0: mParaLevel = nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame->StyleContext()); michael@0: michael@0: mIsVisual = aBlockFrame->PresContext()->IsVisualMode(); michael@0: if (mIsVisual) { michael@0: /** michael@0: * Drill up in content to detect whether this is an element that needs to michael@0: * be rendered with logical order even on visual pages. michael@0: * michael@0: * We always use logical order on form controls, firstly so that text michael@0: * entry will be in logical order, but also because visual pages were michael@0: * written with the assumption that even if the browser had no support michael@0: * for right-to-left text rendering, it would use native widgets with michael@0: * bidi support to display form controls. michael@0: * michael@0: * We also use logical order in XUL elements, since we expect that if a michael@0: * XUL element appears in a visual page, it will be generated by an XBL michael@0: * binding and contain localized text which will be in logical order. michael@0: */ michael@0: for (nsIContent* content = aBlockFrame->GetContent() ; content; michael@0: content = content->GetParent()) { michael@0: if (content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) || michael@0: content->IsXUL()) { michael@0: mIsVisual = false; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: BidiParagraphData* GetSubParagraph() michael@0: { michael@0: if (!mSubParagraph) { michael@0: mSubParagraph = new BidiParagraphData(); michael@0: mSubParagraph->Init(this); michael@0: } michael@0: michael@0: return mSubParagraph; michael@0: } michael@0: michael@0: // Initialise a sub-paragraph from its containing paragraph michael@0: void Init(BidiParagraphData *aBpd) michael@0: { michael@0: mBidiEngine = new nsBidi(); michael@0: mPrevContent = nullptr; michael@0: mIsVisual = aBpd->mIsVisual; michael@0: mReset = false; michael@0: } michael@0: michael@0: void Reset(nsIFrame* aBDIFrame, BidiParagraphData *aBpd) michael@0: { michael@0: mReset = true; michael@0: mLogicalFrames.Clear(); michael@0: mLinePerFrame.Clear(); michael@0: mContentToFrameIndex.Clear(); michael@0: mBuffer.SetLength(0); michael@0: mPrevFrame = aBpd->mPrevFrame; michael@0: mParagraphDepth = aBpd->mParagraphDepth + 1; michael@0: michael@0: const nsStyleTextReset* text = aBDIFrame->StyleTextReset(); michael@0: bool isRTL = (NS_STYLE_DIRECTION_RTL == michael@0: aBDIFrame->StyleVisibility()->mDirection); michael@0: michael@0: if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) { michael@0: mParaLevel = NSBIDI_DEFAULT_LTR; michael@0: } else { michael@0: mParaLevel = mParagraphDepth * 2; michael@0: if (isRTL) ++mParaLevel; michael@0: } michael@0: michael@0: if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_OVERRIDE) { michael@0: PushBidiControl(isRTL ? kRLO : kLRO); michael@0: } michael@0: } michael@0: michael@0: void EmptyBuffer() michael@0: { michael@0: mBuffer.SetLength(0); michael@0: } michael@0: michael@0: nsresult SetPara() michael@0: { michael@0: return mBidiEngine->SetPara(mBuffer.get(), BufferLength(), michael@0: mParaLevel, nullptr); michael@0: } michael@0: michael@0: /** michael@0: * mParaLevel can be NSBIDI_DEFAULT_LTR as well as NSBIDI_LTR or NSBIDI_RTL. michael@0: * GetParaLevel() returns the actual (resolved) paragraph level which is michael@0: * always either NSBIDI_LTR or NSBIDI_RTL michael@0: */ michael@0: nsBidiLevel GetParaLevel() michael@0: { michael@0: nsBidiLevel paraLevel = mParaLevel; michael@0: if (IS_DEFAULT_LEVEL(paraLevel)) { michael@0: mBidiEngine->GetParaLevel(¶Level); michael@0: } michael@0: return paraLevel; michael@0: } michael@0: michael@0: nsBidiDirection GetDirection() michael@0: { michael@0: nsBidiDirection dir; michael@0: mBidiEngine->GetDirection(&dir); michael@0: return dir; michael@0: } michael@0: michael@0: nsresult CountRuns(int32_t *runCount){ return mBidiEngine->CountRuns(runCount); } michael@0: michael@0: nsresult GetLogicalRun(int32_t aLogicalStart, michael@0: int32_t* aLogicalLimit, michael@0: nsBidiLevel* aLevel) michael@0: { michael@0: nsresult rv = mBidiEngine->GetLogicalRun(aLogicalStart, michael@0: aLogicalLimit, aLevel); michael@0: if (mIsVisual || NS_FAILED(rv)) michael@0: *aLevel = GetParaLevel(); michael@0: return rv; michael@0: } michael@0: michael@0: void ResetData() michael@0: { michael@0: mLogicalFrames.Clear(); michael@0: mLinePerFrame.Clear(); michael@0: mContentToFrameIndex.Clear(); michael@0: mBuffer.SetLength(0); michael@0: mPrevContent = nullptr; michael@0: for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) { michael@0: mBuffer.Append(mEmbeddingStack[i]); michael@0: mLogicalFrames.AppendElement(NS_BIDI_CONTROL_FRAME); michael@0: mLinePerFrame.AppendElement((nsLineBox*)nullptr); michael@0: } michael@0: } michael@0: michael@0: void ResetForNewBlock() michael@0: { michael@0: for (BidiParagraphData* bpd = this; bpd; bpd = bpd->mSubParagraph) { michael@0: bpd->mPrevFrame = nullptr; michael@0: } michael@0: } michael@0: michael@0: void AppendFrame(nsIFrame* aFrame, michael@0: nsBlockInFlowLineIterator* aLineIter, michael@0: nsIContent* aContent = nullptr) michael@0: { michael@0: if (aContent) { michael@0: mContentToFrameIndex.Put(aContent, FrameCount()); michael@0: } michael@0: mLogicalFrames.AppendElement(aFrame); michael@0: michael@0: AdvanceLineIteratorToFrame(aFrame, aLineIter, mPrevFrame); michael@0: mLinePerFrame.AppendElement(aLineIter->GetLine().get()); michael@0: } michael@0: michael@0: void AdvanceAndAppendFrame(nsIFrame** aFrame, michael@0: nsBlockInFlowLineIterator* aLineIter, michael@0: nsIFrame** aNextSibling) michael@0: { michael@0: nsIFrame* frame = *aFrame; michael@0: nsIFrame* nextSibling = *aNextSibling; michael@0: michael@0: frame = frame->GetNextContinuation(); michael@0: if (frame) { michael@0: AppendFrame(frame, aLineIter, nullptr); michael@0: michael@0: /* michael@0: * If we have already overshot the saved next-sibling while michael@0: * scanning the frame's continuations, advance it. michael@0: */ michael@0: if (frame == nextSibling) { michael@0: nextSibling = frame->GetNextSibling(); michael@0: } michael@0: } michael@0: michael@0: *aFrame = frame; michael@0: *aNextSibling = nextSibling; michael@0: } michael@0: michael@0: int32_t GetLastFrameForContent(nsIContent *aContent) michael@0: { michael@0: int32_t index = 0; michael@0: mContentToFrameIndex.Get(aContent, &index); michael@0: return index; michael@0: } michael@0: michael@0: int32_t FrameCount(){ return mLogicalFrames.Length(); } michael@0: michael@0: int32_t BufferLength(){ return mBuffer.Length(); } michael@0: michael@0: nsIFrame* FrameAt(int32_t aIndex){ return mLogicalFrames[aIndex]; } michael@0: michael@0: nsLineBox* GetLineForFrameAt(int32_t aIndex){ return mLinePerFrame[aIndex]; } michael@0: michael@0: void AppendUnichar(char16_t aCh){ mBuffer.Append(aCh); } michael@0: michael@0: void AppendString(const nsDependentSubstring& aString){ mBuffer.Append(aString); } michael@0: michael@0: void AppendControlChar(char16_t aCh) michael@0: { michael@0: mLogicalFrames.AppendElement(NS_BIDI_CONTROL_FRAME); michael@0: mLinePerFrame.AppendElement((nsLineBox*)nullptr); michael@0: AppendUnichar(aCh); michael@0: } michael@0: michael@0: void PushBidiControl(char16_t aCh) michael@0: { michael@0: AppendControlChar(aCh); michael@0: mEmbeddingStack.AppendElement(aCh); michael@0: } michael@0: michael@0: void PopBidiControl() michael@0: { michael@0: AppendControlChar(kPDF); michael@0: NS_ASSERTION(mEmbeddingStack.Length(), "embedding/override underflow"); michael@0: mEmbeddingStack.TruncateLength(mEmbeddingStack.Length() - 1); michael@0: } michael@0: michael@0: void ClearBidiControls() michael@0: { michael@0: for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) { michael@0: AppendControlChar(kPDF); michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter, michael@0: nsIFrame* aPrevFrame, nsIFrame* aFrame) michael@0: { michael@0: nsIFrame* endFrame = aLineIter->IsLastLineInList() ? nullptr : michael@0: aLineIter->GetLine().next()->mFirstChild; michael@0: nsIFrame* startFrame = aPrevFrame ? aPrevFrame : aLineIter->GetLine()->mFirstChild; michael@0: for (nsIFrame* frame = startFrame; frame && frame != endFrame; michael@0: frame = frame->GetNextSibling()) { michael@0: if (frame == aFrame) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static void michael@0: AdvanceLineIteratorToFrame(nsIFrame* aFrame, michael@0: nsBlockInFlowLineIterator* aLineIter, michael@0: nsIFrame*& aPrevFrame) michael@0: { michael@0: // Advance aLine to the line containing aFrame michael@0: nsIFrame* child = aFrame; michael@0: nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(child); michael@0: while (parent && !nsLayoutUtils::GetAsBlock(parent)) { michael@0: child = parent; michael@0: parent = nsLayoutUtils::GetParentOrPlaceholderFor(child); michael@0: } michael@0: NS_ASSERTION (parent, "aFrame is not a descendent of aBlockFrame"); michael@0: while (!IsFrameInCurrentLine(aLineIter, aPrevFrame, child)) { michael@0: #ifdef DEBUG michael@0: bool hasNext = michael@0: #endif michael@0: aLineIter->Next(); michael@0: NS_ASSERTION(hasNext, "Can't find frame in lines!"); michael@0: aPrevFrame = nullptr; michael@0: } michael@0: aPrevFrame = child; michael@0: } michael@0: michael@0: }; michael@0: michael@0: struct BidiLineData { michael@0: nsTArray mLogicalFrames; michael@0: nsTArray mVisualFrames; michael@0: nsTArray mIndexMap; michael@0: nsAutoTArray mLevels; michael@0: bool mIsReordered; michael@0: michael@0: BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) michael@0: { michael@0: /** michael@0: * Initialize the logically-ordered array of frames using the top-level michael@0: * frames of a single line michael@0: */ michael@0: mLogicalFrames.Clear(); michael@0: michael@0: bool isReordered = false; michael@0: bool hasRTLFrames = false; michael@0: michael@0: for (nsIFrame* frame = aFirstFrameOnLine; michael@0: frame && aNumFramesOnLine--; michael@0: frame = frame->GetNextSibling()) { michael@0: AppendFrame(frame); michael@0: uint8_t level = nsBidiPresUtils::GetFrameEmbeddingLevel(frame); michael@0: mLevels.AppendElement(level); michael@0: mIndexMap.AppendElement(0); michael@0: if (level & 1) { michael@0: hasRTLFrames = true; michael@0: } michael@0: } michael@0: michael@0: // Reorder the line michael@0: nsBidi::ReorderVisual(mLevels.Elements(), FrameCount(), michael@0: mIndexMap.Elements()); michael@0: michael@0: for (int32_t i = 0; i < FrameCount(); i++) { michael@0: mVisualFrames.AppendElement(LogicalFrameAt(mIndexMap[i])); michael@0: if (i != mIndexMap[i]) { michael@0: isReordered = true; michael@0: } michael@0: } michael@0: michael@0: // If there's an RTL frame, assume the line is reordered michael@0: mIsReordered = isReordered || hasRTLFrames; michael@0: } michael@0: michael@0: void AppendFrame(nsIFrame* aFrame) michael@0: { michael@0: mLogicalFrames.AppendElement(aFrame); michael@0: } michael@0: michael@0: int32_t FrameCount(){ return mLogicalFrames.Length(); } michael@0: michael@0: nsIFrame* LogicalFrameAt(int32_t aIndex){ return mLogicalFrames[aIndex]; } michael@0: michael@0: nsIFrame* VisualFrameAt(int32_t aIndex){ return mVisualFrames[aIndex]; } michael@0: }; michael@0: michael@0: /* Some helper methods for Resolve() */ michael@0: michael@0: // Should this frame be split between text runs? michael@0: static bool michael@0: IsBidiSplittable(nsIFrame* aFrame) michael@0: { michael@0: // Bidi inline containers should be split, unless they're line frames. michael@0: nsIAtom* frameType = aFrame->GetType(); michael@0: return (aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer) && michael@0: frameType != nsGkAtoms::lineFrame) || michael@0: frameType == nsGkAtoms::textFrame; michael@0: } michael@0: michael@0: // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)? michael@0: static bool michael@0: IsBidiLeaf(nsIFrame* aFrame) michael@0: { michael@0: nsIFrame* kid = aFrame->GetFirstPrincipalChild(); michael@0: return !kid || !aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer); michael@0: } michael@0: michael@0: /** michael@0: * Create non-fluid continuations for the ancestors of a given frame all the way michael@0: * up the frame tree until we hit a non-splittable frame (a line or a block). michael@0: * michael@0: * @param aParent the first parent frame to be split michael@0: * @param aFrame the child frames after this frame are reparented to the michael@0: * newly-created continuation of aParent. michael@0: * If aFrame is null, all the children of aParent are reparented. michael@0: */ michael@0: static nsresult michael@0: SplitInlineAncestors(nsIFrame* aParent, michael@0: nsIFrame* aFrame) michael@0: { michael@0: nsPresContext *presContext = aParent->PresContext(); michael@0: nsIPresShell *presShell = presContext->PresShell(); michael@0: nsIFrame* frame = aFrame; michael@0: nsIFrame* parent = aParent; michael@0: nsIFrame* newParent; michael@0: michael@0: while (IsBidiSplittable(parent)) { michael@0: nsIFrame* grandparent = parent->GetParent(); michael@0: NS_ASSERTION(grandparent, "Couldn't get parent's parent in nsBidiPresUtils::SplitInlineAncestors"); michael@0: michael@0: // Split the child list after |frame|, unless it is the last child. michael@0: if (!frame || frame->GetNextSibling()) { michael@0: michael@0: newParent = presShell->FrameConstructor()-> michael@0: CreateContinuingFrame(presContext, parent, grandparent, false); michael@0: michael@0: nsContainerFrame* container = do_QueryFrame(parent); michael@0: nsFrameList tail = container->StealFramesAfter(frame); michael@0: michael@0: // Reparent views as necessary michael@0: nsresult rv; michael@0: rv = nsContainerFrame::ReparentFrameViewList(tail, parent, newParent); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // The parent's continuation adopts the siblings after the split. michael@0: rv = newParent->InsertFrames(nsIFrame::kNoReflowPrincipalList, nullptr, tail); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // The list name kNoReflowPrincipalList would indicate we don't want reflow michael@0: nsFrameList temp(newParent, newParent); michael@0: rv = grandparent->InsertFrames(nsIFrame::kNoReflowPrincipalList, parent, temp); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: frame = parent; michael@0: parent = grandparent; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void michael@0: MakeContinuationFluid(nsIFrame* aFrame, nsIFrame* aNext) michael@0: { michael@0: NS_ASSERTION (!aFrame->GetNextInFlow() || aFrame->GetNextInFlow() == aNext, michael@0: "next-in-flow is not next continuation!"); michael@0: aFrame->SetNextInFlow(aNext); michael@0: michael@0: NS_ASSERTION (!aNext->GetPrevInFlow() || aNext->GetPrevInFlow() == aFrame, michael@0: "prev-in-flow is not prev continuation!"); michael@0: aNext->SetPrevInFlow(aFrame); michael@0: } michael@0: michael@0: static void michael@0: MakeContinuationsNonFluidUpParentChain(nsIFrame* aFrame, nsIFrame* aNext) michael@0: { michael@0: nsIFrame* frame; michael@0: nsIFrame* next; michael@0: michael@0: for (frame = aFrame, next = aNext; michael@0: frame && next && michael@0: next != frame && next == frame->GetNextInFlow() && michael@0: IsBidiSplittable(frame); michael@0: frame = frame->GetParent(), next = next->GetParent()) { michael@0: michael@0: frame->SetNextContinuation(next); michael@0: next->SetPrevContinuation(frame); michael@0: } michael@0: } michael@0: michael@0: // If aFrame is the last child of its parent, convert bidi continuations to michael@0: // fluid continuations for all of its inline ancestors. michael@0: // If it isn't the last child, make sure that its continuation is fluid. michael@0: static void michael@0: JoinInlineAncestors(nsIFrame* aFrame) michael@0: { michael@0: nsIFrame* frame = aFrame; michael@0: do { michael@0: nsIFrame* next = frame->GetNextContinuation(); michael@0: if (next) { michael@0: // Don't join frames if they come from different paragraph depths (i.e. michael@0: // one is bidi isolated relative to the other michael@0: if (nsBidiPresUtils::GetParagraphDepth(frame) == michael@0: nsBidiPresUtils::GetParagraphDepth(next)) { michael@0: MakeContinuationFluid(frame, next); michael@0: } michael@0: } michael@0: // Join the parent only as long as we're its last child. michael@0: if (frame->GetNextSibling()) michael@0: break; michael@0: frame = frame->GetParent(); michael@0: } while (frame && IsBidiSplittable(frame)); michael@0: } michael@0: michael@0: static nsresult michael@0: CreateContinuation(nsIFrame* aFrame, michael@0: nsIFrame** aNewFrame, michael@0: bool aIsFluid) michael@0: { michael@0: NS_PRECONDITION(aNewFrame, "null OUT ptr"); michael@0: NS_PRECONDITION(aFrame, "null ptr"); michael@0: michael@0: *aNewFrame = nullptr; michael@0: michael@0: nsPresContext *presContext = aFrame->PresContext(); michael@0: nsIPresShell *presShell = presContext->PresShell(); michael@0: NS_ASSERTION(presShell, "PresShell must be set on PresContext before calling nsBidiPresUtils::CreateContinuation"); michael@0: michael@0: nsIFrame* parent = aFrame->GetParent(); michael@0: NS_ASSERTION(parent, "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation"); michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: // Have to special case floating first letter frames because the continuation michael@0: // doesn't go in the first letter frame. The continuation goes with the rest michael@0: // of the text that the first letter frame was made out of. michael@0: if (parent->GetType() == nsGkAtoms::letterFrame && michael@0: parent->IsFloating()) { michael@0: nsFirstLetterFrame* letterFrame = do_QueryFrame(parent); michael@0: rv = letterFrame->CreateContinuationForFloatingParent(presContext, aFrame, michael@0: aNewFrame, aIsFluid); michael@0: return rv; michael@0: } michael@0: michael@0: *aNewFrame = presShell->FrameConstructor()-> michael@0: CreateContinuingFrame(presContext, aFrame, parent, aIsFluid); michael@0: michael@0: // The list name kNoReflowPrincipalList would indicate we don't want reflow michael@0: // XXXbz this needs higher-level framelist love michael@0: nsFrameList temp(*aNewFrame, *aNewFrame); michael@0: rv = parent->InsertFrames(nsIFrame::kNoReflowPrincipalList, aFrame, temp); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: if (!aIsFluid) { michael@0: // Split inline ancestor frames michael@0: rv = SplitInlineAncestors(parent, aFrame); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* michael@0: * Overview of the implementation of Resolve(): michael@0: * michael@0: * Walk through the descendants of aBlockFrame and build: michael@0: * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order michael@0: * * mBuffer: an nsString containing a representation of michael@0: * the content of the frames. michael@0: * In the case of text frames, this is the actual text context of the michael@0: * frames, but some other elements are represented in a symbolic form which michael@0: * will make the Unicode Bidi Algorithm give the correct results. michael@0: * Bidi embeddings and overrides set by CSS or elements are michael@0: * represented by the corresponding Unicode control characters. michael@0: *
elements are represented by U+2028 LINE SEPARATOR michael@0: * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT michael@0: * CHARACTER michael@0: * michael@0: * Then pass mBuffer to the Bidi engine for resolving of embedding levels michael@0: * by nsBidi::SetPara() and division into directional runs by michael@0: * nsBidi::CountRuns(). michael@0: * michael@0: * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and michael@0: * correlate them with the frames indexed in mLogicalFrames, setting the michael@0: * baseLevel and embeddingLevel properties according to the results returned michael@0: * by the Bidi engine. michael@0: * michael@0: * The rendering layer requires each text frame to contain text in only one michael@0: * direction, so we may need to call EnsureBidiContinuation() to split frames. michael@0: * We may also need to call RemoveBidiContinuation() to convert frames created michael@0: * by EnsureBidiContinuation() in previous reflows into fluid continuations. michael@0: */ michael@0: nsresult michael@0: nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame) michael@0: { michael@0: BidiParagraphData bpd; michael@0: bpd.Init(aBlockFrame); michael@0: michael@0: // Handle bidi-override being set on the block itself before calling michael@0: // TraverseFrames. michael@0: const nsStyleTextReset* text = aBlockFrame->StyleTextReset(); michael@0: char16_t ch = 0; michael@0: if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_OVERRIDE) { michael@0: const nsStyleVisibility* vis = aBlockFrame->StyleVisibility(); michael@0: if (NS_STYLE_DIRECTION_RTL == vis->mDirection) { michael@0: ch = kRLO; michael@0: } michael@0: else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) { michael@0: ch = kLRO; michael@0: } michael@0: if (ch != 0) { michael@0: bpd.PushBidiControl(ch); michael@0: } michael@0: } michael@0: for (nsBlockFrame* block = aBlockFrame; block; michael@0: block = static_cast(block->GetNextContinuation())) { michael@0: block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); michael@0: nsBlockInFlowLineIterator lineIter(block, block->begin_lines()); michael@0: bpd.ResetForNewBlock(); michael@0: TraverseFrames(aBlockFrame, &lineIter, block->GetFirstPrincipalChild(), &bpd); michael@0: // XXX what about overflow lines? michael@0: } michael@0: michael@0: if (ch != 0) { michael@0: bpd.PopBidiControl(); michael@0: } michael@0: michael@0: BidiParagraphData* subParagraph = bpd.GetSubParagraph(); michael@0: if (subParagraph->BufferLength()) { michael@0: ResolveParagraph(aBlockFrame, subParagraph); michael@0: subParagraph->EmptyBuffer(); michael@0: } michael@0: return ResolveParagraph(aBlockFrame, &bpd); michael@0: } michael@0: michael@0: nsresult michael@0: nsBidiPresUtils::ResolveParagraph(nsBlockFrame* aBlockFrame, michael@0: BidiParagraphData* aBpd) michael@0: { michael@0: nsPresContext *presContext = aBlockFrame->PresContext(); michael@0: michael@0: if (aBpd->BufferLength() < 1) { michael@0: return NS_OK; michael@0: } michael@0: aBpd->mBuffer.ReplaceChar(kSeparators, kSpace); michael@0: michael@0: int32_t runCount; michael@0: michael@0: nsresult rv = aBpd->SetPara(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint8_t embeddingLevel = aBpd->GetParaLevel(); michael@0: michael@0: rv = aBpd->CountRuns(&runCount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int32_t runLength = 0; // the length of the current run of text michael@0: int32_t lineOffset = 0; // the start of the current run michael@0: int32_t logicalLimit = 0; // the end of the current run + 1 michael@0: int32_t numRun = -1; michael@0: int32_t fragmentLength = 0; // the length of the current text frame michael@0: int32_t frameIndex = -1; // index to the frames in mLogicalFrames michael@0: int32_t frameCount = aBpd->FrameCount(); michael@0: int32_t contentOffset = 0; // offset of current frame in its content node michael@0: bool isTextFrame = false; michael@0: nsIFrame* frame = nullptr; michael@0: nsIContent* content = nullptr; michael@0: int32_t contentTextLength = 0; michael@0: michael@0: FramePropertyTable *propTable = presContext->PropertyTable(); michael@0: nsLineBox* currentLine = nullptr; michael@0: michael@0: #ifdef DEBUG michael@0: #ifdef NOISY_BIDI michael@0: printf("Before Resolve(), aBlockFrame=0x%p, mBuffer='%s', frameCount=%d, runCount=%d\n", michael@0: (void*)aBlockFrame, NS_ConvertUTF16toUTF8(aBpd->mBuffer).get(), frameCount, runCount); michael@0: #ifdef REALLY_NOISY_BIDI michael@0: printf(" block frame tree=:\n"); michael@0: aBlockFrame->List(stdout, 0); michael@0: #endif michael@0: #endif michael@0: #endif michael@0: michael@0: if (runCount == 1 && frameCount == 1 && michael@0: aBpd->mParagraphDepth == 0 && aBpd->GetDirection() == NSBIDI_LTR && michael@0: aBpd->GetParaLevel() == 0) { michael@0: // We have a single left-to-right frame in a left-to-right paragraph, michael@0: // without bidi isolation from the surrounding text. michael@0: // Make sure that the embedding level and base level frame properties aren't michael@0: // set (because if they are this frame used to have some other direction, michael@0: // so we can't do this optimization), and we're done. michael@0: nsIFrame* frame = aBpd->FrameAt(0); michael@0: if (frame != NS_BIDI_CONTROL_FRAME && michael@0: !frame->Properties().Get(nsIFrame::EmbeddingLevelProperty()) && michael@0: !frame->Properties().Get(nsIFrame::BaseLevelProperty())) { michael@0: #ifdef DEBUG michael@0: #ifdef NOISY_BIDI michael@0: printf("early return for single direction frame %p\n", (void*)frame); michael@0: #endif michael@0: #endif michael@0: frame->AddStateBits(NS_FRAME_IS_BIDI); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: nsIFrame* firstFrame = nullptr; michael@0: nsIFrame* lastFrame = nullptr; michael@0: michael@0: for (; ;) { michael@0: if (fragmentLength <= 0) { michael@0: // Get the next frame from mLogicalFrames michael@0: if (++frameIndex >= frameCount) { michael@0: break; michael@0: } michael@0: frame = aBpd->FrameAt(frameIndex); michael@0: if (frame == NS_BIDI_CONTROL_FRAME || michael@0: nsGkAtoms::textFrame != frame->GetType()) { michael@0: /* michael@0: * Any non-text frame corresponds to a single character in the text buffer michael@0: * (a bidi control character, LINE SEPARATOR, or OBJECT SUBSTITUTE) michael@0: */ michael@0: isTextFrame = false; michael@0: fragmentLength = 1; michael@0: } michael@0: else { michael@0: if (!firstFrame) { michael@0: firstFrame = frame; michael@0: } michael@0: lastFrame = frame; michael@0: currentLine = aBpd->GetLineForFrameAt(frameIndex); michael@0: content = frame->GetContent(); michael@0: if (!content) { michael@0: rv = NS_OK; michael@0: break; michael@0: } michael@0: contentTextLength = content->TextLength(); michael@0: if (contentTextLength == 0) { michael@0: frame->AdjustOffsetsForBidi(0, 0); michael@0: // Set the base level and embedding level of the current run even michael@0: // on an empty frame. Otherwise frame reordering will not be correct. michael@0: propTable->Set(frame, nsIFrame::EmbeddingLevelProperty(), michael@0: NS_INT32_TO_PTR(embeddingLevel)); michael@0: propTable->Set(frame, nsIFrame::BaseLevelProperty(), michael@0: NS_INT32_TO_PTR(aBpd->GetParaLevel())); michael@0: propTable->Set(frame, nsIFrame::ParagraphDepthProperty(), michael@0: NS_INT32_TO_PTR(aBpd->mParagraphDepth)); michael@0: continue; michael@0: } michael@0: int32_t start, end; michael@0: frame->GetOffsets(start, end); michael@0: NS_ASSERTION(!(contentTextLength < end - start), michael@0: "Frame offsets don't fit in content"); michael@0: fragmentLength = std::min(contentTextLength, end - start); michael@0: contentOffset = start; michael@0: isTextFrame = true; michael@0: } michael@0: } // if (fragmentLength <= 0) michael@0: michael@0: if (runLength <= 0) { michael@0: // Get the next run of text from the Bidi engine michael@0: if (++numRun >= runCount) { michael@0: break; michael@0: } michael@0: lineOffset = logicalLimit; michael@0: if (NS_FAILED(aBpd->GetLogicalRun( michael@0: lineOffset, &logicalLimit, &embeddingLevel) ) ) { michael@0: break; michael@0: } michael@0: runLength = logicalLimit - lineOffset; michael@0: } // if (runLength <= 0) michael@0: michael@0: if (frame == NS_BIDI_CONTROL_FRAME) { michael@0: frame = nullptr; michael@0: ++lineOffset; michael@0: } michael@0: else { michael@0: propTable->Set(frame, nsIFrame::EmbeddingLevelProperty(), michael@0: NS_INT32_TO_PTR(embeddingLevel)); michael@0: propTable->Set(frame, nsIFrame::BaseLevelProperty(), michael@0: NS_INT32_TO_PTR(aBpd->GetParaLevel())); michael@0: propTable->Set(frame, nsIFrame::ParagraphDepthProperty(), michael@0: NS_INT32_TO_PTR(aBpd->mParagraphDepth)); michael@0: if (isTextFrame) { michael@0: if ( (runLength > 0) && (runLength < fragmentLength) ) { michael@0: /* michael@0: * The text in this frame continues beyond the end of this directional run. michael@0: * Create a non-fluid continuation frame for the next directional run. michael@0: */ michael@0: currentLine->MarkDirty(); michael@0: nsIFrame* nextBidi; michael@0: int32_t runEnd = contentOffset + runLength; michael@0: rv = EnsureBidiContinuation(frame, &nextBidi, frameIndex, michael@0: contentOffset, michael@0: runEnd); michael@0: if (NS_FAILED(rv)) { michael@0: break; michael@0: } michael@0: nextBidi->AdjustOffsetsForBidi(runEnd, michael@0: contentOffset + fragmentLength); michael@0: lastFrame = frame = nextBidi; michael@0: contentOffset = runEnd; michael@0: } // if (runLength < fragmentLength) michael@0: else { michael@0: if (contentOffset + fragmentLength == contentTextLength) { michael@0: /* michael@0: * We have finished all the text in this content node. Convert any michael@0: * further non-fluid continuations to fluid continuations and advance michael@0: * frameIndex to the last frame in the content node michael@0: */ michael@0: int32_t newIndex = aBpd->GetLastFrameForContent(content); michael@0: if (newIndex > frameIndex) { michael@0: currentLine->MarkDirty(); michael@0: RemoveBidiContinuation(aBpd, frame, michael@0: frameIndex, newIndex, lineOffset); michael@0: frameIndex = newIndex; michael@0: lastFrame = frame = aBpd->FrameAt(frameIndex); michael@0: } michael@0: } else if (fragmentLength > 0 && runLength > fragmentLength) { michael@0: /* michael@0: * There is more text that belongs to this directional run in the next michael@0: * text frame: make sure it is a fluid continuation of the current frame. michael@0: * Do not advance frameIndex, because the next frame may contain michael@0: * multi-directional text and need to be split michael@0: */ michael@0: int32_t newIndex = frameIndex; michael@0: do { michael@0: } while (++newIndex < frameCount && michael@0: aBpd->FrameAt(newIndex) == NS_BIDI_CONTROL_FRAME); michael@0: if (newIndex < frameCount) { michael@0: currentLine->MarkDirty(); michael@0: RemoveBidiContinuation(aBpd, frame, michael@0: frameIndex, newIndex, lineOffset); michael@0: } michael@0: } else if (runLength == fragmentLength) { michael@0: /* michael@0: * If the directional run ends at the end of the frame, make sure michael@0: * that any continuation is non-fluid, and do the same up the michael@0: * parent chain michael@0: */ michael@0: nsIFrame* next = frame->GetNextInFlow(); michael@0: if (next) { michael@0: currentLine->MarkDirty(); michael@0: MakeContinuationsNonFluidUpParentChain(frame, next); michael@0: } michael@0: } michael@0: frame->AdjustOffsetsForBidi(contentOffset, contentOffset + fragmentLength); michael@0: currentLine->MarkDirty(); michael@0: } michael@0: } // isTextFrame michael@0: else { michael@0: ++lineOffset; michael@0: } michael@0: } // not bidi control frame michael@0: int32_t temp = runLength; michael@0: runLength -= fragmentLength; michael@0: fragmentLength -= temp; michael@0: michael@0: if (frame && fragmentLength <= 0) { michael@0: // If the frame is at the end of a run, and this is not the end of our michael@0: // paragrah, split all ancestor inlines that need splitting. michael@0: // To determine whether we're at the end of the run, we check that we've michael@0: // finished processing the current run, and that the current frame michael@0: // doesn't have a fluid continuation (it could have a fluid continuation michael@0: // of zero length, so testing runLength alone is not sufficient). michael@0: if (runLength <= 0 && !frame->GetNextInFlow()) { michael@0: if (numRun + 1 < runCount) { michael@0: nsIFrame* child = frame; michael@0: nsIFrame* parent = frame->GetParent(); michael@0: // As long as we're on the last sibling, the parent doesn't have to michael@0: // be split. michael@0: // However, if the parent has a fluid continuation, we do have to make michael@0: // it non-fluid. This can happen e.g. when we have a first-letter michael@0: // frame and the end of the first-letter coincides with the end of a michael@0: // directional run. michael@0: while (parent && michael@0: IsBidiSplittable(parent) && michael@0: !child->GetNextSibling()) { michael@0: nsIFrame* next = parent->GetNextInFlow(); michael@0: if (next) { michael@0: parent->SetNextContinuation(next); michael@0: next->SetPrevContinuation(parent); michael@0: } michael@0: child = parent; michael@0: parent = child->GetParent(); michael@0: } michael@0: if (parent && IsBidiSplittable(parent)) { michael@0: SplitInlineAncestors(parent, child); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: // We're not at an end of a run. If |frame| is the last child of its michael@0: // parent, and its ancestors happen to have bidi continuations, convert michael@0: // them into fluid continuations. michael@0: JoinInlineAncestors(frame); michael@0: } michael@0: } michael@0: } // for michael@0: michael@0: if (aBpd->mParagraphDepth > 0) { michael@0: nsIFrame* child; michael@0: nsIFrame* parent; michael@0: if (firstFrame) { michael@0: child = firstFrame->GetParent(); michael@0: if (child) { michael@0: parent = child->GetParent(); michael@0: if (parent && IsBidiSplittable(parent)) { michael@0: nsIFrame* prev = child->GetPrevSibling(); michael@0: if (prev) { michael@0: SplitInlineAncestors(parent, prev); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (lastFrame) { michael@0: child = lastFrame->GetParent(); michael@0: if (child) { michael@0: parent = child->GetParent(); michael@0: if (parent && IsBidiSplittable(parent)) { michael@0: SplitInlineAncestors(parent, child); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: #ifdef REALLY_NOISY_BIDI michael@0: printf("---\nAfter Resolve(), frameTree =:\n"); michael@0: aBlockFrame->List(stdout, 0); michael@0: printf("===\n"); michael@0: #endif michael@0: #endif michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsBidiPresUtils::TraverseFrames(nsBlockFrame* aBlockFrame, michael@0: nsBlockInFlowLineIterator* aLineIter, michael@0: nsIFrame* aCurrentFrame, michael@0: BidiParagraphData* aBpd) michael@0: { michael@0: if (!aCurrentFrame) michael@0: return; michael@0: michael@0: #ifdef DEBUG michael@0: nsBlockFrame* initialLineContainer = aLineIter->GetContainer(); michael@0: #endif michael@0: michael@0: nsIFrame* childFrame = aCurrentFrame; michael@0: do { michael@0: /* michael@0: * It's important to get the next sibling and next continuation *before* michael@0: * handling the frame: If we encounter a forced paragraph break and call michael@0: * ResolveParagraph within this loop, doing GetNextSibling and michael@0: * GetNextContinuation after that could return a bidi continuation that had michael@0: * just been split from the original childFrame and we would process it michael@0: * twice. michael@0: */ michael@0: nsIFrame* nextSibling = childFrame->GetNextSibling(); michael@0: bool isLastFrame = !childFrame->GetNextContinuation(); michael@0: bool isFirstFrame = !childFrame->GetPrevContinuation(); michael@0: michael@0: // If the real frame for a placeholder is a first letter frame, we need to michael@0: // drill down into it and include its contents in Bidi resolution. michael@0: // If not, we just use the placeholder. michael@0: nsIFrame* frame = childFrame; michael@0: if (nsGkAtoms::placeholderFrame == childFrame->GetType()) { michael@0: nsIFrame* realFrame = michael@0: nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame); michael@0: if (realFrame->GetType() == nsGkAtoms::letterFrame) { michael@0: frame = realFrame; michael@0: } michael@0: } michael@0: michael@0: char16_t ch = 0; michael@0: if (frame->IsFrameOfType(nsIFrame::eBidiInlineContainer)) { michael@0: if (!(frame->GetStateBits() & NS_FRAME_FIRST_REFLOW)) { michael@0: nsContainerFrame* c = static_cast(frame); michael@0: MOZ_ASSERT(c = do_QueryFrame(frame), michael@0: "eBidiInlineContainer must be a nsContainerFrame subclass"); michael@0: c->DrainSelfOverflowList(); michael@0: } michael@0: michael@0: const nsStyleVisibility* vis = frame->StyleVisibility(); michael@0: const nsStyleTextReset* text = frame->StyleTextReset(); michael@0: if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_OVERRIDE) { michael@0: if (NS_STYLE_DIRECTION_RTL == vis->mDirection) { michael@0: ch = kRLO; michael@0: } michael@0: else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) { michael@0: ch = kLRO; michael@0: } michael@0: } else if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_EMBED) { michael@0: if (NS_STYLE_DIRECTION_RTL == vis->mDirection) { michael@0: ch = kRLE; michael@0: } michael@0: else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) { michael@0: ch = kLRE; michael@0: } michael@0: } michael@0: michael@0: // Add a dummy frame pointer representing a bidi control code before the michael@0: // first frame of an element specifying embedding or override michael@0: if (ch != 0 && isFirstFrame) { michael@0: aBpd->PushBidiControl(ch); michael@0: } michael@0: } michael@0: michael@0: if (IsBidiLeaf(frame)) { michael@0: /* Bidi leaf frame: add the frame to the mLogicalFrames array, michael@0: * and add its index to the mContentToFrameIndex hashtable. This michael@0: * will be used in RemoveBidiContinuation() to identify the last michael@0: * frame in the array with a given content. michael@0: */ michael@0: nsIContent* content = frame->GetContent(); michael@0: aBpd->AppendFrame(frame, aLineIter, content); michael@0: michael@0: // Append the content of the frame to the paragraph buffer michael@0: nsIAtom* frameType = frame->GetType(); michael@0: if (nsGkAtoms::textFrame == frameType) { michael@0: if (content != aBpd->mPrevContent) { michael@0: aBpd->mPrevContent = content; michael@0: if (!frame->StyleText()->NewlineIsSignificant()) { michael@0: content->AppendTextTo(aBpd->mBuffer); michael@0: } else { michael@0: /* michael@0: * For preformatted text we have to do bidi resolution on each line michael@0: * separately. michael@0: */ michael@0: nsAutoString text; michael@0: content->AppendTextTo(text); michael@0: nsIFrame* next; michael@0: do { michael@0: next = nullptr; michael@0: michael@0: int32_t start, end; michael@0: frame->GetOffsets(start, end); michael@0: int32_t endLine = text.FindChar('\n', start); michael@0: if (endLine == -1) { michael@0: /* michael@0: * If there is no newline in the text content, just save the michael@0: * text from this frame and its continuations, and do bidi michael@0: * resolution later michael@0: */ michael@0: aBpd->AppendString(Substring(text, start)); michael@0: while (frame && nextSibling) { michael@0: aBpd->AdvanceAndAppendFrame(&frame, aLineIter, &nextSibling); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: /* michael@0: * If there is a newline in the frame, break the frame after the michael@0: * newline, do bidi resolution and repeat until the last sibling michael@0: */ michael@0: ++endLine; michael@0: michael@0: /* michael@0: * If the frame ends before the new line, save the text and move michael@0: * into the next continuation michael@0: */ michael@0: aBpd->AppendString(Substring(text, start, michael@0: std::min(end, endLine) - start)); michael@0: while (end < endLine && nextSibling) { michael@0: aBpd->AdvanceAndAppendFrame(&frame, aLineIter, &nextSibling); michael@0: NS_ASSERTION(frame, "Premature end of continuation chain"); michael@0: frame->GetOffsets(start, end); michael@0: aBpd->AppendString(Substring(text, start, michael@0: std::min(end, endLine) - start)); michael@0: } michael@0: michael@0: if (end < endLine) { michael@0: aBpd->mPrevContent = nullptr; michael@0: break; michael@0: } michael@0: michael@0: bool createdContinuation = false; michael@0: if (uint32_t(endLine) < text.Length()) { michael@0: /* michael@0: * Timing is everything here: if the frame already has a bidi michael@0: * continuation, we need to make the continuation fluid *before* michael@0: * resetting the length of the current frame. Otherwise michael@0: * nsTextFrame::SetLength won't set the continuation frame's michael@0: * text offsets correctly. michael@0: * michael@0: * On the other hand, if the frame doesn't have a continuation, michael@0: * we need to create one *after* resetting the length, or michael@0: * CreateContinuingFrame will complain that there is no more michael@0: * content for the continuation. michael@0: */ michael@0: next = frame->GetNextInFlow(); michael@0: if (!next) { michael@0: // If the frame already has a bidi continuation, make it fluid michael@0: next = frame->GetNextContinuation(); michael@0: if (next) { michael@0: MakeContinuationFluid(frame, next); michael@0: JoinInlineAncestors(frame); michael@0: } michael@0: } michael@0: michael@0: nsTextFrame* textFrame = static_cast(frame); michael@0: textFrame->SetLength(endLine - start, nullptr); michael@0: michael@0: if (!next) { michael@0: // If the frame has no next in flow, create one. michael@0: CreateContinuation(frame, &next, true); michael@0: createdContinuation = true; michael@0: } michael@0: aBpd->GetLineForFrameAt(aBpd->FrameCount() - 1)->MarkDirty(); michael@0: } michael@0: ResolveParagraphWithinBlock(aBlockFrame, aBpd); michael@0: michael@0: if (!nextSibling && !createdContinuation) { michael@0: break; michael@0: } else if (next) { michael@0: frame = next; michael@0: aBpd->AppendFrame(frame, aLineIter); michael@0: } michael@0: michael@0: /* michael@0: * If we have already overshot the saved next-sibling while michael@0: * scanning the frame's continuations, advance it. michael@0: */ michael@0: if (frame && frame == nextSibling) { michael@0: nextSibling = frame->GetNextSibling(); michael@0: } michael@0: michael@0: } while (next); michael@0: } michael@0: } michael@0: } else if (nsGkAtoms::brFrame == frameType) { michael@0: // break frame -- append line separator michael@0: aBpd->AppendUnichar(kLineSeparator); michael@0: ResolveParagraphWithinBlock(aBlockFrame, aBpd); michael@0: } else { michael@0: // other frame type -- see the Unicode Bidi Algorithm: michael@0: // "...inline objects (such as graphics) are treated as if they are ... michael@0: // U+FFFC" michael@0: // , however, is treated as U+200B ZERO WIDTH SPACE. See michael@0: // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1 michael@0: aBpd->AppendUnichar(content->IsHTML(nsGkAtoms::wbr) ? michael@0: kZWSP : kObjectSubstitute); michael@0: if (!frame->IsInlineOutside()) { michael@0: // if it is not inline, end the paragraph michael@0: ResolveParagraphWithinBlock(aBlockFrame, aBpd); michael@0: } michael@0: } michael@0: } else { michael@0: // For a non-leaf frame, recurse into TraverseFrames michael@0: nsIFrame* kid = frame->GetFirstPrincipalChild(); michael@0: MOZ_ASSERT(!frame->GetFirstChild(nsIFrame::kOverflowList), michael@0: "should have drained the overflow list above"); michael@0: if (kid) { michael@0: const nsStyleTextReset* text = frame->StyleTextReset(); michael@0: if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_ISOLATE || michael@0: text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) { michael@0: // css "unicode-bidi: isolate" and html5 bdi: michael@0: // resolve the element as a separate paragraph michael@0: BidiParagraphData* subParagraph = aBpd->GetSubParagraph(); michael@0: michael@0: /* michael@0: * As at the beginning of the loop, it's important to check for michael@0: * next-continuations before handling the frame. If we do michael@0: * TraverseFrames and *then* do GetNextContinuation on the original michael@0: * first frame, it could return a bidi continuation that had only michael@0: * just been created, and we would skip doing bidi resolution on the michael@0: * last part of the sub-paragraph. michael@0: */ michael@0: bool isLastContinuation = !frame->GetNextContinuation(); michael@0: if (!frame->GetPrevContinuation() || !subParagraph->mReset) { michael@0: if (subParagraph->BufferLength()) { michael@0: ResolveParagraph(aBlockFrame, subParagraph); michael@0: } michael@0: subParagraph->Reset(frame, aBpd); michael@0: } michael@0: TraverseFrames(aBlockFrame, aLineIter, kid, subParagraph); michael@0: if (isLastContinuation) { michael@0: ResolveParagraph(aBlockFrame, subParagraph); michael@0: subParagraph->EmptyBuffer(); michael@0: } michael@0: michael@0: // Treat the element as a neutral character within its containing michael@0: // paragraph. michael@0: aBpd->AppendControlChar(kObjectSubstitute); michael@0: } else { michael@0: TraverseFrames(aBlockFrame, aLineIter, kid, aBpd); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If the element is attributed by dir, indicate direction pop (add PDF frame) michael@0: if (isLastFrame) { michael@0: if (ch) { michael@0: // Add a dummy frame pointer representing a bidi control code after the michael@0: // last frame of an element specifying embedding or override michael@0: aBpd->PopBidiControl(); michael@0: } michael@0: } michael@0: childFrame = nextSibling; michael@0: } while (childFrame); michael@0: michael@0: MOZ_ASSERT(initialLineContainer == aLineIter->GetContainer()); michael@0: } michael@0: michael@0: void michael@0: nsBidiPresUtils::ResolveParagraphWithinBlock(nsBlockFrame* aBlockFrame, michael@0: BidiParagraphData* aBpd) michael@0: { michael@0: aBpd->ClearBidiControls(); michael@0: ResolveParagraph(aBlockFrame, aBpd); michael@0: aBpd->ResetData(); michael@0: } michael@0: michael@0: void michael@0: nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine, michael@0: int32_t aNumFramesOnLine, michael@0: WritingMode aLineWM, michael@0: nscoord& aLineWidth) michael@0: { michael@0: // If this line consists of a line frame, reorder the line frame's children. michael@0: if (aFirstFrameOnLine->GetType() == nsGkAtoms::lineFrame) { michael@0: aFirstFrameOnLine = aFirstFrameOnLine->GetFirstPrincipalChild(); michael@0: if (!aFirstFrameOnLine) michael@0: return; michael@0: // All children of the line frame are on the first line. Setting aNumFramesOnLine michael@0: // to -1 makes InitLogicalArrayFromLine look at all of them. michael@0: aNumFramesOnLine = -1; michael@0: } michael@0: michael@0: BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); michael@0: RepositionInlineFrames(&bld, aFirstFrameOnLine, aLineWM, aLineWidth); michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame) michael@0: { michael@0: nsIFrame* firstLeaf = aFrame; michael@0: while (!IsBidiLeaf(firstLeaf)) { michael@0: nsIFrame* firstChild = firstLeaf->GetFirstPrincipalChild(); michael@0: nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(firstChild); michael@0: firstLeaf = (realFrame->GetType() == nsGkAtoms::letterFrame) ? michael@0: realFrame : firstChild; michael@0: } michael@0: return firstLeaf; michael@0: } michael@0: michael@0: nsBidiLevel michael@0: nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) michael@0: { michael@0: return NS_GET_EMBEDDING_LEVEL(nsBidiPresUtils::GetFirstLeaf(aFrame)); michael@0: } michael@0: michael@0: uint8_t michael@0: nsBidiPresUtils::GetParagraphDepth(nsIFrame* aFrame) michael@0: { michael@0: return NS_GET_PARAGRAPH_DEPTH(nsBidiPresUtils::GetFirstLeaf(aFrame)); michael@0: } michael@0: michael@0: michael@0: nsBidiLevel michael@0: nsBidiPresUtils::GetFrameBaseLevel(nsIFrame* aFrame) michael@0: { michael@0: nsIFrame* firstLeaf = aFrame; michael@0: while (!IsBidiLeaf(firstLeaf)) { michael@0: firstLeaf = firstLeaf->GetFirstPrincipalChild(); michael@0: } michael@0: return NS_GET_BASE_LEVEL(firstLeaf); michael@0: } michael@0: michael@0: void michael@0: nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame, michael@0: nsContinuationStates* aContinuationStates, michael@0: bool& aIsFirst /* out */, michael@0: bool& aIsLast /* out */) michael@0: { michael@0: /* michael@0: * Since we lay out frames in the line's direction, visiting a frame with michael@0: * 'mFirstVisualFrame == nullptr', means it's the first appearance of one michael@0: * of its continuation chain frames on the line. michael@0: * To determine if it's the last visual frame of its continuation chain on michael@0: * the line or not, we count the number of frames of the chain on the line, michael@0: * and then reduce it when we lay out a frame of the chain. If this value michael@0: * becomes 1 it means that it's the last visual frame of its continuation michael@0: * chain on this line. michael@0: */ michael@0: michael@0: nsFrameContinuationState* frameState = aContinuationStates->GetEntry(aFrame); michael@0: nsFrameContinuationState* firstFrameState; michael@0: michael@0: if (!frameState->mFirstVisualFrame) { michael@0: // aFrame is the first visual frame of its continuation chain michael@0: nsFrameContinuationState* contState; michael@0: nsIFrame* frame; michael@0: michael@0: frameState->mFrameCount = 1; michael@0: frameState->mFirstVisualFrame = aFrame; michael@0: michael@0: /** michael@0: * Traverse continuation chain of aFrame in both backward and forward michael@0: * directions while the frames are on this line. Count the frames and michael@0: * set their mFirstVisualFrame to aFrame. michael@0: */ michael@0: // Traverse continuation chain backward michael@0: for (frame = aFrame->GetPrevContinuation(); michael@0: frame && (contState = aContinuationStates->GetEntry(frame)); michael@0: frame = frame->GetPrevContinuation()) { michael@0: frameState->mFrameCount++; michael@0: contState->mFirstVisualFrame = aFrame; michael@0: } michael@0: frameState->mHasContOnPrevLines = (frame != nullptr); michael@0: michael@0: // Traverse continuation chain forward michael@0: for (frame = aFrame->GetNextContinuation(); michael@0: frame && (contState = aContinuationStates->GetEntry(frame)); michael@0: frame = frame->GetNextContinuation()) { michael@0: frameState->mFrameCount++; michael@0: contState->mFirstVisualFrame = aFrame; michael@0: } michael@0: frameState->mHasContOnNextLines = (frame != nullptr); michael@0: michael@0: aIsFirst = !frameState->mHasContOnPrevLines; michael@0: firstFrameState = frameState; michael@0: } else { michael@0: // aFrame is not the first visual frame of its continuation chain michael@0: aIsFirst = false; michael@0: firstFrameState = aContinuationStates->GetEntry(frameState->mFirstVisualFrame); michael@0: } michael@0: michael@0: aIsLast = (firstFrameState->mFrameCount == 1 && michael@0: !firstFrameState->mHasContOnNextLines); michael@0: michael@0: if ((aIsFirst || aIsLast) && michael@0: (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) { michael@0: // For ib splits, don't treat anything except the last part as michael@0: // endmost or anything except the first part as startmost. michael@0: // As an optimization, only get the first continuation once. michael@0: nsIFrame* firstContinuation = aFrame->FirstContinuation(); michael@0: if (firstContinuation->FrameIsNonLastInIBSplit()) { michael@0: // We are not endmost michael@0: aIsLast = false; michael@0: } michael@0: if (firstContinuation->FrameIsNonFirstInIBSplit()) { michael@0: // We are not startmost michael@0: aIsFirst = false; michael@0: } michael@0: } michael@0: michael@0: // Reduce number of remaining frames of the continuation chain on the line. michael@0: firstFrameState->mFrameCount--; michael@0: } michael@0: michael@0: void michael@0: nsBidiPresUtils::RepositionFrame(nsIFrame* aFrame, michael@0: bool aIsEvenLevel, michael@0: nscoord& aStart, michael@0: nsContinuationStates* aContinuationStates, michael@0: WritingMode aLineWM, michael@0: nscoord& aLineWidth) michael@0: { michael@0: if (!aFrame) michael@0: return; michael@0: michael@0: bool isFirst, isLast; michael@0: IsFirstOrLast(aFrame, michael@0: aContinuationStates, michael@0: isFirst /* out */, michael@0: isLast /* out */); michael@0: michael@0: WritingMode frameWM = aFrame->GetWritingMode(); michael@0: nsInlineFrame* testFrame = do_QueryFrame(aFrame); michael@0: michael@0: if (testFrame) { michael@0: aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET); michael@0: michael@0: if (isFirst) { michael@0: aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST); michael@0: } else { michael@0: aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST); michael@0: } michael@0: michael@0: if (isLast) { michael@0: aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST); michael@0: } else { michael@0: aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST); michael@0: } michael@0: } michael@0: // This method is called from nsBlockFrame::PlaceLine via the call to michael@0: // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines michael@0: // have been reflowed, which is required for GetUsedMargin/Border/Padding michael@0: LogicalMargin margin(frameWM, aFrame->GetUsedMargin()); michael@0: if (isFirst) { michael@0: aStart += margin.IStart(frameWM); michael@0: } michael@0: michael@0: nscoord start = aStart; michael@0: nscoord frameWidth = aFrame->GetSize().width; michael@0: michael@0: if (!IsBidiLeaf(aFrame)) michael@0: { michael@0: nscoord iCoord = 0; michael@0: LogicalMargin borderPadding(frameWM, aFrame->GetUsedBorderAndPadding()); michael@0: if (isFirst) { michael@0: iCoord += borderPadding.IStart(frameWM); michael@0: } michael@0: michael@0: // If the resolved direction of the container is different from the michael@0: // direction of the frame, we need to traverse the child list in reverse michael@0: // order, to make it O(n) we store the list locally and iterate the list michael@0: // in reverse michael@0: bool reverseOrder = aIsEvenLevel != frameWM.IsBidiLTR(); michael@0: nsTArray childList; michael@0: nsIFrame *frame = aFrame->GetFirstPrincipalChild(); michael@0: if (frame && reverseOrder) { michael@0: childList.AppendElement((nsIFrame*)nullptr); michael@0: while (frame) { michael@0: childList.AppendElement(frame); michael@0: frame = frame->GetNextSibling(); michael@0: } michael@0: frame = childList[childList.Length() - 1]; michael@0: } michael@0: michael@0: // Reposition the child frames michael@0: int32_t index = 0; michael@0: while (frame) { michael@0: RepositionFrame(frame, michael@0: aIsEvenLevel, michael@0: iCoord, michael@0: aContinuationStates, michael@0: frameWM, michael@0: frameWidth); michael@0: index++; michael@0: frame = reverseOrder ? michael@0: childList[childList.Length() - index - 1] : michael@0: frame->GetNextSibling(); michael@0: } michael@0: michael@0: if (isLast) { michael@0: iCoord += borderPadding.IEnd(frameWM); michael@0: } michael@0: aStart += iCoord; michael@0: } else { michael@0: aStart += frameWidth; michael@0: } michael@0: michael@0: LogicalRect logicalRect(aLineWM, aFrame->GetRect(), aLineWidth); michael@0: logicalRect.IStart(aLineWM) = start; michael@0: logicalRect.ISize(aLineWM) = aStart - start; michael@0: aFrame->SetRect(aLineWM, logicalRect, aLineWidth); michael@0: michael@0: if (isLast) { michael@0: aStart += margin.IEnd(frameWM); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBidiPresUtils::InitContinuationStates(nsIFrame* aFrame, michael@0: nsContinuationStates* aContinuationStates) michael@0: { michael@0: nsFrameContinuationState* state = aContinuationStates->PutEntry(aFrame); michael@0: state->mFirstVisualFrame = nullptr; michael@0: state->mFrameCount = 0; michael@0: michael@0: if (!IsBidiLeaf(aFrame)) { michael@0: // Continue for child frames michael@0: nsIFrame* frame; michael@0: for (frame = aFrame->GetFirstPrincipalChild(); michael@0: frame; michael@0: frame = frame->GetNextSibling()) { michael@0: InitContinuationStates(frame, michael@0: aContinuationStates); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBidiPresUtils::RepositionInlineFrames(BidiLineData *aBld, michael@0: nsIFrame* aFirstChild, michael@0: WritingMode aLineWM, michael@0: nscoord& aLineWidth) michael@0: { michael@0: nscoord startSpace = 0; michael@0: michael@0: // This method is called from nsBlockFrame::PlaceLine via the call to michael@0: // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines michael@0: // have been reflowed, which is required for GetUsedMargin/Border/Padding michael@0: WritingMode frameWM = aFirstChild->GetWritingMode(); michael@0: LogicalMargin margin(frameWM, aFirstChild->GetUsedMargin()); michael@0: if (!aFirstChild->GetPrevContinuation() && michael@0: !aFirstChild->FrameIsNonFirstInIBSplit()) michael@0: startSpace = margin.IStart(frameWM); michael@0: michael@0: nscoord start = LogicalRect(aLineWM, aFirstChild->GetRect(), michael@0: aLineWidth).IStart(aLineWM) - startSpace; michael@0: nsIFrame* frame; michael@0: int32_t count = aBld->mVisualFrames.Length(); michael@0: int32_t index; michael@0: nsContinuationStates continuationStates; michael@0: michael@0: // Initialize continuation states to (nullptr, 0) for michael@0: // each frame on the line. michael@0: for (index = 0; index < count; index++) { michael@0: InitContinuationStates(aBld->VisualFrameAt(index), &continuationStates); michael@0: } michael@0: michael@0: // Reposition frames in visual order michael@0: int32_t step, limit; michael@0: if (aLineWM.IsBidiLTR()) { michael@0: index = 0; michael@0: step = 1; michael@0: limit = count; michael@0: } else { michael@0: index = count - 1; michael@0: step = -1; michael@0: limit = -1; michael@0: } michael@0: for (; index != limit; index += step) { michael@0: frame = aBld->VisualFrameAt(index); michael@0: RepositionFrame(frame, michael@0: !(aBld->mLevels[aBld->mIndexMap[index]] & 1), michael@0: start, michael@0: &continuationStates, michael@0: aLineWM, michael@0: aLineWidth); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine, michael@0: int32_t aNumFramesOnLine, michael@0: nsIFrame** aFirstVisual, michael@0: nsIFrame** aLastVisual) michael@0: { michael@0: BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); michael@0: int32_t count = bld.FrameCount(); michael@0: michael@0: if (aFirstVisual) { michael@0: *aFirstVisual = bld.VisualFrameAt(0); michael@0: } michael@0: if (aLastVisual) { michael@0: *aLastVisual = bld.VisualFrameAt(count-1); michael@0: } michael@0: michael@0: return bld.mIsReordered; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame, michael@0: nsIFrame* aFirstFrameOnLine, michael@0: int32_t aNumFramesOnLine) michael@0: { michael@0: BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); michael@0: michael@0: int32_t count = bld.mVisualFrames.Length(); michael@0: michael@0: if (aFrame == nullptr && count) michael@0: return bld.VisualFrameAt(0); michael@0: michael@0: for (int32_t i = 0; i < count - 1; i++) { michael@0: if (bld.VisualFrameAt(i) == aFrame) { michael@0: return bld.VisualFrameAt(i+1); michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame* aFrame, michael@0: nsIFrame* aFirstFrameOnLine, michael@0: int32_t aNumFramesOnLine) michael@0: { michael@0: BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); michael@0: michael@0: int32_t count = bld.mVisualFrames.Length(); michael@0: michael@0: if (aFrame == nullptr && count) michael@0: return bld.VisualFrameAt(count-1); michael@0: michael@0: for (int32_t i = 1; i < count; i++) { michael@0: if (bld.VisualFrameAt(i) == aFrame) { michael@0: return bld.VisualFrameAt(i-1); michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: inline nsresult michael@0: nsBidiPresUtils::EnsureBidiContinuation(nsIFrame* aFrame, michael@0: nsIFrame** aNewFrame, michael@0: int32_t& aFrameIndex, michael@0: int32_t aStart, michael@0: int32_t aEnd) michael@0: { michael@0: NS_PRECONDITION(aNewFrame, "null OUT ptr"); michael@0: NS_PRECONDITION(aFrame, "aFrame is null"); michael@0: michael@0: aFrame->AdjustOffsetsForBidi(aStart, aEnd); michael@0: return CreateContinuation(aFrame, aNewFrame, false); michael@0: } michael@0: michael@0: void michael@0: nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData *aBpd, michael@0: nsIFrame* aFrame, michael@0: int32_t aFirstIndex, michael@0: int32_t aLastIndex, michael@0: int32_t& aOffset) michael@0: { michael@0: FrameProperties props = aFrame->Properties(); michael@0: nsBidiLevel embeddingLevel = michael@0: (nsBidiLevel)NS_PTR_TO_INT32(props.Get(nsIFrame::EmbeddingLevelProperty())); michael@0: nsBidiLevel baseLevel = michael@0: (nsBidiLevel)NS_PTR_TO_INT32(props.Get(nsIFrame::BaseLevelProperty())); michael@0: uint8_t paragraphDepth = michael@0: NS_PTR_TO_INT32(props.Get(nsIFrame::ParagraphDepthProperty())); michael@0: michael@0: for (int32_t index = aFirstIndex + 1; index <= aLastIndex; index++) { michael@0: nsIFrame* frame = aBpd->FrameAt(index); michael@0: if (frame == NS_BIDI_CONTROL_FRAME) { michael@0: ++aOffset; michael@0: } michael@0: else { michael@0: // Make the frame and its continuation ancestors fluid, michael@0: // so they can be reused or deleted by normal reflow code michael@0: FrameProperties frameProps = frame->Properties(); michael@0: frameProps.Set(nsIFrame::EmbeddingLevelProperty(), michael@0: NS_INT32_TO_PTR(embeddingLevel)); michael@0: frameProps.Set(nsIFrame::BaseLevelProperty(), michael@0: NS_INT32_TO_PTR(baseLevel)); michael@0: frameProps.Set(nsIFrame::ParagraphDepthProperty(), michael@0: NS_INT32_TO_PTR(paragraphDepth)); michael@0: frame->AddStateBits(NS_FRAME_IS_BIDI); michael@0: while (frame) { michael@0: nsIFrame* prev = frame->GetPrevContinuation(); michael@0: if (prev) { michael@0: MakeContinuationFluid(prev, frame); michael@0: frame = frame->GetParent(); michael@0: } else { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Make sure that the last continuation we made fluid does not itself have a michael@0: // fluid continuation (this can happen when re-resolving after dynamic changes michael@0: // to content) michael@0: nsIFrame* lastFrame = aBpd->FrameAt(aLastIndex); michael@0: MakeContinuationsNonFluidUpParentChain(lastFrame, lastFrame->GetNextInFlow()); michael@0: } michael@0: michael@0: nsresult michael@0: nsBidiPresUtils::FormatUnicodeText(nsPresContext* aPresContext, michael@0: char16_t* aText, michael@0: int32_t& aTextLength, michael@0: nsCharType aCharType, michael@0: bool aIsOddLevel) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: // ahmed michael@0: //adjusted for correct numeral shaping michael@0: uint32_t bidiOptions = aPresContext->GetBidi(); michael@0: switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) { michael@0: michael@0: case IBMBIDI_NUMERAL_HINDI: michael@0: HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI); michael@0: break; michael@0: michael@0: case IBMBIDI_NUMERAL_ARABIC: michael@0: HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC); michael@0: break; michael@0: michael@0: case IBMBIDI_NUMERAL_PERSIAN: michael@0: HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_PERSIAN); michael@0: break; michael@0: michael@0: case IBMBIDI_NUMERAL_REGULAR: michael@0: michael@0: switch (aCharType) { michael@0: michael@0: case eCharType_EuropeanNumber: michael@0: HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC); michael@0: break; michael@0: michael@0: case eCharType_ArabicNumber: michael@0: HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI); michael@0: break; michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: break; michael@0: michael@0: case IBMBIDI_NUMERAL_HINDICONTEXT: michael@0: if ( ( (GET_BIDI_OPTION_DIRECTION(bidiOptions)==IBMBIDI_TEXTDIRECTION_RTL) && (IS_ARABIC_DIGIT (aText[0])) ) || (eCharType_ArabicNumber == aCharType) ) michael@0: HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI); michael@0: else if (eCharType_EuropeanNumber == aCharType) michael@0: HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC); michael@0: break; michael@0: michael@0: case IBMBIDI_NUMERAL_PERSIANCONTEXT: michael@0: if ( ( (GET_BIDI_OPTION_DIRECTION(bidiOptions)==IBMBIDI_TEXTDIRECTION_RTL) && (IS_ARABIC_DIGIT (aText[0])) ) || (eCharType_ArabicNumber == aCharType) ) michael@0: HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_PERSIAN); michael@0: else if (eCharType_EuropeanNumber == aCharType) michael@0: HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC); michael@0: break; michael@0: michael@0: case IBMBIDI_NUMERAL_NOMINAL: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: StripBidiControlCharacters(aText, aTextLength); michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsBidiPresUtils::StripBidiControlCharacters(char16_t* aText, michael@0: int32_t& aTextLength) michael@0: { michael@0: if ( (nullptr == aText) || (aTextLength < 1) ) { michael@0: return; michael@0: } michael@0: michael@0: int32_t stripLen = 0; michael@0: michael@0: for (int32_t i = 0; i < aTextLength; i++) { michael@0: // XXX: This silently ignores surrogate characters. michael@0: // As of Unicode 4.0, all Bidi control characters are within the BMP. michael@0: if (IsBidiControl((uint32_t)aText[i])) { michael@0: ++stripLen; michael@0: } michael@0: else { michael@0: aText[i - stripLen] = aText[i]; michael@0: } michael@0: } michael@0: aTextLength -= stripLen; michael@0: } michael@0: michael@0: #if 0 // XXX: for the future use ??? michael@0: void michael@0: RemoveDiacritics(char16_t* aText, michael@0: int32_t& aTextLength) michael@0: { michael@0: if (aText && (aTextLength > 0) ) { michael@0: int32_t offset = 0; michael@0: michael@0: for (int32_t i = 0; i < aTextLength && aText[i]; i++) { michael@0: if (IS_BIDI_DIACRITIC(aText[i]) ) { michael@0: ++offset; michael@0: continue; michael@0: } michael@0: aText[i - offset] = aText[i]; michael@0: } michael@0: aTextLength = i - offset; michael@0: aText[aTextLength] = 0; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: nsBidiPresUtils::CalculateCharType(nsBidi* aBidiEngine, michael@0: const char16_t* aText, michael@0: int32_t& aOffset, michael@0: int32_t aCharTypeLimit, michael@0: int32_t& aRunLimit, michael@0: int32_t& aRunLength, michael@0: int32_t& aRunCount, michael@0: uint8_t& aCharType, michael@0: uint8_t& aPrevCharType) michael@0: michael@0: { michael@0: bool strongTypeFound = false; michael@0: int32_t offset; michael@0: nsCharType charType; michael@0: michael@0: aCharType = eCharType_OtherNeutral; michael@0: michael@0: for (offset = aOffset; offset < aCharTypeLimit; offset++) { michael@0: // Make sure we give RTL chartype to all characters that would be classified michael@0: // as Right-To-Left by a bidi platform. michael@0: // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.) michael@0: if (IS_HEBREW_CHAR(aText[offset]) ) { michael@0: charType = eCharType_RightToLeft; michael@0: } michael@0: else if (IS_ARABIC_ALPHABETIC(aText[offset]) ) { michael@0: charType = eCharType_RightToLeftArabic; michael@0: } michael@0: else { michael@0: aBidiEngine->GetCharTypeAt(offset, &charType); michael@0: } michael@0: michael@0: if (!CHARTYPE_IS_WEAK(charType) ) { michael@0: michael@0: if (strongTypeFound michael@0: && (charType != aPrevCharType) michael@0: && (CHARTYPE_IS_RTL(charType) || CHARTYPE_IS_RTL(aPrevCharType) ) ) { michael@0: // Stop at this point to ensure uni-directionality of the text michael@0: // (from platform's point of view). michael@0: // Also, don't mix Arabic and Hebrew content (since platform may michael@0: // provide BIDI support to one of them only). michael@0: aRunLength = offset - aOffset; michael@0: aRunLimit = offset; michael@0: ++aRunCount; michael@0: break; michael@0: } michael@0: michael@0: if ( (eCharType_RightToLeftArabic == aPrevCharType michael@0: || eCharType_ArabicNumber == aPrevCharType) michael@0: && eCharType_EuropeanNumber == charType) { michael@0: charType = eCharType_ArabicNumber; michael@0: } michael@0: michael@0: // Set PrevCharType to the last strong type in this frame michael@0: // (for correct numeric shaping) michael@0: aPrevCharType = charType; michael@0: michael@0: strongTypeFound = true; michael@0: aCharType = charType; michael@0: } michael@0: } michael@0: aOffset = offset; michael@0: } michael@0: michael@0: nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, michael@0: int32_t aLength, michael@0: nsBidiLevel aBaseLevel, michael@0: nsPresContext* aPresContext, michael@0: BidiProcessor& aprocessor, michael@0: Mode aMode, michael@0: nsBidiPositionResolve* aPosResolve, michael@0: int32_t aPosResolveCount, michael@0: nscoord* aWidth, michael@0: nsBidi* aBidiEngine) michael@0: { michael@0: NS_ASSERTION((aPosResolve == nullptr) != (aPosResolveCount > 0), "Incorrect aPosResolve / aPosResolveCount arguments"); michael@0: michael@0: int32_t runCount; michael@0: michael@0: nsAutoString textBuffer(aText, aLength); michael@0: michael@0: nsresult rv = aBidiEngine->SetPara(aText, aLength, aBaseLevel, nullptr); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = aBidiEngine->CountRuns(&runCount); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nscoord xOffset = 0; michael@0: nscoord width, xEndRun = 0; michael@0: nscoord totalWidth = 0; michael@0: int32_t i, start, limit, length; michael@0: uint32_t visualStart = 0; michael@0: uint8_t charType; michael@0: uint8_t prevType = eCharType_LeftToRight; michael@0: nsBidiLevel level; michael@0: michael@0: for(int nPosResolve=0; nPosResolve < aPosResolveCount; ++nPosResolve) michael@0: { michael@0: aPosResolve[nPosResolve].visualIndex = kNotFound; michael@0: aPosResolve[nPosResolve].visualLeftTwips = kNotFound; michael@0: aPosResolve[nPosResolve].visualWidth = kNotFound; michael@0: } michael@0: michael@0: for (i = 0; i < runCount; i++) { michael@0: nsBidiDirection dir; michael@0: rv = aBidiEngine->GetVisualRun(i, &start, &length, &dir); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = aBidiEngine->GetLogicalRun(start, &limit, &level); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: int32_t subRunLength = limit - start; michael@0: int32_t lineOffset = start; michael@0: int32_t typeLimit = std::min(limit, aLength); michael@0: int32_t subRunCount = 1; michael@0: int32_t subRunLimit = typeLimit; michael@0: michael@0: /* michael@0: * If |level| is even, i.e. the direction of the run is left-to-right, we michael@0: * render the subruns from left to right and increment the x-coordinate michael@0: * |xOffset| by the width of each subrun after rendering. michael@0: * michael@0: * If |level| is odd, i.e. the direction of the run is right-to-left, we michael@0: * render the subruns from right to left. We begin by incrementing |xOffset| by michael@0: * the width of the whole run, and then decrement it by the width of each michael@0: * subrun before rendering. After rendering all the subruns, we restore the michael@0: * x-coordinate of the end of the run for the start of the next run. michael@0: */ michael@0: michael@0: if (level & 1) { michael@0: aprocessor.SetText(aText + start, subRunLength, nsBidiDirection(level & 1)); michael@0: width = aprocessor.GetWidth(); michael@0: xOffset += width; michael@0: xEndRun = xOffset; michael@0: } michael@0: michael@0: while (subRunCount > 0) { michael@0: // CalculateCharType can increment subRunCount if the run michael@0: // contains mixed character types michael@0: CalculateCharType(aBidiEngine, aText, lineOffset, typeLimit, subRunLimit, subRunLength, subRunCount, charType, prevType); michael@0: michael@0: nsAutoString runVisualText; michael@0: runVisualText.Assign(aText + start, subRunLength); michael@0: if (int32_t(runVisualText.Length()) < subRunLength) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), subRunLength, michael@0: (nsCharType)charType, level & 1); michael@0: michael@0: aprocessor.SetText(runVisualText.get(), subRunLength, nsBidiDirection(level & 1)); michael@0: width = aprocessor.GetWidth(); michael@0: totalWidth += width; michael@0: if (level & 1) { michael@0: xOffset -= width; michael@0: } michael@0: if (aMode == MODE_DRAW) { michael@0: aprocessor.DrawText(xOffset, width); michael@0: } michael@0: michael@0: /* michael@0: * The caller may request to calculate the visual position of one michael@0: * or more characters. michael@0: */ michael@0: for(int nPosResolve=0; nPosResolvevisualLeftTwips != kNotFound) michael@0: continue; michael@0: michael@0: /* michael@0: * First find out if the logical position is within this run. michael@0: */ michael@0: if (start <= posResolve->logicalIndex && michael@0: start + subRunLength > posResolve->logicalIndex) { michael@0: /* michael@0: * If this run is only one character long, we have an easy case: michael@0: * the visual position is the x-coord of the start of the run michael@0: * less the x-coord of the start of the whole text. michael@0: */ michael@0: if (subRunLength == 1) { michael@0: posResolve->visualIndex = visualStart; michael@0: posResolve->visualLeftTwips = xOffset; michael@0: posResolve->visualWidth = width; michael@0: } michael@0: /* michael@0: * Otherwise, we need to measure the width of the run's part michael@0: * which is to the visual left of the index. michael@0: * In other words, the run is broken in two, around the logical index, michael@0: * and we measure the part which is visually left. michael@0: * If the run is right-to-left, this part will span from after the index michael@0: * up to the end of the run; if it is left-to-right, this part will span michael@0: * from the start of the run up to (and inclduing) the character before the index. michael@0: */ michael@0: else { michael@0: /* michael@0: * Here is a description of how the width of the current character michael@0: * (posResolve->visualWidth) is calculated: michael@0: * michael@0: * LTR (current char: "P"): michael@0: * S A M P L E (logical index: 3, visual index: 3) michael@0: * ^ (visualLeftPart) michael@0: * ^ (visualRightSide) michael@0: * visualLeftLength == 3 michael@0: * ^^^^^^ (subWidth) michael@0: * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide) michael@0: * ^^ (posResolve->visualWidth) michael@0: * michael@0: * RTL (current char: "M"): michael@0: * E L P M A S (logical index: 2, visual index: 3) michael@0: * ^ (visualLeftPart) michael@0: * ^ (visualRightSide) michael@0: * visualLeftLength == 3 michael@0: * ^^^^^^ (subWidth) michael@0: * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide) michael@0: * ^^ (posResolve->visualWidth) michael@0: */ michael@0: nscoord subWidth; michael@0: // The position in the text where this run's "left part" begins. michael@0: const char16_t* visualLeftPart, *visualRightSide; michael@0: if (level & 1) { michael@0: // One day, son, this could all be replaced with mBidiEngine.GetVisualIndex ... michael@0: posResolve->visualIndex = visualStart + (subRunLength - (posResolve->logicalIndex + 1 - start)); michael@0: // Skipping to the "left part". michael@0: visualLeftPart = aText + posResolve->logicalIndex + 1; michael@0: // Skipping to the right side of the current character michael@0: visualRightSide = visualLeftPart - 1; michael@0: } michael@0: else { michael@0: posResolve->visualIndex = visualStart + (posResolve->logicalIndex - start); michael@0: // Skipping to the "left part". michael@0: visualLeftPart = aText + start; michael@0: // In LTR mode this is the same as visualLeftPart michael@0: visualRightSide = visualLeftPart; michael@0: } michael@0: // The delta between the start of the run and the left part's end. michael@0: int32_t visualLeftLength = posResolve->visualIndex - visualStart; michael@0: aprocessor.SetText(visualLeftPart, visualLeftLength, nsBidiDirection(level & 1)); michael@0: subWidth = aprocessor.GetWidth(); michael@0: aprocessor.SetText(visualRightSide, visualLeftLength + 1, nsBidiDirection(level & 1)); michael@0: posResolve->visualLeftTwips = xOffset + subWidth; michael@0: posResolve->visualWidth = aprocessor.GetWidth() - subWidth; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!(level & 1)) { michael@0: xOffset += width; michael@0: } michael@0: michael@0: --subRunCount; michael@0: start = lineOffset; michael@0: subRunLimit = typeLimit; michael@0: subRunLength = typeLimit - lineOffset; michael@0: } // while michael@0: if (level & 1) { michael@0: xOffset = xEndRun; michael@0: } michael@0: michael@0: visualStart += length; michael@0: } // for michael@0: michael@0: if (aWidth) { michael@0: *aWidth = totalWidth; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor : public nsBidiPresUtils::BidiProcessor { michael@0: public: michael@0: nsIRenderingContextBidiProcessor(nsRenderingContext* aCtx, michael@0: nsRenderingContext* aTextRunConstructionContext, michael@0: const nsPoint& aPt) michael@0: : mCtx(aCtx), mTextRunConstructionContext(aTextRunConstructionContext), mPt(aPt) { } michael@0: michael@0: ~nsIRenderingContextBidiProcessor() michael@0: { michael@0: mCtx->SetTextRunRTL(false); michael@0: } michael@0: michael@0: virtual void SetText(const char16_t* aText, michael@0: int32_t aLength, michael@0: nsBidiDirection aDirection) MOZ_OVERRIDE michael@0: { michael@0: mTextRunConstructionContext->SetTextRunRTL(aDirection==NSBIDI_RTL); michael@0: mText = aText; michael@0: mLength = aLength; michael@0: } michael@0: michael@0: virtual nscoord GetWidth() MOZ_OVERRIDE michael@0: { michael@0: return mTextRunConstructionContext->GetWidth(mText, mLength); michael@0: } michael@0: michael@0: virtual void DrawText(nscoord aXOffset, michael@0: nscoord) MOZ_OVERRIDE michael@0: { michael@0: mCtx->FontMetrics()->DrawString(mText, mLength, mPt.x + aXOffset, mPt.y, michael@0: mCtx, mTextRunConstructionContext); michael@0: } michael@0: michael@0: private: michael@0: nsRenderingContext* mCtx; michael@0: nsRenderingContext* mTextRunConstructionContext; michael@0: nsPoint mPt; michael@0: const char16_t* mText; michael@0: int32_t mLength; michael@0: }; michael@0: michael@0: nsresult nsBidiPresUtils::ProcessTextForRenderingContext(const char16_t* aText, michael@0: int32_t aLength, michael@0: nsBidiLevel aBaseLevel, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsRenderingContext& aTextRunConstructionContext, michael@0: Mode aMode, michael@0: nscoord aX, michael@0: nscoord aY, michael@0: nsBidiPositionResolve* aPosResolve, michael@0: int32_t aPosResolveCount, michael@0: nscoord* aWidth) michael@0: { michael@0: nsIRenderingContextBidiProcessor processor(&aRenderingContext, &aTextRunConstructionContext, nsPoint(aX, aY)); michael@0: nsBidi bidiEngine; michael@0: return ProcessText(aText, aLength, aBaseLevel, aPresContext, processor, michael@0: aMode, aPosResolve, aPosResolveCount, aWidth, &bidiEngine); michael@0: } michael@0: michael@0: /* static */ michael@0: void nsBidiPresUtils::WriteReverse(const char16_t* aSrc, michael@0: uint32_t aSrcLength, michael@0: char16_t* aDest) michael@0: { michael@0: char16_t* dest = aDest + aSrcLength; michael@0: mozilla::unicode::ClusterIterator iter(aSrc, aSrcLength); michael@0: michael@0: while (!iter.AtEnd()) { michael@0: iter.Next(); michael@0: for (const char16_t *cp = iter; cp > aSrc; ) { michael@0: // Here we rely on the fact that there are no non-BMP mirrored pairs michael@0: // currently in Unicode, so we don't need to look for surrogates michael@0: *--dest = mozilla::unicode::GetMirroredChar(*--cp); michael@0: } michael@0: aSrc = iter; michael@0: } michael@0: michael@0: NS_ASSERTION(dest == aDest, "Whole string not copied"); michael@0: } michael@0: michael@0: /* static */ michael@0: bool nsBidiPresUtils::WriteLogicalToVisual(const char16_t* aSrc, michael@0: uint32_t aSrcLength, michael@0: char16_t* aDest, michael@0: nsBidiLevel aBaseDirection, michael@0: nsBidi* aBidiEngine) michael@0: { michael@0: const char16_t* src = aSrc; michael@0: nsresult rv = aBidiEngine->SetPara(src, aSrcLength, aBaseDirection, nullptr); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: michael@0: nsBidiDirection dir; michael@0: rv = aBidiEngine->GetDirection(&dir); michael@0: // NSBIDI_LTR returned from GetDirection means the whole text is LTR michael@0: if (NS_FAILED(rv) || dir == NSBIDI_LTR) { michael@0: return false; michael@0: } michael@0: michael@0: int32_t runCount; michael@0: rv = aBidiEngine->CountRuns(&runCount); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: michael@0: int32_t runIndex, start, length; michael@0: char16_t* dest = aDest; michael@0: michael@0: for (runIndex = 0; runIndex < runCount; ++runIndex) { michael@0: rv = aBidiEngine->GetVisualRun(runIndex, &start, &length, &dir); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: michael@0: src = aSrc + start; michael@0: michael@0: if (dir == NSBIDI_RTL) { michael@0: WriteReverse(src, length, dest); michael@0: dest += length; michael@0: } else { michael@0: do { michael@0: NS_ASSERTION(src >= aSrc && src < aSrc + aSrcLength, michael@0: "logical index out of range"); michael@0: NS_ASSERTION(dest < aDest + aSrcLength, "visual index out of range"); michael@0: *(dest++) = *(src++); michael@0: } while (--length); michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(static_cast(dest - aDest) == aSrcLength, michael@0: "whole string not copied"); michael@0: return true; michael@0: } michael@0: michael@0: void nsBidiPresUtils::CopyLogicalToVisual(const nsAString& aSource, michael@0: nsAString& aDest, michael@0: nsBidiLevel aBaseDirection, michael@0: bool aOverride) michael@0: { michael@0: aDest.SetLength(0); michael@0: uint32_t srcLength = aSource.Length(); michael@0: if (srcLength == 0) michael@0: return; michael@0: if (!aDest.SetLength(srcLength, fallible_t())) { michael@0: return; michael@0: } michael@0: nsAString::const_iterator fromBegin, fromEnd; michael@0: nsAString::iterator toBegin; michael@0: aSource.BeginReading(fromBegin); michael@0: aSource.EndReading(fromEnd); michael@0: aDest.BeginWriting(toBegin); michael@0: michael@0: if (aOverride) { michael@0: if (aBaseDirection == NSBIDI_RTL) { michael@0: // no need to use the converter -- just copy the string in reverse order michael@0: WriteReverse(fromBegin.get(), srcLength, toBegin.get()); michael@0: } else { michael@0: // if aOverride && aBaseDirection == NSBIDI_LTR, fall through to the michael@0: // simple copy michael@0: aDest.SetLength(0); michael@0: } michael@0: } else { michael@0: nsBidi bidiEngine; michael@0: if (!WriteLogicalToVisual(fromBegin.get(), srcLength, toBegin.get(), michael@0: aBaseDirection, &bidiEngine)) { michael@0: aDest.SetLength(0); michael@0: } michael@0: } michael@0: michael@0: if (aDest.IsEmpty()) { michael@0: // Either there was an error or the source is unidirectional michael@0: // left-to-right. In either case, just copy source to dest. michael@0: CopyUnicodeTo(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd), michael@0: aDest); michael@0: } michael@0: } michael@0: michael@0: /* static */ michael@0: nsBidiLevel michael@0: nsBidiPresUtils::BidiLevelFromStyle(nsStyleContext* aStyleContext) michael@0: { michael@0: if (aStyleContext->StyleTextReset()->mUnicodeBidi & michael@0: NS_STYLE_UNICODE_BIDI_PLAINTEXT) { michael@0: return NSBIDI_DEFAULT_LTR; michael@0: } michael@0: michael@0: if (aStyleContext->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { michael@0: return NSBIDI_RTL; michael@0: } michael@0: michael@0: return NSBIDI_LTR; michael@0: }