Raw File
nsBidiPresUtils.cpp
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifdef IBMBIDI

#include "nsBidiPresUtils.h"
#include "nsGkAtoms.h"
#include "nsPresContext.h"
#include "nsRenderingContext.h"
#include "nsBidiUtils.h"
#include "nsCSSFrameConstructor.h"
#include "nsContainerFrame.h"
#include "nsInlineFrame.h"
#include "nsPlaceholderFrame.h"
#include "nsFirstLetterFrame.h"
#include "nsUnicodeProperties.h"
#include "nsTextFrame.h"
#include "nsBlockFrame.h"
#include "nsIFrameInlines.h"
#include <algorithm>

#undef NOISY_BIDI
#undef REALLY_NOISY_BIDI

using namespace mozilla;

static const PRUnichar kSpace            = 0x0020;
static const PRUnichar kZWSP             = 0x200B;
static const PRUnichar kLineSeparator    = 0x2028;
static const PRUnichar kObjectSubstitute = 0xFFFC;
static const PRUnichar kLRE              = 0x202A;
static const PRUnichar kRLE              = 0x202B;
static const PRUnichar kLRO              = 0x202D;
static const PRUnichar kRLO              = 0x202E;
static const PRUnichar kPDF              = 0x202C;

#define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1)

struct BidiParagraphData {
  nsString            mBuffer;
  nsAutoTArray<PRUnichar, 16> mEmbeddingStack;
  nsTArray<nsIFrame*> mLogicalFrames;
  nsTArray<nsLineBox*> mLinePerFrame;
  nsDataHashtable<nsISupportsHashKey, int32_t> mContentToFrameIndex;
  bool                mIsVisual;
  bool                mReset;
  nsBidiLevel         mParaLevel;
  nsIContent*         mPrevContent;
  nsAutoPtr<nsBidi>   mBidiEngine;
  nsIFrame*           mPrevFrame;
  nsAutoPtr<BidiParagraphData> mSubParagraph;
  uint8_t             mParagraphDepth;

  void Init(nsBlockFrame *aBlockFrame)
  {
    mBidiEngine = new nsBidi();
    mPrevContent = nullptr;
    mParagraphDepth = 0;

    mParaLevel = nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame->StyleContext());

    mIsVisual = aBlockFrame->PresContext()->IsVisualMode();
    if (mIsVisual) {
      /**
       * Drill up in content to detect whether this is an element that needs to
       * be rendered with logical order even on visual pages.
       *
       * We always use logical order on form controls, firstly so that text
       * entry will be in logical order, but also because visual pages were
       * written with the assumption that even if the browser had no support
       * for right-to-left text rendering, it would use native widgets with
       * bidi support to display form controls.
       *
       * We also use logical order in XUL elements, since we expect that if a
       * XUL element appears in a visual page, it will be generated by an XBL
       * binding and contain localized text which will be in logical order.
       */
      for (nsIContent* content = aBlockFrame->GetContent() ; content; 
           content = content->GetParent()) {
        if (content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) ||
            content->IsXUL()) {
          mIsVisual = false;
          break;
        }
      }
    }
  }

  BidiParagraphData* GetSubParagraph()
  {
    if (!mSubParagraph) {
      mSubParagraph = new BidiParagraphData();
      mSubParagraph->Init(this);
    }

    return mSubParagraph;
  }

  // Initialise a sub-paragraph from its containing paragraph
  void Init(BidiParagraphData *aBpd)
  {
    mBidiEngine = new nsBidi();
    mPrevContent = nullptr;
    mIsVisual = aBpd->mIsVisual;
    mReset = false;
  }

  void Reset(nsIFrame* aBDIFrame, BidiParagraphData *aBpd)
  {
    mReset = true;
    mLogicalFrames.Clear();
    mLinePerFrame.Clear();
    mContentToFrameIndex.Clear();
    mBuffer.SetLength(0);
    mPrevFrame = aBpd->mPrevFrame;
    mParagraphDepth = aBpd->mParagraphDepth + 1;

    const nsStyleTextReset* text = aBDIFrame->StyleTextReset();
    bool isRTL = (NS_STYLE_DIRECTION_RTL ==
                  aBDIFrame->StyleVisibility()->mDirection);

    if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
      mParaLevel = NSBIDI_DEFAULT_LTR;
    } else {
      mParaLevel = mParagraphDepth * 2;
      if (isRTL) ++mParaLevel;
    }

    if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_OVERRIDE) {
      PushBidiControl(isRTL ? kRLO : kLRO);
    }
  }

  void EmptyBuffer()
  {
    mBuffer.SetLength(0);
  }

  nsresult SetPara()
  {
    return mBidiEngine->SetPara(mBuffer.get(), BufferLength(),
                                mParaLevel, nullptr);
  }

  /**
   * mParaLevel can be NSBIDI_DEFAULT_LTR as well as NSBIDI_LTR or NSBIDI_RTL.
   * GetParaLevel() returns the actual (resolved) paragraph level which is
   * always either NSBIDI_LTR or NSBIDI_RTL
   */
  nsBidiLevel GetParaLevel()
  {
    nsBidiLevel paraLevel = mParaLevel;
    if (IS_DEFAULT_LEVEL(paraLevel)) {
      mBidiEngine->GetParaLevel(&paraLevel);
    }
    return paraLevel;
  }

  nsBidiDirection GetDirection()
  {
    nsBidiDirection dir;
    mBidiEngine->GetDirection(&dir);
    return dir;
  }

  nsresult CountRuns(int32_t *runCount){ return mBidiEngine->CountRuns(runCount); }

  nsresult GetLogicalRun(int32_t aLogicalStart, 
                         int32_t* aLogicalLimit,
                         nsBidiLevel* aLevel)
  {
    nsresult rv = mBidiEngine->GetLogicalRun(aLogicalStart,
                                             aLogicalLimit, aLevel);
    if (mIsVisual || NS_FAILED(rv))
      *aLevel = GetParaLevel();
    return rv;
  }

  void ResetData()
  {
    mLogicalFrames.Clear();
    mLinePerFrame.Clear();
    mContentToFrameIndex.Clear();
    mBuffer.SetLength(0);
    mPrevContent = nullptr;
    for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) {
      mBuffer.Append(mEmbeddingStack[i]);
      mLogicalFrames.AppendElement(NS_BIDI_CONTROL_FRAME);
      mLinePerFrame.AppendElement((nsLineBox*)nullptr);
    }
  }

  void ResetForNewBlock()
  {
    for (BidiParagraphData* bpd = this; bpd; bpd = bpd->mSubParagraph) {
      bpd->mPrevFrame = nullptr;
    }
  }

  void AppendFrame(nsIFrame* aFrame,
                   nsBlockInFlowLineIterator* aLineIter,
                   nsIContent* aContent = nullptr)
  {
    if (aContent) {
      mContentToFrameIndex.Put(aContent, FrameCount());
    }
    mLogicalFrames.AppendElement(aFrame);

    AdvanceLineIteratorToFrame(aFrame, aLineIter, mPrevFrame);
    mLinePerFrame.AppendElement(aLineIter->GetLine().get());
  }

  void AdvanceAndAppendFrame(nsIFrame** aFrame,
                             nsBlockInFlowLineIterator* aLineIter,
                             nsIFrame** aNextSibling)
  {
    nsIFrame* frame = *aFrame;
    nsIFrame* nextSibling = *aNextSibling;

    frame = frame->GetNextContinuation();
    if (frame) {
      AppendFrame(frame, aLineIter, nullptr);

      /*
       * If we have already overshot the saved next-sibling while
       * scanning the frame's continuations, advance it.
       */
      if (frame == nextSibling) {
        nextSibling = frame->GetNextSibling();
      }
    }

    *aFrame = frame;
    *aNextSibling = nextSibling;
  }

  int32_t GetLastFrameForContent(nsIContent *aContent)
  {
    int32_t index = 0;
    mContentToFrameIndex.Get(aContent, &index);
    return index;
  }

  int32_t FrameCount(){ return mLogicalFrames.Length(); }

  int32_t BufferLength(){ return mBuffer.Length(); }

  nsIFrame* FrameAt(int32_t aIndex){ return mLogicalFrames[aIndex]; }

  nsLineBox* GetLineForFrameAt(int32_t aIndex){ return mLinePerFrame[aIndex]; }

  void AppendUnichar(PRUnichar aCh){ mBuffer.Append(aCh); }

  void AppendString(const nsDependentSubstring& aString){ mBuffer.Append(aString); }

  void AppendControlChar(PRUnichar aCh)
  {
    mLogicalFrames.AppendElement(NS_BIDI_CONTROL_FRAME);
    mLinePerFrame.AppendElement((nsLineBox*)nullptr);
    AppendUnichar(aCh);
  }

  void PushBidiControl(PRUnichar aCh)
  {
    AppendControlChar(aCh);
    mEmbeddingStack.AppendElement(aCh);
  }

  void PopBidiControl()
  {
    AppendControlChar(kPDF);
    NS_ASSERTION(mEmbeddingStack.Length(), "embedding/override underflow");
    mEmbeddingStack.TruncateLength(mEmbeddingStack.Length() - 1);
  }

  void ClearBidiControls()
  {
    for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) {
      AppendControlChar(kPDF);
    }
  }

  static bool
  IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter,
                       nsIFrame* aPrevFrame, nsIFrame* aFrame)
  {
    nsIFrame* endFrame = aLineIter->IsLastLineInList() ? nullptr :
      aLineIter->GetLine().next()->mFirstChild;
    nsIFrame* startFrame = aPrevFrame ? aPrevFrame : aLineIter->GetLine()->mFirstChild;
    for (nsIFrame* frame = startFrame; frame && frame != endFrame;
         frame = frame->GetNextSibling()) {
      if (frame == aFrame)
        return true;
    }
    return false;
  }

  static void
  AdvanceLineIteratorToFrame(nsIFrame* aFrame,
                             nsBlockInFlowLineIterator* aLineIter,
                             nsIFrame*& aPrevFrame)
  {
    // Advance aLine to the line containing aFrame
    nsIFrame* child = aFrame;
    nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
    while (parent && !nsLayoutUtils::GetAsBlock(parent)) {
      child = parent;
      parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
    }
    NS_ASSERTION (parent, "aFrame is not a descendent of aBlockFrame");
    while (!IsFrameInCurrentLine(aLineIter, aPrevFrame, child)) {
#ifdef DEBUG
      bool hasNext =
#endif
        aLineIter->Next();
      NS_ASSERTION(hasNext, "Can't find frame in lines!");
      aPrevFrame = nullptr;
    }
    aPrevFrame = child;
  }

};

