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