diff options
Diffstat (limited to 'layout/base/nsBidiPresUtils.h')
-rw-r--r-- | layout/base/nsBidiPresUtils.h | 587 |
1 files changed, 587 insertions, 0 deletions
diff --git a/layout/base/nsBidiPresUtils.h b/layout/base/nsBidiPresUtils.h new file mode 100644 index 0000000000..c369a06f2d --- /dev/null +++ b/layout/base/nsBidiPresUtils.h @@ -0,0 +1,587 @@ +/* -*- 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; +struct 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: + 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(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___ */ |