struct BidiLineData {
  nsTArray<nsIFrame*> mLogicalFrames;
  nsTArray<nsIFrame*> mVisualFrames;
  nsTArray<int32_t> mIndexMap;
  nsAutoTArray<uint8_t, 18> mLevels;
  bool mIsReordered;

  BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t   aNumFramesOnLine)
  {
    /**
     * Initialize the logically-ordered array of frames using the top-level
     * frames of a single line
     */
    mLogicalFrames.Clear();

    bool isReordered = false;
    bool hasRTLFrames = false;

    for (nsIFrame* frame = aFirstFrameOnLine;
         frame && aNumFramesOnLine--;
         frame = frame->GetNextSibling()) {
      AppendFrame(frame);
      uint8_t level = nsBidiPresUtils::GetFrameEmbeddingLevel(frame);
      mLevels.AppendElement(level);
      mIndexMap.AppendElement(0);
      if (level & 1) {
        hasRTLFrames = true;
      }
    }

    // Reorder the line
    nsBidi::ReorderVisual(mLevels.Elements(), FrameCount(),
                          mIndexMap.Elements());

    for (int32_t i = 0; i < FrameCount(); i++) {
      mVisualFrames.AppendElement(LogicalFrameAt(mIndexMap[i]));
      if (i != mIndexMap[i]) {
        isReordered = true;
      }
    }

    // If there's an RTL frame, assume the line is reordered
    mIsReordered = isReordered || hasRTLFrames;
  }

  void AppendFrame(nsIFrame* aFrame)
  {
    mLogicalFrames.AppendElement(aFrame); 
  }

  int32_t FrameCount(){ return mLogicalFrames.Length(); }

  nsIFrame* LogicalFrameAt(int32_t aIndex){ return mLogicalFrames[aIndex]; }

  nsIFrame* VisualFrameAt(int32_t aIndex){ return mVisualFrames[aIndex]; }
};

/* Some helper methods for Resolve() */

// Should this frame be split between text runs?
static bool
IsBidiSplittable(nsIFrame* aFrame)
{
  // Bidi inline containers should be split, unless they're line frames.
  nsIAtom* frameType = aFrame->GetType();
  return (aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer) &&
          frameType != nsGkAtoms::lineFrame) ||
         frameType == nsGkAtoms::textFrame;
}

// Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
static bool
IsBidiLeaf(nsIFrame* aFrame)
{
  nsIFrame* kid = aFrame->GetFirstPrincipalChild();
  return !kid || !aFrame->IsFrameOfType(nsIFrame::eBidiInlineContainer);
}

/**
 * Create non-fluid continuations for the ancestors of a given frame all the way
 * up the frame tree until we hit a non-splittable frame (a line or a block).
 *
 * @param aParent the first parent frame to be split
 * @param aFrame the child frames after this frame are reparented to the
 *        newly-created continuation of aParent.
 *        If aFrame is null, all the children of aParent are reparented.
 */
static nsresult
SplitInlineAncestors(nsIFrame* aParent,
                     nsIFrame* aFrame)
{
  nsPresContext *presContext = aParent->PresContext();
  nsIPresShell *presShell = presContext->PresShell();
  nsIFrame* frame = aFrame;
  nsIFrame* parent = aParent;
  nsIFrame* newParent;

  while (IsBidiSplittable(parent)) {
    nsIFrame* grandparent = parent->GetParent();
    NS_ASSERTION(grandparent, "Couldn't get parent's parent in nsBidiPresUtils::SplitInlineAncestors");
    
    // Split the child list after |frame|, unless it is the last child.
    if (!frame || frame->GetNextSibling()) {
    
      newParent = presShell->FrameConstructor()->
        CreateContinuingFrame(presContext, parent, grandparent, false);

      nsContainerFrame* container = do_QueryFrame(parent);
      nsFrameList tail = container->StealFramesAfter(frame);

      // Reparent views as necessary
      nsresult rv;
      rv = nsContainerFrame::ReparentFrameViewList(presContext, tail, parent, newParent);
      if (NS_FAILED(rv)) {
        return rv;
      }

      // The parent's continuation adopts the siblings after the split.
      rv = newParent->InsertFrames(nsIFrame::kNoReflowPrincipalList, nullptr, tail);
      if (NS_FAILED(rv)) {
        return rv;
      }
    
      // The list name kNoReflowPrincipalList would indicate we don't want reflow
      nsFrameList temp(newParent, newParent);
      rv = grandparent->InsertFrames(nsIFrame::kNoReflowPrincipalList, parent, temp);
      if (NS_FAILED(rv)) {
        return rv;
      }
    }
    
    frame = parent;
    parent = grandparent;
  }
  
  return NS_OK;
}

static void
MakeContinuationFluid(nsIFrame* aFrame, nsIFrame* aNext)
{
  NS_ASSERTION (!aFrame->GetNextInFlow() || aFrame->GetNextInFlow() == aNext, 
                "next-in-flow is not next continuation!");
  aFrame->SetNextInFlow(aNext);

  NS_ASSERTION (!aNext->GetPrevInFlow() || aNext->GetPrevInFlow() == aFrame,
                "prev-in-flow is not prev continuation!");
  aNext->SetPrevInFlow(aFrame);
}

static void
MakeContinuationsNonFluidUpParentChain(nsIFrame* aFrame, nsIFrame* aNext)
{
  nsIFrame* frame;
  nsIFrame* next;

  for (frame = aFrame, next = aNext;
       frame && next &&
         next != frame && next == frame->GetNextInFlow() &&
         IsBidiSplittable(frame);
       frame = frame->GetParent(), next = next->GetParent()) {

    frame->SetNextContinuation(next);
    next->SetPrevContinuation(frame);
  }
}

// If aFrame is the last child of its parent, convert bidi continuations to
// fluid continuations for all of its inline ancestors.
// If it isn't the last child, make sure that its continuation is fluid.
static void
JoinInlineAncestors(nsIFrame* aFrame)
{
  nsIFrame* frame = aFrame;
  do {
    nsIFrame* next = frame->GetNextContinuation();
    if (next) {
      // Don't join frames if they come from different paragraph depths (i.e.
      // one is bidi isolated relative to the other
      if (nsBidiPresUtils::GetParagraphDepth(frame) ==
          nsBidiPresUtils::GetParagraphDepth(next)) {
        MakeContinuationFluid(frame, next);
      }
    }
    // Join the parent only as long as we're its last child.
    if (frame->GetNextSibling())
      break;
    frame = frame->GetParent();
  } while (frame && IsBidiSplittable(frame));
}

