summaryrefslogtreecommitdiffstats
path: root/layout/base/nsBidiPresUtils.h
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base/nsBidiPresUtils.h')
-rw-r--r--layout/base/nsBidiPresUtils.h587
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___ */