mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
e263983c59
Differential Revision: https://phabricator.services.mozilla.com/D229245
2537 lines
94 KiB
C++
2537 lines
94 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 kNextLine = 0x0085;
|
|
static const char16_t kZWSP = 0x200B;
|
|
static const char16_t kLineSeparator = 0x2028;
|
|
static const char16_t kParagraphSeparator = 0x2029;
|
|
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.
|
|
// This should be kept in sync with the table in ReplaceSeparators.
|
|
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),
|
|
kNextLine, kParagraphSeparator, 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);
|
|
}
|
|
}
|
|
};
|
|
|
|
class MOZ_STACK_CLASS BidiLineData {
|
|
public:
|
|
BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) {
|
|
// Initialize the logically-ordered array of frames using the top-level
|
|
// frames of a single line
|
|
auto appendFrame = [&](nsIFrame* frame, BidiEmbeddingLevel level) {
|
|
mLogicalFrames.AppendElement(frame);
|
|
mLevels.AppendElement(level);
|
|
mIndexMap.AppendElement(0);
|
|
};
|
|
|
|
for (nsIFrame* frame = aFirstFrameOnLine; frame && aNumFramesOnLine--;
|
|
frame = frame->GetNextSibling()) {
|
|
FrameBidiData bidiData = nsBidiPresUtils::GetFrameBidiData(frame);
|
|
if (bidiData.precedingControl != kBidiLevelNone) {
|
|
appendFrame(NS_BIDI_CONTROL_FRAME, bidiData.precedingControl);
|
|
}
|
|
appendFrame(frame, bidiData.embeddingLevel);
|
|
}
|
|
|
|
// Reorder the line
|
|
BidiEngine::ReorderVisual(mLevels.Elements(), mLevels.Length(),
|
|
mIndexMap.Elements());
|
|
|
|
// Collect the frames in visual order, omitting virtual controls
|
|
// and noting whether frames are reordered.
|
|
for (uint32_t i = 0; i < mIndexMap.Length(); i++) {
|
|
nsIFrame* frame = mLogicalFrames[mIndexMap[i]];
|
|
if (frame == NS_BIDI_CONTROL_FRAME) {
|
|
continue;
|
|
}
|
|
mVisualFrameIndex.AppendElement(mIndexMap[i]);
|
|
if (int32_t(i) != mIndexMap[i]) {
|
|
mIsReordered = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t LogicalFrameCount() const { return mLogicalFrames.Length(); }
|
|
uint32_t VisualFrameCount() const { return mVisualFrameIndex.Length(); }
|
|
|
|
nsIFrame* LogicalFrameAt(uint32_t aIndex) const {
|
|
return mLogicalFrames[aIndex];
|
|
}
|
|
|
|
nsIFrame* VisualFrameAt(uint32_t aIndex) const {
|
|
return mLogicalFrames[mVisualFrameIndex[aIndex]];
|
|
}
|
|
|
|
std::pair<nsIFrame*, BidiEmbeddingLevel> VisualFrameAndLevelAt(
|
|
uint32_t aIndex) const {
|
|
int32_t index = mVisualFrameIndex[aIndex];
|
|
return std::pair(mLogicalFrames[index], mLevels[index]);
|
|
}
|
|
|
|
bool IsReordered() const { return mIsReordered; }
|
|
|
|
void InitContinuationStates(nsContinuationStates* aContinuationStates) const {
|
|
for (auto* frame : mLogicalFrames) {
|
|
if (frame != NS_BIDI_CONTROL_FRAME) {
|
|
nsBidiPresUtils::InitContinuationStates(frame, aContinuationStates);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
AutoTArray<nsIFrame*, 16> mLogicalFrames;
|
|
AutoTArray<int32_t, 16> mVisualFrameIndex;
|
|
AutoTArray<int32_t, 16> mIndexMap;
|
|
AutoTArray<BidiEmbeddingLevel, 16> mLevels;
|
|
bool mIsReordered = false;
|
|
};
|
|
|
|
#ifdef DEBUG
|
|
extern "C" {
|
|
void MOZ_EXPORT DumpBidiLine(BidiLineData* aData, bool aVisualOrder) {
|
|
auto dump = [](nsIFrame* frame) {
|
|
if (frame == NS_BIDI_CONTROL_FRAME) {
|
|
fprintf_stderr(stderr, "(Bidi control frame)\n");
|
|
} else {
|
|
frame->List();
|
|
}
|
|
};
|
|
|
|
if (aVisualOrder) {
|
|
for (uint32_t i = 0; i < aData->VisualFrameCount(); i++) {
|
|
dump(aData->VisualFrameAt(i));
|
|
}
|
|
} else {
|
|
for (uint32_t i = 0; i < aData->LogicalFrameCount(); i++) {
|
|
dump(aData->LogicalFrameAt(i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#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);
|
|
}
|
|
|
|
// In ResolveParagraph, we previously used ReplaceChar(kSeparators, kSpace)
|
|
// to convert separators to spaces, but this hard-coded implementation is
|
|
// substantially faster than the general-purpose ReplaceChar function.
|
|
// This must be kept in sync with the definition of kSeparators.
|
|
static inline void ReplaceSeparators(nsString& aText, size_t aStartIndex = 0) {
|
|
for (char16_t* cp = aText.BeginWriting() + aStartIndex;
|
|
cp < aText.EndWriting(); cp++) {
|
|
if (MOZ_UNLIKELY(*cp < char16_t(' '))) {
|
|
static constexpr char16_t SeparatorToSpace[32] = {
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ' ', ' ',
|
|
' ', 0x0c, ' ', 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
|
|
0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, ' ', ' ', ' ', ' ',
|
|
};
|
|
*cp = SeparatorToSpace[*cp];
|
|
} else if (MOZ_UNLIKELY(*cp == kNextLine || *cp == kParagraphSeparator)) {
|
|
*cp = ' ';
|
|
}
|
|
}
|
|
}
|
|
|
|
nsresult nsBidiPresUtils::ResolveParagraph(BidiParagraphData* aBpd) {
|
|
if (aBpd->BufferLength() < 1) {
|
|
return NS_OK;
|
|
}
|
|
|
|
ReplaceSeparators(aBpd->mBuffer);
|
|
|
|
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).
|
|
if (frame != NS_BIDI_CONTROL_FRAME) {
|
|
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;
|
|
}
|
|
|
|
// No need to bidi-reorder the line if there's only a single frame.
|
|
if (aNumFramesOnLine == 1) {
|
|
auto bidiData = nsBidiPresUtils::GetFrameBidiData(aFirstFrameOnLine);
|
|
nsContinuationStates continuationStates;
|
|
InitContinuationStates(aFirstFrameOnLine, &continuationStates);
|
|
return aStart + RepositionFrame(aFirstFrameOnLine,
|
|
bidiData.embeddingLevel.IsLTR(), aStart,
|
|
&continuationStates, aLineWM, false,
|
|
containerSize);
|
|
}
|
|
|
|
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(const BidiLineData& aBld,
|
|
WritingMode aLineWM,
|
|
const nsSize& aContainerSize,
|
|
nscoord aStart) {
|
|
nsContinuationStates continuationStates;
|
|
aBld.InitContinuationStates(&continuationStates);
|
|
|
|
if (aLineWM.IsBidiLTR()) {
|
|
for (auto index : IntegerRange(aBld.VisualFrameCount())) {
|
|
auto [frame, level] = aBld.VisualFrameAndLevelAt(index);
|
|
aStart +=
|
|
RepositionFrame(frame, level.IsLTR(), aStart, &continuationStates,
|
|
aLineWM, false, aContainerSize);
|
|
}
|
|
} else {
|
|
for (auto index : Reversed(IntegerRange(aBld.VisualFrameCount()))) {
|
|
auto [frame, level] = aBld.VisualFrameAndLevelAt(index);
|
|
aStart +=
|
|
RepositionFrame(frame, level.IsLTR(), aStart, &continuationStates,
|
|
aLineWM, false, aContainerSize);
|
|
}
|
|
}
|
|
|
|
return aStart;
|
|
}
|
|
|
|
bool nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine,
|
|
int32_t aNumFramesOnLine,
|
|
nsIFrame** aFirstVisual,
|
|
nsIFrame** aLastVisual) {
|
|
BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
|
|
|
|
if (aFirstVisual) {
|
|
*aFirstVisual = bld.VisualFrameAt(0);
|
|
}
|
|
if (aLastVisual) {
|
|
*aLastVisual = bld.VisualFrameAt(bld.VisualFrameCount() - 1);
|
|
}
|
|
|
|
return bld.IsReordered();
|
|
}
|
|
|
|
nsIFrame* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame,
|
|
nsIFrame* aFirstFrameOnLine,
|
|
int32_t aNumFramesOnLine) {
|
|
BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
|
|
|
|
int32_t count = bld.VisualFrameCount();
|
|
|
|
if (!aFrame && 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.VisualFrameCount();
|
|
|
|
if (!aFrame && 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) {
|
|
// If we're only processing one frame, and it is already a fluid continuation
|
|
// (next-in-flow), there's nothing to do.
|
|
if (aLastIndex == aFirstIndex + 1 &&
|
|
aFrame->GetNextInFlow() == aFrame->GetNextContinuation()) {
|
|
return;
|
|
}
|
|
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) {
|
|
if (!aLength) {
|
|
if (aWidth) {
|
|
*aWidth = 0;
|
|
}
|
|
return;
|
|
}
|
|
// 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));
|
|
nsDependentSubstring text(aText, aLength);
|
|
auto separatorIndex = text.FindCharInSet(kSeparators);
|
|
if (separatorIndex == kNotFound) {
|
|
return ProcessText(text.BeginReading(), text.Length(), aBaseLevel,
|
|
aPresContext, processor, aMode, aPosResolve,
|
|
aPosResolveCount, aWidth, aPresContext->BidiEngine());
|
|
}
|
|
|
|
// We need to replace any block or segment separators with space for bidi
|
|
// processing, so make a local copy.
|
|
nsAutoString localText(text);
|
|
ReplaceSeparators(localText, separatorIndex);
|
|
return ProcessText(localText.BeginReading(), localText.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();
|
|
}
|