static nsresult
CreateContinuation(nsIFrame*  aFrame,
                   nsIFrame** aNewFrame,
                   bool       aIsFluid)
{
  NS_PRECONDITION(aNewFrame, "null OUT ptr");
  NS_PRECONDITION(aFrame, "null ptr");

  *aNewFrame = nullptr;

  nsPresContext *presContext = aFrame->PresContext();
  nsIPresShell *presShell = presContext->PresShell();
  NS_ASSERTION(presShell, "PresShell must be set on PresContext before calling nsBidiPresUtils::CreateContinuation");

  nsIFrame* parent = aFrame->GetParent();
  NS_ASSERTION(parent, "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");

  nsresult rv = NS_OK;
  
  // Have to special case floating first letter frames because the continuation
  // doesn't go in the first letter frame. The continuation goes with the rest
  // of the text that the first letter frame was made out of.
  if (parent->GetType() == nsGkAtoms::letterFrame &&
      parent->IsFloating()) {
    nsFirstLetterFrame* letterFrame = do_QueryFrame(parent);
    rv = letterFrame->CreateContinuationForFloatingParent(presContext, aFrame,
                                                          aNewFrame, aIsFluid);
    return rv;
  }

  *aNewFrame = presShell->FrameConstructor()->
    CreateContinuingFrame(presContext, aFrame, parent, aIsFluid);

  // The list name kNoReflowPrincipalList would indicate we don't want reflow
  // XXXbz this needs higher-level framelist love
  nsFrameList temp(*aNewFrame, *aNewFrame);
  rv = parent->InsertFrames(nsIFrame::kNoReflowPrincipalList, aFrame, temp);
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (!aIsFluid) {  
    // Split inline ancestor frames
    rv = SplitInlineAncestors(parent, aFrame);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  return NS_OK;
}

/*
 * Overview of the implementation of Resolve():
 *
 *  Walk through the descendants of aBlockFrame and build:
 *   * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
 *   * mBuffer: an nsString containing a representation of
 *     the content of the frames.
 *     In the case of text frames, this is the actual text context of the
 *     frames, but some other elements are represented in a symbolic form which
 *     will make the Unicode Bidi Algorithm give the correct results.
 *     Bidi embeddings and overrides set by CSS or <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;
  bpd.Init(aBlockFrame);

  // Handle bidi-override being set on the block itself before calling
  // TraverseFrames.
  const nsStyleTextReset* text = aBlockFrame->StyleTextReset();
  PRUnichar ch = 0;
  if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_OVERRIDE) {
    const nsStyleVisibility* vis = aBlockFrame->StyleVisibility();
    if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
      ch = kRLO;
    }
    else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) {
      ch = kLRO;
    }
    if (ch != 0) {
      bpd.PushBidiControl(ch);
    }
  }
  for (nsBlockFrame* block = aBlockFrame; block;
       block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
    block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
    nsBlockInFlowLineIterator lineIter(block, block->begin_lines());
    bpd.ResetForNewBlock();
    TraverseFrames(aBlockFrame, &lineIter, block->GetFirstPrincipalChild(), &bpd);
    // XXX what about overflow lines?
  }

  if (ch != 0) {
    bpd.PopBidiControl();
  }

  BidiParagraphData* subParagraph = bpd.GetSubParagraph();
  if (subParagraph->BufferLength()) {
    ResolveParagraph(aBlockFrame, subParagraph);
    subParagraph->EmptyBuffer();
  }
  return ResolveParagraph(aBlockFrame, &bpd);
}

nsresult
nsBidiPresUtils::ResolveParagraph(nsBlockFrame* aBlockFrame,
                                  BidiParagraphData* aBpd)
{
  nsPresContext *presContext = aBlockFrame->PresContext();

  if (aBpd->BufferLength() < 1) {
    return NS_OK;
  }
  aBpd->mBuffer.ReplaceChar("\t\r\n", kSpace);

  int32_t runCount;

  nsresult rv = aBpd->SetPara();
  NS_ENSURE_SUCCESS(rv, rv);

  uint8_t embeddingLevel = aBpd->GetParaLevel();

  rv = aBpd->CountRuns(&runCount);
  NS_ENSURE_SUCCESS(rv, rv);

  int32_t     runLength      = 0;   // the length of the current run of text
  int32_t     lineOffset     = 0;   // the start of the current run
  int32_t     logicalLimit   = 0;   // the end of the current run + 1
  int32_t     numRun         = -1;
  int32_t     fragmentLength = 0;   // the length of the current text frame
  int32_t     frameIndex     = -1;  // index to the frames in mLogicalFrames
  int32_t     frameCount     = aBpd->FrameCount();
  int32_t     contentOffset  = 0;   // offset of current frame in its content node
  bool        isTextFrame    = false;
  nsIFrame*   frame = nullptr;
  nsIContent* content = nullptr;
  int32_t     contentTextLength = 0;

  FramePropertyTable *propTable = presContext->PropertyTable();
  nsLineBox* currentLine = nullptr;
  
#ifdef DEBUG
#ifdef NOISY_BIDI
  printf("Before Resolve(), aBlockFrame=0x%p, mBuffer='%s', frameCount=%d, runCount=%d\n",
         (void*)aBlockFrame, NS_ConvertUTF16toUTF8(aBpd->mBuffer).get(), frameCount, runCount);
#ifdef REALLY_NOISY_BIDI
  printf(" block frame tree=:\n");
  aBlockFrame->List(stdout, 0);
#endif
#endif
#endif

  if (runCount == 1 && frameCount == 1 &&
      aBpd->mParagraphDepth == 0 && aBpd->GetDirection() == NSBIDI_LTR &&
      aBpd->GetParaLevel() == 0) {
    // We have a single left-to-right frame in a left-to-right paragraph,
    // without bidi isolation from the surrounding text.
    // Make sure that the embedding level and base level frame properties aren't
    // set (because if they are this frame used to have some other direction,
    // so we can't do this optimization), and we're done.
    nsIFrame* frame = aBpd->FrameAt(0);
    if (frame != NS_BIDI_CONTROL_FRAME &&
        !frame->Properties().Get(nsIFrame::EmbeddingLevelProperty()) &&
        !frame->Properties().Get(nsIFrame::BaseLevelProperty())) {
#ifdef DEBUG
#ifdef NOISY_BIDI
      printf("early return for single direction frame %p\n", (void*)frame);
#endif
#endif
      return NS_OK;
    }
  }

  nsIFrame* firstFrame = nullptr;
  nsIFrame* lastFrame = nullptr;

  for (; ;) {
    if (fragmentLength <= 0) {
      // Get the next frame from mLogicalFrames
      if (++frameIndex >= frameCount) {
        break;
      }
      frame = aBpd->FrameAt(frameIndex);
      if (frame == NS_BIDI_CONTROL_FRAME ||
          nsGkAtoms::textFrame != frame->GetType()) {
        /*
         * Any non-text frame corresponds to a single character in the text buffer
         * (a bidi control character, LINE SEPARATOR, or OBJECT SUBSTITUTE)
         */
        isTextFrame = false;
        fragmentLength = 1;
      }
      else {
        if (!firstFrame) {
          firstFrame = frame;
        }
        lastFrame = frame;
        currentLine = aBpd->GetLineForFrameAt(frameIndex);
        content = frame->GetContent();
        if (!content) {
          rv = NS_OK;
          break;
        }
        contentTextLength = content->TextLength();
        if (contentTextLength == 0) {
          frame->AdjustOffsetsForBidi(0, 0);
          // Set the base level and embedding level of the current run even
          // on an empty frame. Otherwise frame reordering will not be correct.
          propTable->Set(frame, nsIFrame::EmbeddingLevelProperty(),
                         NS_INT32_TO_PTR(embeddingLevel));
          propTable->Set(frame, nsIFrame::BaseLevelProperty(),
                         NS_INT32_TO_PTR(aBpd->GetParaLevel()));
          propTable->Set(frame, nsIFrame::ParagraphDepthProperty(),
                         NS_INT32_TO_PTR(aBpd->mParagraphDepth));
          continue;
        }
        int32_t start, end;
        frame->GetOffsets(start, end);
        NS_ASSERTION(!(contentTextLength < end - start),
                     "Frame offsets don't fit in content");
        fragmentLength = std::min(contentTextLength, end - start);
        contentOffset = start;
        isTextFrame = true;
      }
    } // if (fragmentLength <= 0)

    if (runLength <= 0) {
      // Get the next run of text from the Bidi engine
      if (++numRun >= runCount) {
        break;
      }
      lineOffset = logicalLimit;
      if (NS_FAILED(aBpd->GetLogicalRun(
              lineOffset, &logicalLimit, &embeddingLevel) ) ) {
        break;
      }
      runLength = logicalLimit - lineOffset;
    } // if (runLength <= 0)

    if (frame == NS_BIDI_CONTROL_FRAME) {
      frame = nullptr;
      ++lineOffset;
    }
    else {
      propTable->Set(frame, nsIFrame::EmbeddingLevelProperty(),
                     NS_INT32_TO_PTR(embeddingLevel));
      propTable->Set(frame, nsIFrame::BaseLevelProperty(),
                     NS_INT32_TO_PTR(aBpd->GetParaLevel()));
      propTable->Set(frame, nsIFrame::ParagraphDepthProperty(),
                     NS_INT32_TO_PTR(aBpd->mParagraphDepth));
      if (isTextFrame) {
        if ( (runLength > 0) && (runLength < fragmentLength) ) {
          /*
           * The text in this frame continues beyond the end of this directional run.
           * Create a non-fluid continuation frame for the next directional run.
           */
          currentLine->MarkDirty();
          nsIFrame* nextBidi;
          int32_t runEnd = contentOffset + runLength;
          rv = EnsureBidiContinuation(frame, &nextBidi, frameIndex,
                                      contentOffset,
                                      runEnd);
          if (NS_FAILED(rv)) {
            break;
          }
          nextBidi->AdjustOffsetsForBidi(runEnd,
                                         contentOffset + fragmentLength);
          lastFrame = frame = nextBidi;
          contentOffset = runEnd;
        } // if (runLength < fragmentLength)
        else {
          if (contentOffset + fragmentLength == contentTextLength) {
            /* 
             * We have finished all the text in this content node. Convert any
             * further non-fluid continuations to fluid continuations and advance
             * frameIndex to the last frame in the content node
             */
            int32_t newIndex = aBpd->GetLastFrameForContent(content);
            if (newIndex > frameIndex) {
              currentLine->MarkDirty();
              RemoveBidiContinuation(aBpd, frame,
                                     frameIndex, newIndex, lineOffset);
              frameIndex = newIndex;
              lastFrame = frame = aBpd->FrameAt(frameIndex);
            }
          } else if (fragmentLength > 0 && runLength > fragmentLength) {
            /*
             * There is more text that belongs to this directional run in the next
             * text frame: make sure it is a fluid continuation of the current frame.
             * Do not advance frameIndex, because the next frame may contain
             * multi-directional text and need to be split
             */
            int32_t newIndex = frameIndex;
            do {
            } while (++newIndex < frameCount &&
                     aBpd->FrameAt(newIndex) == NS_BIDI_CONTROL_FRAME);
            if (newIndex < frameCount) {
              currentLine->MarkDirty();
              RemoveBidiContinuation(aBpd, frame,
                                     frameIndex, newIndex, lineOffset);
            }
          } else if (runLength == fragmentLength) {
            /*
             * If the directional run ends at the end of the frame, make sure
             * that any continuation is non-fluid, and do the same up the
             * parent chain
             */
            nsIFrame* next = frame->GetNextInFlow();
            if (next) {
              currentLine->MarkDirty();
              MakeContinuationsNonFluidUpParentChain(frame, next);
            }
          }
          frame->AdjustOffsetsForBidi(contentOffset, contentOffset + fragmentLength);
        }
      } // isTextFrame
      else {
        ++lineOffset;
      }
    } // not bidi control frame
    int32_t temp = runLength;
    runLength -= fragmentLength;
    fragmentLength -= temp;

    if (frame && fragmentLength <= 0) {
      // If the frame is at the end of a run, and this is not the end of our
      // paragrah, split all ancestor inlines that need splitting.
      // To determine whether we're at the end of the run, we check that we've
      // finished processing the current run, and that the current frame
      // doesn't have a fluid continuation (it could have a fluid continuation
      // of zero length, so testing runLength alone is not sufficient).
      if (runLength <= 0 && !frame->GetNextInFlow()) {
        if (numRun + 1 < runCount) {
          nsIFrame* child = frame;
          nsIFrame* parent = frame->GetParent();
          // As long as we're on the last sibling, the parent doesn't have to
          // be split.
          // However, if the parent has a fluid continuation, we do have to make
          // it non-fluid. This can happen e.g. when we have a first-letter
          // frame and the end of the first-letter coincides with the end of a
          // directional run.
          while (parent &&
                 IsBidiSplittable(parent) &&
                 !child->GetNextSibling()) {
            nsIFrame* next = parent->GetNextInFlow();
            if (next) {
              parent->SetNextContinuation(next);
              next->SetPrevContinuation(parent);
            }
            child = parent;
            parent = child->GetParent();
          }
          if (parent && IsBidiSplittable(parent)) {
            SplitInlineAncestors(parent, child);
          }
        }
      }
      else {
        // We're not at an end of a run. If |frame| is the last child of its
        // parent, and its ancestors happen to have bidi continuations, convert
        // them into fluid continuations.
        JoinInlineAncestors(frame);
      }
    }
  } // for

  if (aBpd->mParagraphDepth > 0) {
    nsIFrame* child;
    nsIFrame* parent;
    if (firstFrame) {
      child = firstFrame->GetParent();
      if (child) {
        parent = child->GetParent();
        if (parent && IsBidiSplittable(parent)) {
          nsIFrame* prev = child->GetPrevSibling();
          if (prev) {
            SplitInlineAncestors(parent, prev);
          }
        }
      }
    }
    if (lastFrame) {
      child = lastFrame->GetParent();
      if (child) {
        parent = child->GetParent();
        if (parent && IsBidiSplittable(parent)) {
          SplitInlineAncestors(parent, child);
        }
      }
    }
  }

