Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/. */
#ifndef nsBidiPresUtils_h___
#define nsBidiPresUtils_h___
#include "gfxContext.h"
#include "mozilla/intl/BidiClass.h"
#include "mozilla/intl/BidiEmbeddingLevel.h"
#include "nsBidiUtils.h"
#include "nsHashKeys.h"
#include "nsCoord.h"
#include "nsTArray.h"
#include "nsLineBox.h"
#ifdef DrawText
# undef DrawText
#endif
struct BidiParagraphData;
class BidiLineData;
class gfxContext;
class nsFontMetrics;
class nsIFrame;
class nsBlockFrame;
class nsPresContext;
struct nsSize;
template <class T>
class nsTHashtable;
namespace mozilla {
namespace intl {
class Bidi;
}
class ComputedStyle;
class LogicalMargin;
class WritingMode;
} // namespace mozilla
/**
* A structure representing some continuation state for each frame on the line,
* used to determine the first and the last continuation frame for each
* continuation chain on the line.
*/
struct nsFrameContinuationState : public nsVoidPtrHashKey {
explicit nsFrameContinuationState(const void* aFrame)
: nsVoidPtrHashKey(aFrame) {}
/**
* The first visual frame in the continuation chain containing this frame, or
* nullptr if this frame is the first visual frame in the chain.
*/
nsIFrame* mFirstVisualFrame{nullptr};
/**
* The number of frames in the continuation chain containing this frame, if
* this frame is the first visual frame of the chain, or 0 otherwise.
*/
uint32_t mFrameCount{0};
/**
* TRUE if this frame is the first visual frame of its continuation chain on
* this line and the chain has some frames on the previous lines.
*/
bool mHasContOnPrevLines{false};
/**
* TRUE if this frame is the first visual frame of its continuation chain on
* this line and the chain has some frames left for next lines.
*/
bool mHasContOnNextLines{false};
};
// A table of nsFrameContinuationState objects.
//
// This state is used between calls to nsBidiPresUtils::IsFirstOrLast.
struct nsContinuationStates {
static constexpr size_t kArrayMax = 32;
// We use the array to gather up all the continuation state objects. If in
// the end there are more than kArrayMax of them, we convert it to a hash
// table for faster lookup.
bool mUseTable = false;
AutoTArray<nsFrameContinuationState, kArrayMax> mValues;
nsTHashtable<nsFrameContinuationState> mTable;
void Insert(nsIFrame* aFrame) {
if (MOZ_UNLIKELY(mUseTable)) {
mTable.PutEntry(aFrame);
return;
}
if (MOZ_LIKELY(mValues.Length() < kArrayMax)) {
mValues.AppendElement(aFrame);
return;
}
for (const auto& entry : mValues) {
mTable.PutEntry(entry.GetKey());
}
mTable.PutEntry(aFrame);
mValues.Clear();
mUseTable = true;
}
nsFrameContinuationState* Get(nsIFrame* aFrame) {
MOZ_ASSERT(mValues.IsEmpty() != mTable.IsEmpty(),
"expect entries to either be in mValues or in mTable");
if (mUseTable) {
return mTable.GetEntry(aFrame);
}
for (size_t i = 0, len = mValues.Length(); i != len; ++i) {
if (mValues[i].GetKey() == aFrame) {
return &mValues[i];
}
}
return nullptr;
}
};
/**
* A structure representing a logical position which should be resolved
* into its visual position during BiDi processing.
*/
struct nsBidiPositionResolve {
// [in] Logical index within string.
int32_t logicalIndex;
// [out] Visual index within string.
// If the logical position was not found, set to kNotFound.
int32_t visualIndex;
// [out] Visual position of the character, from the left (on the X axis), in
// twips. Eessentially, this is the X position (relative to the rendering
// context) where the text was drawn + the font metric of the visual string to
// the left of the given logical position. If the logical position was not
// found, set to kNotFound.
int32_t visualLeftTwips;
// [out] Visual width of the character, in twips.
// If the logical position was not found, set to kNotFound.
int32_t visualWidth;
};
class nsBidiPresUtils {
public:
typedef mozilla::gfx::DrawTarget DrawTarget;
nsBidiPresUtils();
~nsBidiPresUtils();
/**
* Interface for the processor used by ProcessText. Used by process text to
* collect information about the width of subruns and to notify where each
* subrun should be rendered.
*/
class BidiProcessor {
public:
virtual ~BidiProcessor() = default;
/**
* Sets the current text with the given length and the given direction.
*
* @remark The reason that the function gives a string instead of an index
* is that ProcessText copies and modifies the string passed to it, so
* passing an index would be impossible.
*
* @param aText The string of text.
* @param aLength The length of the string of text.
* @param aDirection The direction of the text. The string will never have
* mixed direction.
*/
virtual void SetText(const char16_t* aText, int32_t aLength,
mozilla::intl::BidiDirection aDirection) = 0;
/**
* Returns the measured width of the text given in SetText. If SetText was
* not called with valid parameters, the result of this call is undefined.
* This call is guaranteed to only be called once between SetText calls.
* Will be invoked before DrawText.
*/
virtual nscoord GetWidth() = 0;
/**
* Draws the text given in SetText to a rendering context. If SetText was
* not called with valid parameters, the result of this call is undefined.
* This call is guaranteed to only be called once between SetText calls.
*
* @param aXOffset The offset of the left side of the substring to be drawn
* from the beginning of the overall string passed to ProcessText.
*/
virtual void DrawText(nscoord aXOffset) = 0;
};
/**
* Make Bidi engine calculate the embedding levels of the frames that are
* descendants of a given block frame.
*
* @param aBlockFrame The block frame
*
* @lina 06/18/2000
*/
static nsresult Resolve(nsBlockFrame* aBlockFrame);
static nsresult ResolveParagraph(BidiParagraphData* aBpd);
static void ResolveParagraphWithinBlock(BidiParagraphData* aBpd);
/**
* Reorder this line using Bidi engine.
* Update frame array, following the new visual sequence.
*
* @return total inline size
*
* @lina 05/02/2000
*/
static nscoord ReorderFrames(nsIFrame* aFirstFrameOnLine,
int32_t aNumFramesOnLine,
mozilla::WritingMode aLineWM,
const nsSize& aContainerSize, nscoord aStart);
/**
* Format Unicode text, taking into account bidi capabilities
* of the platform. The formatting includes: reordering, Arabic shaping,
* symmetric and numeric swapping, removing control characters.
*
* @lina 06/18/2000
*/
static nsresult FormatUnicodeText(nsPresContext* aPresContext,
char16_t* aText, int32_t& aTextLength,
mozilla::intl::BidiClass aBidiClass);
/**
* Reorder plain text using the Unicode Bidi algorithm and send it to
* a rendering context for rendering.
*
* @param[in] aText the string to be rendered (in logical order)
* @param aLength the number of characters in the string
* @param aBaseLevel the base embedding level of the string
* @param aPresContext the presentation context
* @param aRenderingContext the rendering context to render to
* @param aTextRunConstructionContext the rendering context to be used to
* construct the textrun (affects font hinting)
* @param aX the x-coordinate to render the string
* @param aY the y-coordinate to render the string
* @param[in,out] aPosResolve array of logical positions to resolve into
* visual positions; can be nullptr if this functionality is not required
* @param aPosResolveCount number of items in the aPosResolve array
*/
static nsresult RenderText(const char16_t* aText, int32_t aLength,
mozilla::intl::BidiEmbeddingLevel aBaseLevel,
nsPresContext* aPresContext,
gfxContext& aRenderingContext,
DrawTarget* aTextRunConstructionDrawTarget,
nsFontMetrics& aFontMetrics, nscoord aX,
nscoord aY,
nsBidiPositionResolve* aPosResolve = nullptr,
int32_t aPosResolveCount = 0) {
return ProcessTextForRenderingContext(
aText, aLength, aBaseLevel, aPresContext, aRenderingContext,
aTextRunConstructionDrawTarget, aFontMetrics, MODE_DRAW, aX, aY,
aPosResolve, aPosResolveCount, nullptr);
}
static nscoord MeasureTextWidth(const char16_t* aText, int32_t aLength,
mozilla::intl::BidiEmbeddingLevel aBaseLevel,
nsPresContext* aPresContext,
gfxContext& aRenderingContext,
nsFontMetrics& aFontMetrics) {
nscoord length;
nsresult rv = ProcessTextForRenderingContext(
aText, aLength, aBaseLevel, aPresContext, aRenderingContext,
aRenderingContext.GetDrawTarget(), aFontMetrics, MODE_MEASURE, 0, 0,
nullptr, 0, &length);
return NS_SUCCEEDED(rv) ? length : 0;
}
/**
* Check if a line is reordered, i.e., if the child frames are not
* all laid out left-to-right.
* @param aFirstFrameOnLine : first frame of the line to be tested
* @param aNumFramesOnLine : number of frames on this line
* @param[out] aLeftMost : leftmost frame on this line
* @param[out] aRightMost : rightmost frame on this line
*/
static bool CheckLineOrder(nsIFrame* aFirstFrameOnLine,
int32_t aNumFramesOnLine, nsIFrame** aLeftmost,
nsIFrame** aRightmost);
/**
* Get the frame to the right of the given frame, on the same line.
* @param aFrame : We're looking for the frame to the right of this frame.
* If null, return the leftmost frame on the line.
* @param aFirstFrameOnLine : first frame of the line to be tested
* @param aNumFramesOnLine : number of frames on this line
*/
static nsIFrame* GetFrameToRightOf(const nsIFrame* aFrame,
nsIFrame* aFirstFrameOnLine,
int32_t aNumFramesOnLine);
/**
* Get the frame to the left of the given frame, on the same line.
* @param aFrame : We're looking for the frame to the left of this frame.
* If null, return the rightmost frame on the line.
* @param aFirstFrameOnLine : first frame of the line to be tested
* @param aNumFramesOnLine : number of frames on this line
*/
static nsIFrame* GetFrameToLeftOf(const nsIFrame* aFrame,
nsIFrame* aFirstFrameOnLine,
int32_t aNumFramesOnLine);
static nsIFrame* GetFirstLeaf(nsIFrame* aFrame);
/**
* Get the bidi data of the given (inline) frame.
*/
static mozilla::FrameBidiData GetFrameBidiData(nsIFrame* aFrame);
/**
* Get the bidi embedding level of the given (inline) frame.
*/
static mozilla::intl::BidiEmbeddingLevel GetFrameEmbeddingLevel(
nsIFrame* aFrame);
/**
* Get the bidi base level of the given (inline) frame.
*/
static mozilla::intl::BidiEmbeddingLevel GetFrameBaseLevel(
const nsIFrame* aFrame);
/**
* Get a mozilla::intl::BidiDirection representing the direction implied by
* the bidi base level of the frame.
* @return mozilla::intl::BidiDirection
*/
static mozilla::intl::BidiDirection ParagraphDirection(
const nsIFrame* aFrame) {
return GetFrameBaseLevel(aFrame).Direction();
}
/**
* Get a mozilla::intl::BidiDirection representing the direction implied by
* the bidi embedding level of the frame.
* @return mozilla::intl::BidiDirection
*/
static mozilla::intl::BidiDirection FrameDirection(nsIFrame* aFrame) {
return GetFrameEmbeddingLevel(aFrame).Direction();
}
static bool IsFrameInParagraphDirection(nsIFrame* aFrame) {
return ParagraphDirection(aFrame) == FrameDirection(aFrame);
}
// This is faster than nsBidiPresUtils::IsFrameInParagraphDirection,
// because it uses the frame pointer passed in without drilling down to
// the leaf frame.
static bool IsReversedDirectionFrame(const nsIFrame* aFrame) {
mozilla::FrameBidiData bidiData = aFrame->GetBidiData();
return !bidiData.embeddingLevel.IsSameDirection(bidiData.baseLevel);
}
enum Mode { MODE_DRAW, MODE_MEASURE };
/**
* Reorder plain text using the Unicode Bidi algorithm and send it to
* a processor for rendering or measuring
*
* @param[in] aText the string to be processed (in logical order)
* @param aLength the number of characters in the string
* @param aBaseLevel the base embedding level of the string
* @param aPresContext the presentation context
* @param aprocessor the bidi processor
* @param aMode the operation to process
* MODE_DRAW - invokes DrawText on the processor for each substring
* MODE_MEASURE - does not invoke DrawText on the processor
* Note that the string is always measured, regardless of mode
* @param[in,out] aPosResolve array of logical positions to resolve into
* visual positions; can be nullptr if this functionality is not required
* @param aPosResolveCount number of items in the aPosResolve array
* @param[out] aWidth Pointer to where the width will be stored (may be null)
*/
static nsresult ProcessText(const char16_t* aText, size_t aLength,
mozilla::intl::BidiEmbeddingLevel aBaseLevel,
nsPresContext* aPresContext,
BidiProcessor& aprocessor, Mode aMode,
nsBidiPositionResolve* aPosResolve,
int32_t aPosResolveCount, nscoord* aWidth,
mozilla::intl::Bidi& aBidiEngine);
/**
* Use style attributes to determine the base paragraph level to pass to the
* bidi algorithm.
*
* If |unicode-bidi| is set to "[-moz-]plaintext", returns
* BidiEmbeddingLevel::DefaultLTR, in other words the direction is determined
* from the first strong character in the text according to rules P2 and P3 of
* the bidi algorithm, or LTR if there is no strong character.
*
* Otherwise returns BidiEmbeddingLevel::LTR or BidiEmbeddingLevel::RTL
* depending on the value of |direction|
*/
static mozilla::intl::BidiEmbeddingLevel BidiLevelFromStyle(
mozilla::ComputedStyle* aComputedStyle);
private:
friend class BidiLineData;
static nsresult ProcessTextForRenderingContext(
const char16_t* aText, int32_t aLength,
mozilla::intl::BidiEmbeddingLevel aBaseLevel, nsPresContext* aPresContext,
gfxContext& aRenderingContext, DrawTarget* aTextRunConstructionDrawTarget,
nsFontMetrics& aFontMetrics, Mode aMode,
nscoord aX, // DRAW only
nscoord aY, // DRAW only
nsBidiPositionResolve* aPosResolve, /* may be null */
int32_t aPosResolveCount, nscoord* aWidth /* may be null */);
/**
* Simplified form of ProcessText body, used when aText is a single Unicode
* character (one UTF-16 codepoint, or a surrogate pair), or a run that is
* known to have no bidi content.
*/
static void ProcessSimpleRun(const char16_t* aText, size_t aLength,
mozilla::intl::BidiEmbeddingLevel aBaseLevel,
nsPresContext* aPresContext,
BidiProcessor& aprocessor, Mode aMode,
nsBidiPositionResolve* aPosResolve,
int32_t aPosResolveCount, nscoord* aWidth);
/**
* Traverse the child frames of the block element and:
* Set up an array of the frames in logical order
* Create a string containing the text content of all the frames
* If we encounter content that requires us to split the element into more
* than one paragraph for bidi resolution, resolve the paragraph up to that
* point.
*/
static void TraverseFrames(nsIFrame* aCurrentFrame, BidiParagraphData* aBpd);
/**
* Perform a recursive "pre-traversal" of the child frames of a block or
* inline container frame, to determine whether full bidi resolution is
* actually needed.
* This explores the same frames as TraverseFrames (above), but is less
* expensive and may allow us to avoid performing the full TraverseFrames
* operation.
* @param aFirstChild frame to start traversal from
* @param[in/out] aCurrContent the content node that we've most recently
* scanned for RTL characters (so that when descendant frames refer
* to the same content, we can avoid repeatedly scanning it).
* @return true if it finds that bidi is (or may be) required,
* false if no potentially-bidi content is present.
*/
static bool ChildListMayRequireBidi(nsIFrame* aFirstChild,
nsIContent** aCurrContent);
/**
* Position ruby content frames (ruby base/text frame).
* Called from RepositionRubyFrame.
*/
static void RepositionRubyContentFrame(
nsIFrame* aFrame, mozilla::WritingMode aFrameWM,
const mozilla::LogicalMargin& aBorderPadding);
/*
* Position ruby frames. Called from RepositionFrame.
*/
static nscoord RepositionRubyFrame(
nsIFrame* aFrame, nsContinuationStates* aContinuationStates,
const mozilla::WritingMode aContainerWM,
const mozilla::LogicalMargin& aBorderPadding);
/*
* Position aFrame and its descendants to their visual places. Also if aFrame
* is not leaf, resize it to embrace its children.
*
* @param aFrame The frame which itself and its children are
* going to be repositioned
* @param aIsEvenLevel TRUE means the embedding level of this frame
* is even (LTR)
* @param aStartOrEnd The distance to the start or the end of aFrame
* without considering its inline margin. If the
* container is reordering frames in reverse
* direction, it's the distance to the end,
* otherwise, it's the distance to the start.
* @param aContinuationStates A map from nsIFrame* to
* nsFrameContinuationState
* @return The isize aFrame takes, including margins.
*/
static nscoord RepositionFrame(nsIFrame* aFrame, bool aIsEvenLevel,
nscoord aStartOrEnd,
nsContinuationStates* aContinuationStates,
mozilla::WritingMode aContainerWM,
bool aContainerReverseOrder,
const nsSize& aContainerSize);
/*
* Initialize the continuation state(nsFrameContinuationState) to
* (nullptr, 0) for aFrame and its descendants.
*
* @param aFrame The frame which itself and its descendants will
* be initialized
* @param aContinuationStates A map from nsIFrame* to
* nsFrameContinuationState
*/
static void InitContinuationStates(nsIFrame* aFrame,
nsContinuationStates* aContinuationStates);
/*
* Determine if aFrame is first or last, and set aIsFirst and
* aIsLast values. Also set continuation states of
* aContinuationStates.
*
* A frame is first if it's the first appearance of its continuation
* chain on the line and the chain is on its first line.
* A frame is last if it's the last appearance of its continuation
* chain on the line and the chain is on its last line.
*
* N.B: "First appearance" and "Last appearance" in the previous
* paragraph refer to the frame's inline direction, not necessarily
* the line's.
*
* @param aContinuationStates A map from nsIFrame* to
* nsFrameContinuationState
* @param[in] aSpanDirMatchesLineDir TRUE means that the inline
* direction of aFrame is the same
* as its container
* @param[out] aIsFirst TRUE means aFrame is first frame
* or continuation
* @param[out] aIsLast TRUE means aFrame is last frame
* or continuation
*/
static void IsFirstOrLast(nsIFrame* aFrame,
nsContinuationStates* aContinuationStates,
bool aSpanInLineOrder /* in */,
bool& aIsFirst /* out */, bool& aIsLast /* out */);
/**
* Adjust frame positions following their visual order
*
* @param aFirstChild the first kid
* @return total inline size
*
* @lina 04/11/2000
*/
static nscoord RepositionInlineFrames(const BidiLineData& aBld,
mozilla::WritingMode aLineWM,
const nsSize& aContainerSize,
nscoord aStart);
/**
* Helper method for Resolve()
* Truncate a text frame to the end of a single-directional run and possibly
* create a continuation frame for the remainder of its content.
*
* @param aFrame the original frame
* @param aLine the line box containing aFrame
* @param aNewFrame [OUT] the new frame that was created
* @param aStart [IN] the start of the content mapped by aFrame (and
* any fluid continuations)
* @param aEnd [IN] the offset of the end of the single-directional
* text run.
* @see Resolve()
* @see RemoveBidiContinuation()
*/
static inline void EnsureBidiContinuation(nsIFrame* aFrame,
const nsLineList::iterator aLine,
nsIFrame** aNewFrame,
int32_t aStart, int32_t aEnd);
/**
* Helper method for Resolve()
* Convert one or more bidi continuation frames created in a previous reflow
* by EnsureBidiContinuation() into fluid continuations.
* @param aFrame the frame whose continuations are to be removed
* @param aFirstIndex index of aFrame in mLogicalFrames
* @param aLastIndex index of the last frame to be removed
*
* @see Resolve()
* @see EnsureBidiContinuation()
*/
static void RemoveBidiContinuation(BidiParagraphData* aBpd, nsIFrame* aFrame,
int32_t aFirstIndex, int32_t aLastIndex);
static void CalculateBidiClass(const char16_t* aText, int32_t& aOffset,
int32_t aBidiClassLimit, int32_t& aRunLimit,
int32_t& aRunLength, int32_t& aRunCount,
mozilla::intl::BidiClass& aBidiClass,
mozilla::intl::BidiClass& aPrevBidiClass);
static void StripBidiControlCharacters(char16_t* aText, int32_t& aTextLength);
};
#endif /* nsBidiPresUtils_h___ */