/* -*- 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/. */
using BidiEngine = intl::Bidi; using BidiClass = intl::BidiClass; using BidiDirection = intl::BidiDirection; using BidiEmbeddingLevel = intl::BidiEmbeddingLevel;
staticconst char16_t kNextLine = 0x0085; staticconst char16_t kZWSP = 0x200B; staticconst char16_t kLineSeparator = 0x2028; staticconst char16_t kParagraphSeparator = 0x2029; staticconst char16_t kObjectSubstitute = 0xFFFC; staticconst char16_t kLRE = 0x202A; staticconst char16_t kRLE = 0x202B; staticconst char16_t kLRO = 0x202D; staticconst char16_t kRLO = 0x202E; staticconst char16_t kPDF = 0x202C; staticconst char16_t kLRI = 0x2066; staticconst char16_t kRLI = 0x2067; staticconst char16_t kFSI = 0x2068; staticconst 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. staticconst 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)};
// 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 staticinlinebool AreContinuationsInOrder(nsIFrame* aFrame1,
nsIFrame* aFrame2) {
nsIFrame* f = aFrame1; do {
f = f->GetNextContinuation();
} while (f && f != aFrame2); return !!f;
} #endif
/** * 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;
// 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);
}
};
#ifdef DEBUG // Only used for NOISY debug output. // Matches the current TraverseFrames state, not the ResolveParagraph // state.
nsBlockFrame* mCurrentBlock; #endif
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;
}
}
}
}
/** * 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;
}
// 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));
}
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();
}
}
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);
};
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? staticbool 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)? staticbool IsBidiLeaf(const nsIFrame* aFrame) {
nsIFrame* kid = aFrame->PrincipalChildList().FirstChild(); if (kid) { if (aFrame->IsBidiInlineContainer() ||
RubyUtils::IsRubyBox(aFrame->Type())) { returnfalse;
}
} returntrue;
}
/** * 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.
*/ staticvoid 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));
// 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;
}
}
staticvoid 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);
}
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. staticvoid 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();
}
}
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;
}
// 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));
/* * 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;
}
}
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;
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;
}
}
}
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;
}
} elseif (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);
}
} elseif (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);
}
}
} elseif (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
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;
}
}
// 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);
}
}
// 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);
}
}
} elseif (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);
bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame* aFirstChild,
nsIContent** aCurrContent) {
MOZ_ASSERT(!aFirstChild || !aFirstChild->GetPrevSibling(), "Expecting to traverse from the start of a child list");
// 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)) { returntrue;
}
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())) { returntrue;
}
// 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()))) { returntrue;
}
}
}
} elseif (ChildListMayRequireBidi(frame->PrincipalChildList().FirstChild(),
aCurrContent)) { returntrue;
}
}
// 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);
}
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.
*/
if (!frameState->mFirstVisualFrame) { // aFrame is the first visual frame of its continuation chain
nsFrameContinuationState* contState;
nsIFrame* frame;
/** * 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);
firstInLineOrder = true;
firstFrameState = frameState;
} else { // aFrame is not the first visual frame of its continuation chain
firstInLineOrder = false;
firstFrameState = aContinuationStates->Get(frameState->mFirstVisualFrame);
}
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 (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);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.