#ifdef DEBUG
#ifdef REALLY_NOISY_BIDI
  printf("---\nAfter Resolve(), frameTree =:\n");
  aBlockFrame->List(stdout, 0);
  printf("===\n");
#endif
#endif

  return rv;
}

void
nsBidiPresUtils::TraverseFrames(nsBlockFrame*              aBlockFrame,
                                nsBlockInFlowLineIterator* aLineIter,
                                nsIFrame*                  aCurrentFrame,
                                BidiParagraphData*         aBpd)
{
  if (!aCurrentFrame)
    return;

#ifdef DEBUG
  nsBlockFrame* initialLineContainer = aLineIter->GetContainer();
#endif

  nsIFrame* childFrame = aCurrentFrame;
  do {
    /*
     * It's important to get the next sibling and next continuation *before*
     * handling the frame: If we encounter a forced paragraph break and call
     * ResolveParagraph within this loop, doing GetNextSibling and
     * GetNextContinuation after that could return a bidi continuation that had
     * just been split from the original childFrame and we would process it
     * twice.
     */
    nsIFrame* nextSibling = childFrame->GetNextSibling();
    bool isLastFrame = !childFrame->GetNextContinuation();
    bool isFirstFrame = !childFrame->GetPrevContinuation();

    // If the real frame for a placeholder is a first letter frame, we need to
    // drill down into it and include its contents in Bidi resolution.
    // If not, we just use the placeholder.
    nsIFrame* frame = childFrame;
    if (nsGkAtoms::placeholderFrame == childFrame->GetType()) {
      nsIFrame* realFrame =
        nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
      if (realFrame->GetType() == nsGkAtoms::letterFrame) {
        frame = realFrame;
      }
    }

    PRUnichar ch = 0;
    if (frame->IsFrameOfType(nsIFrame::eBidiInlineContainer)) {
      const nsStyleVisibility* vis = frame->StyleVisibility();
      const nsStyleTextReset* text = frame->StyleTextReset();
      if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_OVERRIDE) {
        if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
          ch = kRLO;
        }
        else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) {
          ch = kLRO;
        }
      } else if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_EMBED) {
        if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
          ch = kRLE;
        }
        else if (NS_STYLE_DIRECTION_LTR == vis->mDirection) {
          ch = kLRE;
        }
      }

      // Add a dummy frame pointer representing a bidi control code before the
      // first frame of an element specifying embedding or override
      if (ch != 0 && isFirstFrame) {
        aBpd->PushBidiControl(ch);
      }
    }

    if (IsBidiLeaf(frame)) {
      /* Bidi leaf frame: add the frame to the mLogicalFrames array,
       * and add its index to the mContentToFrameIndex hashtable. This
       * will be used in RemoveBidiContinuation() to identify the last
       * frame in the array with a given content.
       */
      nsIContent* content = frame->GetContent();
      aBpd->AppendFrame(frame, aLineIter, content);

      // Append the content of the frame to the paragraph buffer
      nsIAtom* frameType = frame->GetType();
      if (nsGkAtoms::textFrame == frameType) {
        if (content != aBpd->mPrevContent) {
          aBpd->mPrevContent = content;
          if (!frame->StyleText()->NewlineIsSignificant()) {
            content->AppendTextTo(aBpd->mBuffer);
          } else {
            /*
             * For preformatted text we have to do bidi resolution on each line
             * separately. 
             */
            nsAutoString text;
            content->AppendTextTo(text);
            nsIFrame* next;
            do {
              next = nullptr;

              int32_t start, end;
              frame->GetOffsets(start, end);
              int32_t endLine = text.FindChar('\n', start);
              if (endLine == -1) {
                /*
                 * If there is no newline in the text content, just save the
                 * text from this frame and its continuations, and do bidi
                 * resolution later
                 */
                aBpd->AppendString(Substring(text, start));
                while (frame && nextSibling) {
                  aBpd->AdvanceAndAppendFrame(&frame, aLineIter, &nextSibling);
                }
                break;
              }

              /*
               * If there is a newline in the frame, break the frame after the
               * newline, do bidi resolution and repeat until the last sibling
               */
              ++endLine;

              /*
               * If the frame ends before the new line, save the text and move
               * into the next continuation
               */
              aBpd->AppendString(Substring(text, start,
                                           std::min(end, endLine) - start));
              while (end < endLine && nextSibling) { 
                aBpd->AdvanceAndAppendFrame(&frame, aLineIter, &nextSibling);
                NS_ASSERTION(frame, "Premature end of continuation chain");
                frame->GetOffsets(start, end);
                aBpd->AppendString(Substring(text, start,
                                             std::min(end, endLine) - start));
              }

              if (end < endLine) {
                aBpd->mPrevContent = nullptr;
                break;
              }

              bool createdContinuation = false;
              if (uint32_t(endLine) < text.Length()) {
                /*
                 * Timing is everything here: if the frame already has a bidi
                 * continuation, we need to make the continuation fluid *before*
                 * resetting the length of the current frame. Otherwise
                 * nsTextFrame::SetLength won't set the continuation frame's
                 * text offsets correctly.
                 *
                 * On the other hand, if the frame doesn't have a continuation,
                 * we need to create one *after* resetting the length, or
                 * CreateContinuingFrame will complain that there is no more
                 * content for the continuation.               
                 */
                next = frame->GetNextInFlow();
                if (!next) {
                  // If the frame already has a bidi continuation, make it fluid
                  next = frame->GetNextContinuation();
                  if (next) {
                    MakeContinuationFluid(frame, next);
                    JoinInlineAncestors(frame);
                  }
                }

                nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
                textFrame->SetLength(endLine - start, nullptr);

                if (!next) {
                  // If the frame has no next in flow, create one.
                  CreateContinuation(frame, &next, true);
                  createdContinuation = true;
                }
                aBpd->GetLineForFrameAt(aBpd->FrameCount() - 1)->MarkDirty();
              }
              ResolveParagraphWithinBlock(aBlockFrame, aBpd);

              if (!nextSibling && !createdContinuation) {
                break;
              } else if (next) {
                frame = next;
                aBpd->AppendFrame(frame, aLineIter);
              }

              /*
               * If we have already overshot the saved next-sibling while
               * scanning the frame's continuations, advance it.
               */
              if (frame && frame == nextSibling) {
                nextSibling = frame->GetNextSibling();
              }

            } while (next);
          }
        }
      } else if (nsGkAtoms::brFrame == frameType) {
        // break frame -- append line separator
        aBpd->AppendUnichar(kLineSeparator);
        ResolveParagraphWithinBlock(aBlockFrame, aBpd);
      } else { 
        // other frame type -- see the Unicode Bidi Algorithm:
        // "...inline objects (such as graphics) are treated as if they are ...
        // U+FFFC"
        // <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->IsHTML(nsGkAtoms::wbr) ?
                            kZWSP : kObjectSubstitute);
        if (!frame->IsInlineOutside()) {
          // if it is not inline, end the paragraph
          ResolveParagraphWithinBlock(aBlockFrame, aBpd);
        }
      }
    } else {
      // For a non-leaf frame, recurse into TraverseFrames
      nsIFrame* kid = frame->GetFirstPrincipalChild();
      nsIFrame* overflowKid = frame->GetFirstChild(nsIFrame::kOverflowList);
      if (kid || overflowKid) {
        const nsStyleTextReset* text = frame->StyleTextReset();
        if (text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_ISOLATE ||
            text->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
          // css "unicode-bidi: isolate" and html5 bdi: 
          //  resolve the element as a separate paragraph
          BidiParagraphData* subParagraph = aBpd->GetSubParagraph();

          /*
           * As at the beginning of the loop, it's important to check for
           * next-continuations before handling the frame. If we do
           * TraverseFrames and *then* do GetNextContinuation on the original
           * first frame, it could return a bidi continuation that had only
           * just been created, and we would skip doing bidi resolution on the
           * last part of the sub-paragraph.
           */
          bool isLastContinuation = !frame->GetNextContinuation();
          if (!frame->GetPrevContinuation() || !subParagraph->mReset) {
            if (subParagraph->BufferLength()) {
              ResolveParagraph(aBlockFrame, subParagraph);
            }
            subParagraph->Reset(frame, aBpd);
          }
          TraverseFrames(aBlockFrame, aLineIter, kid, subParagraph);
          TraverseFrames(aBlockFrame, aLineIter, overflowKid, subParagraph);
          if (isLastContinuation) {
            ResolveParagraph(aBlockFrame, subParagraph);
            subParagraph->EmptyBuffer();
          }

          // Treat the element as a neutral character within its containing
          //  paragraph.
          aBpd->AppendControlChar(kObjectSubstitute);
        } else {
          TraverseFrames(aBlockFrame, aLineIter, kid, aBpd);
          TraverseFrames(aBlockFrame, aLineIter, overflowKid, aBpd);
        }
      }
    }

    // If the element is attributed by dir, indicate direction pop (add PDF frame)
    if (isLastFrame) {
      if (ch) {
        // Add a dummy frame pointer representing a bidi control code after the
        // last frame of an element specifying embedding or override
        aBpd->PopBidiControl();
      }
    }
    childFrame = nextSibling;
  } while (childFrame);

  MOZ_ASSERT(initialLineContainer == aLineIter->GetContainer());
}

