1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef nsBidiPresUtils_h___
8 #define nsBidiPresUtils_h___
10 #include "gfxContext.h"
11 #include "mozilla/intl/BidiClass.h"
12 #include "mozilla/intl/BidiEmbeddingLevel.h"
13 #include "nsBidiUtils.h"
14 #include "nsHashKeys.h"
17 #include "nsLineBox.h"
23 struct BidiParagraphData
;
40 } // namespace mozilla
43 * A structure representing some continuation state for each frame on the line,
44 * used to determine the first and the last continuation frame for each
45 * continuation chain on the line.
47 struct nsFrameContinuationState
: public nsVoidPtrHashKey
{
48 explicit nsFrameContinuationState(const void* aFrame
)
49 : nsVoidPtrHashKey(aFrame
) {}
52 * The first visual frame in the continuation chain containing this frame, or
53 * nullptr if this frame is the first visual frame in the chain.
55 nsIFrame
* mFirstVisualFrame
{nullptr};
58 * The number of frames in the continuation chain containing this frame, if
59 * this frame is the first visual frame of the chain, or 0 otherwise.
61 uint32_t mFrameCount
{0};
64 * TRUE if this frame is the first visual frame of its continuation chain on
65 * this line and the chain has some frames on the previous lines.
67 bool mHasContOnPrevLines
{false};
70 * TRUE if this frame is the first visual frame of its continuation chain on
71 * this line and the chain has some frames left for next lines.
73 bool mHasContOnNextLines
{false};
76 // A table of nsFrameContinuationState objects.
78 // This state is used between calls to nsBidiPresUtils::IsFirstOrLast.
79 struct nsContinuationStates
{
80 static constexpr size_t kArrayMax
= 32;
82 // We use the array to gather up all the continuation state objects. If in
83 // the end there are more than kArrayMax of them, we convert it to a hash
84 // table for faster lookup.
85 bool mUseTable
= false;
86 AutoTArray
<nsFrameContinuationState
, kArrayMax
> mValues
;
87 nsTHashtable
<nsFrameContinuationState
> mTable
;
89 void Insert(nsIFrame
* aFrame
) {
90 if (MOZ_UNLIKELY(mUseTable
)) {
91 mTable
.PutEntry(aFrame
);
94 if (MOZ_LIKELY(mValues
.Length() < kArrayMax
)) {
95 mValues
.AppendElement(aFrame
);
98 for (const auto& entry
: mValues
) {
99 mTable
.PutEntry(entry
.GetKey());
101 mTable
.PutEntry(aFrame
);
106 nsFrameContinuationState
* Get(nsIFrame
* aFrame
) {
107 MOZ_ASSERT(mValues
.IsEmpty() != mTable
.IsEmpty(),
108 "expect entries to either be in mValues or in mTable");
110 return mTable
.GetEntry(aFrame
);
112 for (size_t i
= 0, len
= mValues
.Length(); i
!= len
; ++i
) {
113 if (mValues
[i
].GetKey() == aFrame
) {
122 * A structure representing a logical position which should be resolved
123 * into its visual position during BiDi processing.
125 struct nsBidiPositionResolve
{
126 // [in] Logical index within string.
127 int32_t logicalIndex
;
128 // [out] Visual index within string.
129 // If the logical position was not found, set to kNotFound.
131 // [out] Visual position of the character, from the left (on the X axis), in
132 // twips. Eessentially, this is the X position (relative to the rendering
133 // context) where the text was drawn + the font metric of the visual string to
134 // the left of the given logical position. If the logical position was not
135 // found, set to kNotFound.
136 int32_t visualLeftTwips
;
137 // [out] Visual width of the character, in twips.
138 // If the logical position was not found, set to kNotFound.
142 class nsBidiPresUtils
{
144 typedef mozilla::gfx::DrawTarget DrawTarget
;
150 * Interface for the processor used by ProcessText. Used by process text to
151 * collect information about the width of subruns and to notify where each
152 * subrun should be rendered.
154 class BidiProcessor
{
156 virtual ~BidiProcessor() = default;
159 * Sets the current text with the given length and the given direction.
161 * @remark The reason that the function gives a string instead of an index
162 * is that ProcessText copies and modifies the string passed to it, so
163 * passing an index would be impossible.
165 * @param aText The string of text.
166 * @param aLength The length of the string of text.
167 * @param aDirection The direction of the text. The string will never have
170 virtual void SetText(const char16_t
* aText
, int32_t aLength
,
171 mozilla::intl::BidiDirection aDirection
) = 0;
174 * Returns the measured width of the text given in SetText. If SetText was
175 * not called with valid parameters, the result of this call is undefined.
176 * This call is guaranteed to only be called once between SetText calls.
177 * Will be invoked before DrawText.
179 virtual nscoord
GetWidth() = 0;
182 * Draws the text given in SetText to a rendering context. If SetText was
183 * not called with valid parameters, the result of this call is undefined.
184 * This call is guaranteed to only be called once between SetText calls.
186 * @param aXOffset The offset of the left side of the substring to be drawn
187 * from the beginning of the overall string passed to ProcessText.
189 virtual void DrawText(nscoord aXOffset
) = 0;
193 * Make Bidi engine calculate the embedding levels of the frames that are
194 * descendants of a given block frame.
196 * @param aBlockFrame The block frame
200 static nsresult
Resolve(nsBlockFrame
* aBlockFrame
);
201 static nsresult
ResolveParagraph(BidiParagraphData
* aBpd
);
202 static void ResolveParagraphWithinBlock(BidiParagraphData
* aBpd
);
205 * Reorder this line using Bidi engine.
206 * Update frame array, following the new visual sequence.
208 * @return total inline size
212 static nscoord
ReorderFrames(nsIFrame
* aFirstFrameOnLine
,
213 int32_t aNumFramesOnLine
,
214 mozilla::WritingMode aLineWM
,
215 const nsSize
& aContainerSize
, nscoord aStart
);
218 * Format Unicode text, taking into account bidi capabilities
219 * of the platform. The formatting includes: reordering, Arabic shaping,
220 * symmetric and numeric swapping, removing control characters.
224 static nsresult
FormatUnicodeText(nsPresContext
* aPresContext
,
225 char16_t
* aText
, int32_t& aTextLength
,
226 mozilla::intl::BidiClass aBidiClass
);
229 * Reorder plain text using the Unicode Bidi algorithm and send it to
230 * a rendering context for rendering.
232 * @param[in] aText the string to be rendered (in logical order)
233 * @param aLength the number of characters in the string
234 * @param aBaseLevel the base embedding level of the string
235 * @param aPresContext the presentation context
236 * @param aRenderingContext the rendering context to render to
237 * @param aTextRunConstructionContext the rendering context to be used to
238 * construct the textrun (affects font hinting)
239 * @param aX the x-coordinate to render the string
240 * @param aY the y-coordinate to render the string
241 * @param[in,out] aPosResolve array of logical positions to resolve into
242 * visual positions; can be nullptr if this functionality is not required
243 * @param aPosResolveCount number of items in the aPosResolve array
245 static nsresult
RenderText(const char16_t
* aText
, int32_t aLength
,
246 mozilla::intl::BidiEmbeddingLevel aBaseLevel
,
247 nsPresContext
* aPresContext
,
248 gfxContext
& aRenderingContext
,
249 DrawTarget
* aTextRunConstructionDrawTarget
,
250 nsFontMetrics
& aFontMetrics
, nscoord aX
,
252 nsBidiPositionResolve
* aPosResolve
= nullptr,
253 int32_t aPosResolveCount
= 0) {
254 return ProcessTextForRenderingContext(
255 aText
, aLength
, aBaseLevel
, aPresContext
, aRenderingContext
,
256 aTextRunConstructionDrawTarget
, aFontMetrics
, MODE_DRAW
, aX
, aY
,
257 aPosResolve
, aPosResolveCount
, nullptr);
260 static nscoord
MeasureTextWidth(const char16_t
* aText
, int32_t aLength
,
261 mozilla::intl::BidiEmbeddingLevel aBaseLevel
,
262 nsPresContext
* aPresContext
,
263 gfxContext
& aRenderingContext
,
264 nsFontMetrics
& aFontMetrics
) {
266 nsresult rv
= ProcessTextForRenderingContext(
267 aText
, aLength
, aBaseLevel
, aPresContext
, aRenderingContext
,
268 aRenderingContext
.GetDrawTarget(), aFontMetrics
, MODE_MEASURE
, 0, 0,
269 nullptr, 0, &length
);
270 return NS_SUCCEEDED(rv
) ? length
: 0;
274 * Check if a line is reordered, i.e., if the child frames are not
275 * all laid out left-to-right.
276 * @param aFirstFrameOnLine : first frame of the line to be tested
277 * @param aNumFramesOnLine : number of frames on this line
278 * @param[out] aLeftMost : leftmost frame on this line
279 * @param[out] aRightMost : rightmost frame on this line
281 static bool CheckLineOrder(nsIFrame
* aFirstFrameOnLine
,
282 int32_t aNumFramesOnLine
, nsIFrame
** aLeftmost
,
283 nsIFrame
** aRightmost
);
286 * Get the frame to the right of the given frame, on the same line.
287 * @param aFrame : We're looking for the frame to the right of this frame.
288 * If null, return the leftmost frame on the line.
289 * @param aFirstFrameOnLine : first frame of the line to be tested
290 * @param aNumFramesOnLine : number of frames on this line
292 static nsIFrame
* GetFrameToRightOf(const nsIFrame
* aFrame
,
293 nsIFrame
* aFirstFrameOnLine
,
294 int32_t aNumFramesOnLine
);
297 * Get the frame to the left of the given frame, on the same line.
298 * @param aFrame : We're looking for the frame to the left of this frame.
299 * If null, return the rightmost frame on the line.
300 * @param aFirstFrameOnLine : first frame of the line to be tested
301 * @param aNumFramesOnLine : number of frames on this line
303 static nsIFrame
* GetFrameToLeftOf(const nsIFrame
* aFrame
,
304 nsIFrame
* aFirstFrameOnLine
,
305 int32_t aNumFramesOnLine
);
307 static nsIFrame
* GetFirstLeaf(nsIFrame
* aFrame
);
310 * Get the bidi data of the given (inline) frame.
312 static mozilla::FrameBidiData
GetFrameBidiData(nsIFrame
* aFrame
);
315 * Get the bidi embedding level of the given (inline) frame.
317 static mozilla::intl::BidiEmbeddingLevel
GetFrameEmbeddingLevel(
321 * Get the bidi base level of the given (inline) frame.
323 static mozilla::intl::BidiEmbeddingLevel
GetFrameBaseLevel(
324 const nsIFrame
* aFrame
);
327 * Get a mozilla::intl::BidiDirection representing the direction implied by
328 * the bidi base level of the frame.
329 * @return mozilla::intl::BidiDirection
331 static mozilla::intl::BidiDirection
ParagraphDirection(
332 const nsIFrame
* aFrame
) {
333 return GetFrameBaseLevel(aFrame
).Direction();
337 * Get a mozilla::intl::BidiDirection representing the direction implied by
338 * the bidi embedding level of the frame.
339 * @return mozilla::intl::BidiDirection
341 static mozilla::intl::BidiDirection
FrameDirection(nsIFrame
* aFrame
) {
342 return GetFrameEmbeddingLevel(aFrame
).Direction();
345 static bool IsFrameInParagraphDirection(nsIFrame
* aFrame
) {
346 return ParagraphDirection(aFrame
) == FrameDirection(aFrame
);
349 // This is faster than nsBidiPresUtils::IsFrameInParagraphDirection,
350 // because it uses the frame pointer passed in without drilling down to
352 static bool IsReversedDirectionFrame(const nsIFrame
* aFrame
) {
353 mozilla::FrameBidiData bidiData
= aFrame
->GetBidiData();
354 return !bidiData
.embeddingLevel
.IsSameDirection(bidiData
.baseLevel
);
357 enum Mode
{ MODE_DRAW
, MODE_MEASURE
};
360 * Reorder plain text using the Unicode Bidi algorithm and send it to
361 * a processor for rendering or measuring
363 * @param[in] aText the string to be processed (in logical order)
364 * @param aLength the number of characters in the string
365 * @param aBaseLevel the base embedding level of the string
366 * @param aPresContext the presentation context
367 * @param aprocessor the bidi processor
368 * @param aMode the operation to process
369 * MODE_DRAW - invokes DrawText on the processor for each substring
370 * MODE_MEASURE - does not invoke DrawText on the processor
371 * Note that the string is always measured, regardless of mode
372 * @param[in,out] aPosResolve array of logical positions to resolve into
373 * visual positions; can be nullptr if this functionality is not required
374 * @param aPosResolveCount number of items in the aPosResolve array
375 * @param[out] aWidth Pointer to where the width will be stored (may be null)
377 static nsresult
ProcessText(const char16_t
* aText
, size_t aLength
,
378 mozilla::intl::BidiEmbeddingLevel aBaseLevel
,
379 nsPresContext
* aPresContext
,
380 BidiProcessor
& aprocessor
, Mode aMode
,
381 nsBidiPositionResolve
* aPosResolve
,
382 int32_t aPosResolveCount
, nscoord
* aWidth
,
383 mozilla::intl::Bidi
& aBidiEngine
);
386 * Use style attributes to determine the base paragraph level to pass to the
389 * If |unicode-bidi| is set to "[-moz-]plaintext", returns
390 * BidiEmbeddingLevel::DefaultLTR, in other words the direction is determined
391 * from the first strong character in the text according to rules P2 and P3 of
392 * the bidi algorithm, or LTR if there is no strong character.
394 * Otherwise returns BidiEmbeddingLevel::LTR or BidiEmbeddingLevel::RTL
395 * depending on the value of |direction|
397 static mozilla::intl::BidiEmbeddingLevel
BidiLevelFromStyle(
398 mozilla::ComputedStyle
* aComputedStyle
);
401 static nsresult
ProcessTextForRenderingContext(
402 const char16_t
* aText
, int32_t aLength
,
403 mozilla::intl::BidiEmbeddingLevel aBaseLevel
, nsPresContext
* aPresContext
,
404 gfxContext
& aRenderingContext
, DrawTarget
* aTextRunConstructionDrawTarget
,
405 nsFontMetrics
& aFontMetrics
, Mode aMode
,
406 nscoord aX
, // DRAW only
407 nscoord aY
, // DRAW only
408 nsBidiPositionResolve
* aPosResolve
, /* may be null */
409 int32_t aPosResolveCount
, nscoord
* aWidth
/* may be null */);
412 * Simplified form of ProcessText body, used when aText is a single Unicode
413 * character (one UTF-16 codepoint, or a surrogate pair), or a run that is
414 * known to have no bidi content.
416 static void ProcessSimpleRun(const char16_t
* aText
, size_t aLength
,
417 mozilla::intl::BidiEmbeddingLevel aBaseLevel
,
418 nsPresContext
* aPresContext
,
419 BidiProcessor
& aprocessor
, Mode aMode
,
420 nsBidiPositionResolve
* aPosResolve
,
421 int32_t aPosResolveCount
, nscoord
* aWidth
);
424 * Traverse the child frames of the block element and:
425 * Set up an array of the frames in logical order
426 * Create a string containing the text content of all the frames
427 * If we encounter content that requires us to split the element into more
428 * than one paragraph for bidi resolution, resolve the paragraph up to that
431 static void TraverseFrames(nsIFrame
* aCurrentFrame
, BidiParagraphData
* aBpd
);
434 * Perform a recursive "pre-traversal" of the child frames of a block or
435 * inline container frame, to determine whether full bidi resolution is
437 * This explores the same frames as TraverseFrames (above), but is less
438 * expensive and may allow us to avoid performing the full TraverseFrames
440 * @param aFirstChild frame to start traversal from
441 * @param[in/out] aCurrContent the content node that we've most recently
442 * scanned for RTL characters (so that when descendant frames refer
443 * to the same content, we can avoid repeatedly scanning it).
444 * @return true if it finds that bidi is (or may be) required,
445 * false if no potentially-bidi content is present.
447 static bool ChildListMayRequireBidi(nsIFrame
* aFirstChild
,
448 nsIContent
** aCurrContent
);
451 * Position ruby content frames (ruby base/text frame).
452 * Called from RepositionRubyFrame.
454 static void RepositionRubyContentFrame(
455 nsIFrame
* aFrame
, mozilla::WritingMode aFrameWM
,
456 const mozilla::LogicalMargin
& aBorderPadding
);
459 * Position ruby frames. Called from RepositionFrame.
461 static nscoord
RepositionRubyFrame(
462 nsIFrame
* aFrame
, nsContinuationStates
* aContinuationStates
,
463 const mozilla::WritingMode aContainerWM
,
464 const mozilla::LogicalMargin
& aBorderPadding
);
467 * Position aFrame and its descendants to their visual places. Also if aFrame
468 * is not leaf, resize it to embrace its children.
470 * @param aFrame The frame which itself and its children are
471 * going to be repositioned
472 * @param aIsEvenLevel TRUE means the embedding level of this frame
474 * @param aStartOrEnd The distance to the start or the end of aFrame
475 * without considering its inline margin. If the
476 * container is reordering frames in reverse
477 * direction, it's the distance to the end,
478 * otherwise, it's the distance to the start.
479 * @param aContinuationStates A map from nsIFrame* to
480 * nsFrameContinuationState
481 * @return The isize aFrame takes, including margins.
483 static nscoord
RepositionFrame(nsIFrame
* aFrame
, bool aIsEvenLevel
,
485 nsContinuationStates
* aContinuationStates
,
486 mozilla::WritingMode aContainerWM
,
487 bool aContainerReverseOrder
,
488 const nsSize
& aContainerSize
);
491 * Initialize the continuation state(nsFrameContinuationState) to
492 * (nullptr, 0) for aFrame and its descendants.
494 * @param aFrame The frame which itself and its descendants will
496 * @param aContinuationStates A map from nsIFrame* to
497 * nsFrameContinuationState
499 static void InitContinuationStates(nsIFrame
* aFrame
,
500 nsContinuationStates
* aContinuationStates
);
503 * Determine if aFrame is first or last, and set aIsFirst and
504 * aIsLast values. Also set continuation states of
505 * aContinuationStates.
507 * A frame is first if it's the first appearance of its continuation
508 * chain on the line and the chain is on its first line.
509 * A frame is last if it's the last appearance of its continuation
510 * chain on the line and the chain is on its last line.
512 * N.B: "First appearance" and "Last appearance" in the previous
513 * paragraph refer to the frame's inline direction, not necessarily
516 * @param aContinuationStates A map from nsIFrame* to
517 * nsFrameContinuationState
518 * @param[in] aSpanDirMatchesLineDir TRUE means that the inline
519 * direction of aFrame is the same
521 * @param[out] aIsFirst TRUE means aFrame is first frame
523 * @param[out] aIsLast TRUE means aFrame is last frame
526 static void IsFirstOrLast(nsIFrame
* aFrame
,
527 nsContinuationStates
* aContinuationStates
,
528 bool aSpanInLineOrder
/* in */,
529 bool& aIsFirst
/* out */, bool& aIsLast
/* out */);
532 * Adjust frame positions following their visual order
534 * @param aFirstChild the first kid
535 * @return total inline size
539 static nscoord
RepositionInlineFrames(BidiLineData
* aBld
,
540 mozilla::WritingMode aLineWM
,
541 const nsSize
& aContainerSize
,
545 * Helper method for Resolve()
546 * Truncate a text frame to the end of a single-directional run and possibly
547 * create a continuation frame for the remainder of its content.
549 * @param aFrame the original frame
550 * @param aLine the line box containing aFrame
551 * @param aNewFrame [OUT] the new frame that was created
552 * @param aStart [IN] the start of the content mapped by aFrame (and
553 * any fluid continuations)
554 * @param aEnd [IN] the offset of the end of the single-directional
557 * @see RemoveBidiContinuation()
559 static inline void EnsureBidiContinuation(nsIFrame
* aFrame
,
560 const nsLineList::iterator aLine
,
561 nsIFrame
** aNewFrame
,
562 int32_t aStart
, int32_t aEnd
);
565 * Helper method for Resolve()
566 * Convert one or more bidi continuation frames created in a previous reflow
567 * by EnsureBidiContinuation() into fluid continuations.
568 * @param aFrame the frame whose continuations are to be removed
569 * @param aFirstIndex index of aFrame in mLogicalFrames
570 * @param aLastIndex index of the last frame to be removed
573 * @see EnsureBidiContinuation()
575 static void RemoveBidiContinuation(BidiParagraphData
* aBpd
, nsIFrame
* aFrame
,
576 int32_t aFirstIndex
, int32_t aLastIndex
);
578 static void CalculateBidiClass(const char16_t
* aText
, int32_t& aOffset
,
579 int32_t aBidiClassLimit
, int32_t& aRunLimit
,
580 int32_t& aRunLength
, int32_t& aRunCount
,
581 mozilla::intl::BidiClass
& aBidiClass
,
582 mozilla::intl::BidiClass
& aPrevBidiClass
);
584 static void StripBidiControlCharacters(char16_t
* aText
, int32_t& aTextLength
);
587 #endif /* nsBidiPresUtils_h___ */