void
nsBidiPresUtils::ResolveParagraphWithinBlock(nsBlockFrame* aBlockFrame,
                                             BidiParagraphData* aBpd)
{
  aBpd->ClearBidiControls();
  ResolveParagraph(aBlockFrame, aBpd);
  aBpd->ResetData();
}

void
nsBidiPresUtils::ReorderFrames(nsIFrame*            aFirstFrameOnLine,
                               int32_t              aNumFramesOnLine)
{
  // If this line consists of a line frame, reorder the line frame's children.
  if (aFirstFrameOnLine->GetType() == nsGkAtoms::lineFrame) {
    aFirstFrameOnLine = aFirstFrameOnLine->GetFirstPrincipalChild();
    if (!aFirstFrameOnLine)
      return;
    // All children of the line frame are on the first line. Setting aNumFramesOnLine
    // to -1 makes InitLogicalArrayFromLine look at all of them.
    aNumFramesOnLine = -1;
  }

  BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
  RepositionInlineFrames(&bld, aFirstFrameOnLine);
}

nsIFrame*
nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame)
{
  nsIFrame* firstLeaf = aFrame;
  while (!IsBidiLeaf(firstLeaf)) {
    nsIFrame* firstChild = firstLeaf->GetFirstPrincipalChild();
    nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(firstChild);
    firstLeaf = (realFrame->GetType() == nsGkAtoms::letterFrame) ?
                 realFrame : firstChild;
  }
  return firstLeaf;
}

nsBidiLevel
nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame)
{
  return NS_GET_EMBEDDING_LEVEL(nsBidiPresUtils::GetFirstLeaf(aFrame));
}

uint8_t
nsBidiPresUtils::GetParagraphDepth(nsIFrame* aFrame)
{
  return NS_GET_PARAGRAPH_DEPTH(nsBidiPresUtils::GetFirstLeaf(aFrame));
}


nsBidiLevel
nsBidiPresUtils::GetFrameBaseLevel(nsIFrame* aFrame)
{
  nsIFrame* firstLeaf = aFrame;
  while (!IsBidiLeaf(firstLeaf)) {
    firstLeaf = firstLeaf->GetFirstPrincipalChild();
  }
  return NS_GET_BASE_LEVEL(firstLeaf);
}

void
nsBidiPresUtils::IsLeftOrRightMost(nsIFrame*              aFrame,
                                   nsContinuationStates*  aContinuationStates,
                                   bool&                aIsLeftMost /* out */,
                                   bool&                aIsRightMost /* out */)
{
  const nsStyleVisibility* vis = aFrame->StyleVisibility();
  bool isLTR = (NS_STYLE_DIRECTION_LTR == vis->mDirection);

  /*
   * Since we lay out frames from left to right (in both LTR and RTL), visiting a
   * frame with 'mFirstVisualFrame == nullptr', means it's the first appearance of
   * one of its continuation chain frames on the line.
   * To determine if it's the last visual frame of its continuation chain on the line
   * or not, we count the number of frames of the chain on the line, and then reduce
   * it when we lay out a frame of the chain. If this value becomes 1 it means
   * that it's the last visual frame of its continuation chain on this line.
   */

  nsFrameContinuationState* frameState = aContinuationStates->GetEntry(aFrame);
  nsFrameContinuationState* firstFrameState;

  if (!frameState->mFirstVisualFrame) {
    // aFrame is the first visual frame of its continuation chain
    nsFrameContinuationState* contState;
    nsIFrame* frame;

    frameState->mFrameCount = 1;
    frameState->mFirstVisualFrame = aFrame;

    /**
     * Traverse continuation chain of aFrame in both backward and forward
     * directions while the frames are on this line. Count the frames and
     * set their mFirstVisualFrame to aFrame.
     */
    // Traverse continuation chain backward
    for (frame = aFrame->GetPrevContinuation();
         frame && (contState = aContinuationStates->GetEntry(frame));
         frame = frame->GetPrevContinuation()) {
      frameState->mFrameCount++;
      contState->mFirstVisualFrame = aFrame;
    }
    frameState->mHasContOnPrevLines = (frame != nullptr);

    // Traverse continuation chain forward
    for (frame = aFrame->GetNextContinuation();
         frame && (contState = aContinuationStates->GetEntry(frame));
         frame = frame->GetNextContinuation()) {
      frameState->mFrameCount++;
      contState->mFirstVisualFrame = aFrame;
    }
    frameState->mHasContOnNextLines = (frame != nullptr);

    aIsLeftMost = isLTR ? !frameState->mHasContOnPrevLines
                        : !frameState->mHasContOnNextLines;
    firstFrameState = frameState;
  } else {
    // aFrame is not the first visual frame of its continuation chain
    aIsLeftMost = false;
    firstFrameState = aContinuationStates->GetEntry(frameState->mFirstVisualFrame);
  }

  aIsRightMost = (firstFrameState->mFrameCount == 1) &&
                 (isLTR ? !firstFrameState->mHasContOnNextLines
                        : !firstFrameState->mHasContOnPrevLines);

  if ((aIsLeftMost || aIsRightMost) &&
      (aFrame->GetStateBits() & NS_FRAME_IS_SPECIAL)) {
    // 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
      if (isLTR) {
        aIsRightMost = false;
      } else {
        aIsLeftMost = false;
      }
    }
    if (firstContinuation->FrameIsNonFirstInIBSplit()) {
      // We are not startmost
      if (isLTR) {
        aIsLeftMost = false;
      } else {
        aIsRightMost = false;
      }
    }
  }

  // Reduce number of remaining frames of the continuation chain on the line.
  firstFrameState->mFrameCount--;
}

void
nsBidiPresUtils::RepositionFrame(nsIFrame*              aFrame,
                                 bool                   aIsOddLevel,
                                 nscoord&               aLeft,
                                 nsContinuationStates*  aContinuationStates)
{
  if (!aFrame)
    return;

  bool isLeftMost, isRightMost;
  IsLeftOrRightMost(aFrame,
                    aContinuationStates,
                    isLeftMost /* out */,
                    isRightMost /* out */);

  nsInlineFrame* testFrame = do_QueryFrame(aFrame);
  if (testFrame) {
    aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET);

    if (isLeftMost)
      aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LEFT_MOST);
    else
      aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LEFT_MOST);

    if (isRightMost)
      aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_RIGHT_MOST);
    else
      aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_RIGHT_MOST);
  }
  // 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
  nsMargin margin = aFrame->GetUsedMargin();
  if (isLeftMost)
    aLeft += margin.left;

  nscoord start = aLeft;

  if (!IsBidiLeaf(aFrame))
  {
    nscoord x = 0;
    nsMargin borderPadding = aFrame->GetUsedBorderAndPadding();
    if (isLeftMost) {
      x += borderPadding.left;
    }

    // If aIsOddLevel is true, so we need to traverse the child list
    // in reverse order, to make it O(n) we store the list locally and
    // iterate the list reversely
    nsTArray<nsIFrame*> childList;
    nsIFrame *frame = aFrame->GetFirstPrincipalChild();
    if (frame && aIsOddLevel) {
      childList.AppendElement((nsIFrame*)nullptr);
      while (frame) {
        childList.AppendElement(frame);
        frame = frame->GetNextSibling();
      }
      frame = childList[childList.Length() - 1];
    }

    // Reposition the child frames
    int32_t index = 0;
    while (frame) {
      RepositionFrame(frame,
                      aIsOddLevel,
                      x,
                      aContinuationStates);
      index++;
      frame = aIsOddLevel ?
                childList[childList.Length() - index - 1] :
                frame->GetNextSibling();
    }

    if (isRightMost) {
      x += borderPadding.right;
    }
    aLeft += x;
  } else {
    aLeft += aFrame->GetSize().width;
  }
  nsRect rect = aFrame->GetRect();
  aFrame->SetRect(nsRect(start, rect.y, aLeft - start, rect.height));

  if (isRightMost)
    aLeft += margin.right;
}

void
nsBidiPresUtils::InitContinuationStates(nsIFrame*              aFrame,
                                        nsContinuationStates*  aContinuationStates)
{
  nsFrameContinuationState* state = aContinuationStates->PutEntry(aFrame);
  state->mFirstVisualFrame = nullptr;
  state->mFrameCount = 0;

  if (!IsBidiLeaf(aFrame)) {
    // Continue for child frames
    nsIFrame* frame;
    for (frame = aFrame->GetFirstPrincipalChild();
         frame;
         frame = frame->GetNextSibling()) {
      InitContinuationStates(frame,
                             aContinuationStates);
    }
  }
}

void
nsBidiPresUtils::RepositionInlineFrames(BidiLineData *aBld,
                                        nsIFrame* aFirstChild)
{
  const nsStyleVisibility* vis = aFirstChild->StyleVisibility();
  bool isLTR = (NS_STYLE_DIRECTION_LTR == vis->mDirection);
  nscoord leftSpace = 0;

  // This method is called from nsBlockFrame::PlaceLine via the call to
  // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
  // have been reflowed, which is required for GetUsedMargin/Border/Padding
  nsMargin margin = aFirstChild->GetUsedMargin();
  if (!aFirstChild->GetPrevContinuation() &&
      !aFirstChild->FrameIsNonFirstInIBSplit())
    leftSpace = isLTR ? margin.left : margin.right;

  nscoord left = aFirstChild->GetPosition().x - leftSpace;
  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
  for (index = 0; index < count; index++) {
    frame = aBld->VisualFrameAt(index);
    RepositionFrame(frame,
                    (aBld->mLevels[aBld->mIndexMap[index]] & 1),
                    left,
                    &continuationStates);
  } // for
}

bool
nsBidiPresUtils::CheckLineOrder(nsIFrame*  aFirstFrameOnLine,
                                int32_t    aNumFramesOnLine,
                                nsIFrame** aFirstVisual,
                                nsIFrame** aLastVisual)
{
  BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
  int32_t count = bld.FrameCount();
  
  if (aFirstVisual) {
    *aFirstVisual = bld.VisualFrameAt(0);
  }
  if (aLastVisual) {
    *aLastVisual = bld.VisualFrameAt(count-1);
  }
  
  return bld.mIsReordered;
}

nsIFrame*
nsBidiPresUtils::GetFrameToRightOf(const nsIFrame*  aFrame,
                                   nsIFrame*        aFirstFrameOnLine,
                                   int32_t          aNumFramesOnLine)
{
  BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);

  int32_t count = bld.mVisualFrames.Length();

  if (aFrame == nullptr && count)
    return bld.VisualFrameAt(0);
  
  for (int32_t i = 0; i < count - 1; i++) {
    if (bld.VisualFrameAt(i) == aFrame) {
      return bld.VisualFrameAt(i+1);
    }
  }
  
  return nullptr;
}

nsIFrame*
nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame*  aFrame,
                                  nsIFrame*        aFirstFrameOnLine,
                                  int32_t          aNumFramesOnLine)
{
  BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);

  int32_t count = bld.mVisualFrames.Length();
  
  if (aFrame == nullptr && count)
    return bld.VisualFrameAt(count-1);
  
  for (int32_t i = 1; i < count; i++) {
    if (bld.VisualFrameAt(i) == aFrame) {
      return bld.VisualFrameAt(i-1);
    }
  }
  
  return nullptr;
}

inline nsresult
nsBidiPresUtils::EnsureBidiContinuation(nsIFrame*       aFrame,
                                        nsIFrame**      aNewFrame,
                                        int32_t&        aFrameIndex,
                                        int32_t         aStart,
                                        int32_t         aEnd)
{
  NS_PRECONDITION(aNewFrame, "null OUT ptr");
  NS_PRECONDITION(aFrame, "aFrame is null");

  aFrame->AdjustOffsetsForBidi(aStart, aEnd);
  return CreateContinuation(aFrame, aNewFrame, false);
}

void
nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData *aBpd,
                                        nsIFrame*       aFrame,
                                        int32_t         aFirstIndex,
                                        int32_t         aLastIndex,
                                        int32_t&        aOffset)
{
  FrameProperties props = aFrame->Properties();
  nsBidiLevel embeddingLevel =
    (nsBidiLevel)NS_PTR_TO_INT32(props.Get(nsIFrame::EmbeddingLevelProperty()));
  nsBidiLevel baseLevel =
    (nsBidiLevel)NS_PTR_TO_INT32(props.Get(nsIFrame::BaseLevelProperty()));
  uint8_t paragraphDepth = 
    NS_PTR_TO_INT32(props.Get(nsIFrame::ParagraphDepthProperty()));

  for (int32_t index = aFirstIndex + 1; index <= aLastIndex; index++) {
    nsIFrame* frame = aBpd->FrameAt(index);
    if (frame == NS_BIDI_CONTROL_FRAME) {
      ++aOffset;
    }
    else {
      // Make the frame and its continuation ancestors fluid,
      // so they can be reused or deleted by normal reflow code
      FrameProperties frameProps = frame->Properties();
      frameProps.Set(nsIFrame::EmbeddingLevelProperty(),
                     NS_INT32_TO_PTR(embeddingLevel));
      frameProps.Set(nsIFrame::BaseLevelProperty(),
                     NS_INT32_TO_PTR(baseLevel));
      frameProps.Set(nsIFrame::ParagraphDepthProperty(),
                     NS_INT32_TO_PTR(paragraphDepth));
      frame->AddStateBits(NS_FRAME_IS_BIDI);
      while (frame) {
        nsIFrame* prev = frame->GetPrevContinuation();
        if (prev) {
          MakeContinuationFluid(prev, frame);
          frame = frame->GetParent();
        } else {
          break;
        }
      }
    }
  }

  // Make sure that the last continuation we made fluid does not itself have a
  // fluid continuation (this can happen when re-resolving after dynamic changes
  // to content)
  nsIFrame* lastFrame = aBpd->FrameAt(aLastIndex);
  MakeContinuationsNonFluidUpParentChain(lastFrame, lastFrame->GetNextInFlow());
}

nsresult
nsBidiPresUtils::FormatUnicodeText(nsPresContext*  aPresContext,
                                   PRUnichar*       aText,
                                   int32_t&         aTextLength,
                                   nsCharType       aCharType,
                                   bool             aIsOddLevel)
{
  nsresult rv = NS_OK;
  // ahmed 
  //adjusted for correct numeral shaping  
  uint32_t bidiOptions = aPresContext->GetBidi();
  switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) {

    case IBMBIDI_NUMERAL_HINDI:
      HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI);
      break;

    case IBMBIDI_NUMERAL_ARABIC:
      HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
      break;

    case IBMBIDI_NUMERAL_PERSIAN:
      HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_PERSIAN);
      break;

    case IBMBIDI_NUMERAL_REGULAR:

      switch (aCharType) {

        case eCharType_EuropeanNumber:
          HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
          break;

        case eCharType_ArabicNumber:
          HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI);
          break;

        default:
          break;
      }
      break;

    case IBMBIDI_NUMERAL_HINDICONTEXT:
      if ( ( (GET_BIDI_OPTION_DIRECTION(bidiOptions)==IBMBIDI_TEXTDIRECTION_RTL) && (IS_ARABIC_DIGIT (aText[0])) ) || (eCharType_ArabicNumber == aCharType) )
        HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_HINDI);
      else if (eCharType_EuropeanNumber == aCharType)
        HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
      break;

    case IBMBIDI_NUMERAL_PERSIANCONTEXT:
      if ( ( (GET_BIDI_OPTION_DIRECTION(bidiOptions)==IBMBIDI_TEXTDIRECTION_RTL) && (IS_ARABIC_DIGIT (aText[0])) ) || (eCharType_ArabicNumber == aCharType) )
        HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_PERSIAN);
      else if (eCharType_EuropeanNumber == aCharType)
        HandleNumbers(aText,aTextLength,IBMBIDI_NUMERAL_ARABIC);
      break;

    case IBMBIDI_NUMERAL_NOMINAL:
    default:
      break;
  }

  StripBidiControlCharacters(aText, aTextLength);
  return rv;
}

void
nsBidiPresUtils::StripBidiControlCharacters(PRUnichar* aText,
                                            int32_t&   aTextLength)
{
  if ( (nullptr == aText) || (aTextLength < 1) ) {
    return;
  }

  int32_t stripLen = 0;

  for (int32_t i = 0; i < aTextLength; i++) {
    // XXX: This silently ignores surrogate characters.
    //      As of Unicode 4.0, all Bidi control characters are within the BMP.
    if (IsBidiControl((uint32_t)aText[i])) {
      ++stripLen;
    }
    else {
      aText[i - stripLen] = aText[i];
    }
  }
  aTextLength -= stripLen;
}
 
#if 0 // XXX: for the future use ???
void
RemoveDiacritics(PRUnichar* aText,
                 int32_t&   aTextLength)
{
  if (aText && (aTextLength > 0) ) {
    int32_t offset = 0;

    for (int32_t i = 0; i < aTextLength && aText[i]; i++) {
      if (IS_BIDI_DIACRITIC(aText[i]) ) {
        ++offset;
        continue;
      }
      aText[i - offset] = aText[i];
    }
    aTextLength = i - offset;
    aText[aTextLength] = 0;
  }
}
#endif

void
nsBidiPresUtils::CalculateCharType(nsBidi* aBidiEngine,
                                   const PRUnichar* aText,
                                   int32_t& aOffset,
                                   int32_t  aCharTypeLimit,
                                   int32_t& aRunLimit,
                                   int32_t& aRunLength,
                                   int32_t& aRunCount,
                                   uint8_t& aCharType,
                                   uint8_t& aPrevCharType)

{
  bool       strongTypeFound = false;
  int32_t    offset;
  nsCharType charType;

  aCharType = eCharType_OtherNeutral;

  for (offset = aOffset; offset < aCharTypeLimit; offset++) {
    // Make sure we give RTL chartype to all characters that would be classified
    // as Right-To-Left by a bidi platform.
    // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
    if (IS_HEBREW_CHAR(aText[offset]) ) {
      charType = eCharType_RightToLeft;
    }
    else if (IS_ARABIC_ALPHABETIC(aText[offset]) ) {
      charType = eCharType_RightToLeftArabic;
    }
    else {
      aBidiEngine->GetCharTypeAt(offset, &charType);
    }

    if (!CHARTYPE_IS_WEAK(charType) ) {

      if (strongTypeFound
          && (charType != aPrevCharType)
          && (CHARTYPE_IS_RTL(charType) || CHARTYPE_IS_RTL(aPrevCharType) ) ) {
        // Stop at this point to ensure uni-directionality of the text
        // (from platform's point of view).
        // Also, don't mix Arabic and Hebrew content (since platform may
        // provide BIDI support to one of them only).
        aRunLength = offset - aOffset;
        aRunLimit = offset;
        ++aRunCount;
        break;
      }

      if ( (eCharType_RightToLeftArabic == aPrevCharType
            || eCharType_ArabicNumber == aPrevCharType)
          && eCharType_EuropeanNumber == charType) {
        charType = eCharType_ArabicNumber;
      }

      // Set PrevCharType to the last strong type in this frame
      // (for correct numeric shaping)
      aPrevCharType = charType;

      strongTypeFound = true;
      aCharType = charType;
    }
  }
  aOffset = offset;
}

nsresult nsBidiPresUtils::ProcessText(const PRUnichar*       aText,
                                      int32_t                aLength,
                                      nsBidiLevel            aBaseLevel,
                                      nsPresContext*         aPresContext,
                                      BidiProcessor&         aprocessor,
                                      Mode                   aMode,
                                      nsBidiPositionResolve* aPosResolve,
                                      int32_t                aPosResolveCount,
                                      nscoord*               aWidth,
                                      nsBidi*                aBidiEngine)
{
  NS_ASSERTION((aPosResolve == nullptr) != (aPosResolveCount > 0), "Incorrect aPosResolve / aPosResolveCount arguments");

  int32_t runCount;

  nsAutoString textBuffer(aText, aLength);

  nsresult rv = aBidiEngine->SetPara(aText, aLength, aBaseLevel, nullptr);
  if (NS_FAILED(rv))
    return rv;

  rv = aBidiEngine->CountRuns(&runCount);
  if (NS_FAILED(rv))
    return rv;

  nscoord xOffset = 0;
  nscoord width, xEndRun = 0;
  nscoord totalWidth = 0;
  int32_t i, start, limit, length;
  uint32_t visualStart = 0;
  uint8_t charType;
  uint8_t prevType = eCharType_LeftToRight;
  nsBidiLevel level;
      
  for(int nPosResolve=0; nPosResolve < aPosResolveCount; ++nPosResolve)
  {
    aPosResolve[nPosResolve].visualIndex = kNotFound;
    aPosResolve[nPosResolve].visualLeftTwips = kNotFound;
    aPosResolve[nPosResolve].visualWidth = kNotFound;
  }

  for (i = 0; i < runCount; i++) {
    nsBidiDirection dir;
    rv = aBidiEngine->GetVisualRun(i, &start, &length, &dir);
    if (NS_FAILED(rv))
      return rv;

    rv = aBidiEngine->GetLogicalRun(start, &limit, &level);
    if (NS_FAILED(rv))
      return rv;

    int32_t subRunLength = limit - start;
    int32_t lineOffset = start;
    int32_t typeLimit = std::min(limit, aLength);
    int32_t subRunCount = 1;
    int32_t subRunLimit = typeLimit;

    /*
     * If |level| is even, i.e. the direction of the run is left-to-right, we
     * render the subruns from left to right and increment the x-coordinate
     * |xOffset| by the width of each subrun after rendering.
     *
     * If |level| is odd, i.e. the direction of the run is right-to-left, we
     * render the subruns from right to left. We begin by incrementing |xOffset| by
     * the width of the whole run, and then decrement it by the width of each
     * subrun before rendering. After rendering all the subruns, we restore the
     * x-coordinate of the end of the run for the start of the next run.
     */

    if (level & 1) {
      aprocessor.SetText(aText + start, subRunLength, nsBidiDirection(level & 1));
      width = aprocessor.GetWidth();
      xOffset += width;
      xEndRun = xOffset;
    }

    while (subRunCount > 0) {
      // CalculateCharType can increment subRunCount if the run
      // contains mixed character types
      CalculateCharType(aBidiEngine, aText, lineOffset, typeLimit, subRunLimit, subRunLength, subRunCount, charType, prevType);
      
      nsAutoString runVisualText;
      runVisualText.Assign(aText + start, subRunLength);
      if (int32_t(runVisualText.Length()) < subRunLength)
        return NS_ERROR_OUT_OF_MEMORY;
      FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), subRunLength,
                        (nsCharType)charType, level & 1);

      aprocessor.SetText(runVisualText.get(), subRunLength, nsBidiDirection(level & 1));
      width = aprocessor.GetWidth();
      totalWidth += width;
      if (level & 1) {
        xOffset -= width;
      }
      if (aMode == MODE_DRAW) {
        aprocessor.DrawText(xOffset, width);
      }

      /*
       * The caller may request to calculate the visual position of one
       * or more characters.
       */
      for(int nPosResolve=0; 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 PRUnichar* visualLeftPart, *visualRightSide;
            if (level & 1) {
              // One day, son, this could all be replaced with mBidiEngine.GetVisualIndex ...
              posResolve->visualIndex = visualStart + (subRunLength - (posResolve->logicalIndex + 1 - start));
              // Skipping to the "left part".
              visualLeftPart = aText + posResolve->logicalIndex + 1;
              // Skipping to the right side of the current character
              visualRightSide = visualLeftPart - 1;
            }
            else {
              posResolve->visualIndex = visualStart + (posResolve->logicalIndex - start);
              // Skipping to the "left part".
              visualLeftPart = aText + start;
              // In LTR mode this is the same as visualLeftPart
              visualRightSide = visualLeftPart;
            }
            // The delta between the start of the run and the left part's end.
            int32_t visualLeftLength = posResolve->visualIndex - visualStart;
            aprocessor.SetText(visualLeftPart, visualLeftLength, nsBidiDirection(level & 1));
            subWidth = aprocessor.GetWidth();
            aprocessor.SetText(visualRightSide, visualLeftLength + 1, nsBidiDirection(level & 1));
            posResolve->visualLeftTwips = xOffset + subWidth;
            posResolve->visualWidth = aprocessor.GetWidth() - subWidth;
          }
        }
      }

      if (!(level & 1)) {
        xOffset += width;
      }

      --subRunCount;
      start = lineOffset;
      subRunLimit = typeLimit;
      subRunLength = typeLimit - lineOffset;
    } // while
    if (level & 1) {
      xOffset = xEndRun;
    }
    
    visualStart += length;
  } // for

  if (aWidth) {
    *aWidth = totalWidth;
  }
  return NS_OK;
}

class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor : public nsBidiPresUtils::BidiProcessor {
public:
  nsIRenderingContextBidiProcessor(nsRenderingContext* aCtx,
                                   nsRenderingContext* aTextRunConstructionContext,
                                   const nsPoint&       aPt)
    : mCtx(aCtx), mTextRunConstructionContext(aTextRunConstructionContext), mPt(aPt) { }

  ~nsIRenderingContextBidiProcessor()
  {
    mCtx->SetTextRunRTL(false);
  }

  virtual void SetText(const PRUnichar* aText,
                       int32_t          aLength,
                       nsBidiDirection  aDirection)
  {
    mTextRunConstructionContext->SetTextRunRTL(aDirection==NSBIDI_RTL);
    mText = aText;
    mLength = aLength;
  }

  virtual nscoord GetWidth()
  {
    return mTextRunConstructionContext->GetWidth(mText, mLength);
  }

  virtual void DrawText(nscoord aXOffset,
                        nscoord)
  {
    mCtx->FontMetrics()->DrawString(mText, mLength, mPt.x + aXOffset, mPt.y,
                                    mCtx, mTextRunConstructionContext);
  }

private:
  nsRenderingContext* mCtx;
  nsRenderingContext* mTextRunConstructionContext;
  nsPoint mPt;
  const PRUnichar* mText;
  int32_t mLength;
};

nsresult nsBidiPresUtils::ProcessTextForRenderingContext(const PRUnichar*       aText,
                                                         int32_t                aLength,
                                                         nsBidiLevel            aBaseLevel,
                                                         nsPresContext*         aPresContext,
                                                         nsRenderingContext&   aRenderingContext,
                                                         nsRenderingContext&   aTextRunConstructionContext,
                                                         Mode                   aMode,
                                                         nscoord                aX,
                                                         nscoord                aY,
                                                         nsBidiPositionResolve* aPosResolve,
                                                         int32_t                aPosResolveCount,
                                                         nscoord*               aWidth)
{
  nsIRenderingContextBidiProcessor processor(&aRenderingContext, &aTextRunConstructionContext, nsPoint(aX, aY));
  nsBidi bidiEngine;
  return ProcessText(aText, aLength, aBaseLevel, aPresContext, processor,
                     aMode, aPosResolve, aPosResolveCount, aWidth, &bidiEngine);
}

/* static */
void nsBidiPresUtils::WriteReverse(const PRUnichar* aSrc,
                                   uint32_t aSrcLength,
                                   PRUnichar* aDest)
{
  PRUnichar* dest = aDest + aSrcLength;
  mozilla::unicode::ClusterIterator iter(aSrc, aSrcLength);

  while (!iter.AtEnd()) {
    iter.Next();
    for (const PRUnichar *cp = iter; cp > aSrc; ) {
      // Here we rely on the fact that there are no non-BMP mirrored pairs
      // currently in Unicode, so we don't need to look for surrogates
      *--dest = mozilla::unicode::GetMirroredChar(*--cp);
    }
    aSrc = iter;
  }

  NS_ASSERTION(dest == aDest, "Whole string not copied");
}

/* static */
bool nsBidiPresUtils::WriteLogicalToVisual(const PRUnichar* aSrc,
                                           uint32_t aSrcLength,
                                           PRUnichar* aDest,
                                           nsBidiLevel aBaseDirection,
                                           nsBidi* aBidiEngine)
{
  const PRUnichar* src = aSrc;
  nsresult rv = aBidiEngine->SetPara(src, aSrcLength, aBaseDirection, nullptr);
  if (NS_FAILED(rv)) {
    return false;
  }

  nsBidiDirection dir;
  rv = aBidiEngine->GetDirection(&dir);
  // NSBIDI_LTR returned from GetDirection means the whole text is LTR
  if (NS_FAILED(rv) || dir == NSBIDI_LTR) {
    return false;
  }

  int32_t runCount;
  rv = aBidiEngine->CountRuns(&runCount);
  if (NS_FAILED(rv)) {
    return false;
  }

  int32_t runIndex, start, length;
  PRUnichar* dest = aDest;

  for (runIndex = 0; runIndex < runCount; ++runIndex) {
    rv = aBidiEngine->GetVisualRun(runIndex, &start, &length, &dir);
    if (NS_FAILED(rv)) {
      return false;
    }

    src = aSrc + start;

    if (dir == NSBIDI_RTL) {
      WriteReverse(src, length, dest);
      dest += length;
    } else {
      do {
        NS_ASSERTION(src >= aSrc && src < aSrc + aSrcLength,
                     "logical index out of range");
        NS_ASSERTION(dest < aDest + aSrcLength, "visual index out of range");
        *(dest++) = *(src++);
      } while (--length);
    }
  }

  NS_ASSERTION(static_cast<uint32_t>(dest - aDest) == aSrcLength,
               "whole string not copied");
  return true;
}

void nsBidiPresUtils::CopyLogicalToVisual(const nsAString& aSource,
                                          nsAString& aDest,
                                          nsBidiLevel aBaseDirection,
                                          bool aOverride)
{
  aDest.SetLength(0);
  uint32_t srcLength = aSource.Length();
  if (srcLength == 0)
    return;
  if (!aDest.SetLength(srcLength, fallible_t())) {
    return;
  }
  nsAString::const_iterator fromBegin, fromEnd;
  nsAString::iterator toBegin;
  aSource.BeginReading(fromBegin);
  aSource.EndReading(fromEnd);
  aDest.BeginWriting(toBegin);

  if (aOverride) {
    if (aBaseDirection == NSBIDI_RTL) {
      // no need to use the converter -- just copy the string in reverse order
      WriteReverse(fromBegin.get(), srcLength, toBegin.get());
    } else {
      // if aOverride && aBaseDirection == NSBIDI_LTR, fall through to the
      // simple copy
      aDest.SetLength(0);
    }
  } else {
    nsBidi bidiEngine;
    if (!WriteLogicalToVisual(fromBegin.get(), srcLength, toBegin.get(),
                             aBaseDirection, &bidiEngine)) {
      aDest.SetLength(0);
    }
  }

  if (aDest.IsEmpty()) {
    // Either there was an error or the source is unidirectional
    //  left-to-right. In either case, just copy source to dest.
    CopyUnicodeTo(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd),
                  aDest);
  }
}

/* static */
nsBidiLevel
nsBidiPresUtils::BidiLevelFromStyle(nsStyleContext* aStyleContext)
{
  if (aStyleContext->StyleTextReset()->mUnicodeBidi &
      NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
    return NSBIDI_DEFAULT_LTR;
  }

  if (aStyleContext->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
    return NSBIDI_RTL;
  }

  return NSBIDI_LTR;
}
#endif // IBMBIDI
back to top