nsBidiPresUtils.h (24029B)
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/. */ 6 7 #ifndef nsBidiPresUtils_h___ 8 #define nsBidiPresUtils_h___ 9 10 #include "gfxContext.h" 11 #include "mozilla/intl/BidiClass.h" 12 #include "mozilla/intl/BidiEmbeddingLevel.h" 13 #include "nsBidiUtils.h" 14 #include "nsCoord.h" 15 #include "nsHashKeys.h" 16 #include "nsLineBox.h" 17 #include "nsTArray.h" 18 19 #ifdef DrawText 20 # undef DrawText 21 #endif 22 23 struct BidiParagraphData; 24 class BidiLineData; 25 class gfxContext; 26 class nsFontMetrics; 27 class nsIFrame; 28 class nsBlockFrame; 29 class nsPresContext; 30 struct nsSize; 31 template <class T> 32 class nsTHashtable; 33 namespace mozilla { 34 namespace intl { 35 class Bidi; 36 } 37 class ComputedStyle; 38 class LogicalMargin; 39 class WritingMode; 40 } // namespace mozilla 41 42 /** 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. 46 */ 47 struct nsFrameContinuationState : public nsVoidPtrHashKey { 48 explicit nsFrameContinuationState(const void* aFrame) 49 : nsVoidPtrHashKey(aFrame) {} 50 51 /** 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. 54 */ 55 nsIFrame* mFirstVisualFrame{nullptr}; 56 57 /** 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. 60 */ 61 uint32_t mFrameCount{0}; 62 63 /** 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. 66 */ 67 bool mHasContOnPrevLines{false}; 68 69 /** 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. 72 */ 73 bool mHasContOnNextLines{false}; 74 }; 75 76 // A table of nsFrameContinuationState objects. 77 // 78 // This state is used between calls to nsBidiPresUtils::IsFirstOrLast. 79 struct nsContinuationStates { 80 static constexpr size_t kArrayMax = 32; 81 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; 88 89 void Insert(nsIFrame* aFrame) { 90 if (MOZ_UNLIKELY(mUseTable)) { 91 mTable.PutEntry(aFrame); 92 return; 93 } 94 if (MOZ_LIKELY(mValues.Length() < kArrayMax)) { 95 mValues.AppendElement(aFrame); 96 return; 97 } 98 for (const auto& entry : mValues) { 99 mTable.PutEntry(entry.GetKey()); 100 } 101 mTable.PutEntry(aFrame); 102 mValues.Clear(); 103 mUseTable = true; 104 } 105 106 nsFrameContinuationState* Get(nsIFrame* aFrame) { 107 MOZ_ASSERT(mValues.IsEmpty() != mTable.IsEmpty(), 108 "expect entries to either be in mValues or in mTable"); 109 if (mUseTable) { 110 return mTable.GetEntry(aFrame); 111 } 112 for (size_t i = 0, len = mValues.Length(); i != len; ++i) { 113 if (mValues[i].GetKey() == aFrame) { 114 return &mValues[i]; 115 } 116 } 117 return nullptr; 118 } 119 }; 120 121 /** 122 * A structure representing a logical position which should be resolved 123 * into its visual position during BiDi processing. 124 */ 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. 130 int32_t visualIndex; 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. 139 int32_t visualWidth; 140 }; 141 142 class nsBidiPresUtils { 143 public: 144 typedef mozilla::gfx::DrawTarget DrawTarget; 145 146 nsBidiPresUtils(); 147 ~nsBidiPresUtils(); 148 149 /** 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. 153 */ 154 class BidiProcessor { 155 public: 156 virtual ~BidiProcessor() = default; 157 158 /** 159 * Sets the current text with the given length and the given direction. 160 * 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. 164 * 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 168 * mixed direction. 169 */ 170 virtual void SetText(const char16_t* aText, int32_t aLength, 171 mozilla::intl::BidiDirection aDirection) = 0; 172 173 /** 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. 178 */ 179 virtual nscoord GetWidth() = 0; 180 181 /** 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. 185 * 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. 188 */ 189 virtual void DrawText(nscoord aXOffset) = 0; 190 }; 191 192 /** 193 * Make Bidi engine calculate the embedding levels of the frames that are 194 * descendants of a given block frame. 195 * 196 * @param aBlockFrame The block frame 197 * 198 * @lina 06/18/2000 199 */ 200 static nsresult Resolve(nsBlockFrame* aBlockFrame); 201 static nsresult ResolveParagraph(BidiParagraphData* aBpd); 202 static void ResolveParagraphWithinBlock(BidiParagraphData* aBpd); 203 204 /** 205 * Reorder this line using Bidi engine. 206 * Update frame array, following the new visual sequence. 207 * 208 * @return total inline size 209 * 210 * @lina 05/02/2000 211 */ 212 static nscoord ReorderFrames(nsIFrame* aFirstFrameOnLine, 213 int32_t aNumFramesOnLine, 214 mozilla::WritingMode aLineWM, 215 const nsSize& aContainerSize, nscoord aStart); 216 217 /** 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. 221 * 222 * @lina 06/18/2000 223 */ 224 static nsresult FormatUnicodeText(nsPresContext* aPresContext, 225 char16_t* aText, int32_t& aTextLength, 226 mozilla::intl::BidiClass aBidiClass); 227 228 /** 229 * Reorder plain text using the Unicode Bidi algorithm and send it to 230 * a rendering context for rendering. 231 * 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 244 */ 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, 251 nscoord aY, 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); 258 } 259 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) { 265 nscoord length; 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; 271 } 272 273 /** 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 280 */ 281 static bool CheckLineOrder(nsIFrame* aFirstFrameOnLine, 282 int32_t aNumFramesOnLine, nsIFrame** aLeftmost, 283 nsIFrame** aRightmost); 284 285 /** 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 291 */ 292 static nsIFrame* GetFrameToRightOf(const nsIFrame* aFrame, 293 nsIFrame* aFirstFrameOnLine, 294 int32_t aNumFramesOnLine); 295 296 /** 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 302 */ 303 static nsIFrame* GetFrameToLeftOf(const nsIFrame* aFrame, 304 nsIFrame* aFirstFrameOnLine, 305 int32_t aNumFramesOnLine); 306 307 static nsIFrame* GetFirstLeaf(nsIFrame* aFrame); 308 309 /** 310 * Get the bidi data of the given (inline) frame. 311 */ 312 static mozilla::FrameBidiData GetFrameBidiData(nsIFrame* aFrame); 313 314 /** 315 * Get the bidi embedding level of the given (inline) frame. 316 */ 317 static mozilla::intl::BidiEmbeddingLevel GetFrameEmbeddingLevel( 318 nsIFrame* aFrame); 319 320 /** 321 * Get the bidi base level of the given (inline) frame. 322 */ 323 static mozilla::intl::BidiEmbeddingLevel GetFrameBaseLevel( 324 const nsIFrame* aFrame); 325 326 /** 327 * Get a mozilla::intl::BidiDirection representing the direction implied by 328 * the bidi base level of the frame. 329 * @return mozilla::intl::BidiDirection 330 */ 331 static mozilla::intl::BidiDirection ParagraphDirection( 332 const nsIFrame* aFrame) { 333 return GetFrameBaseLevel(aFrame).Direction(); 334 } 335 336 /** 337 * Get a mozilla::intl::BidiDirection representing the direction implied by 338 * the bidi embedding level of the frame. 339 * @return mozilla::intl::BidiDirection 340 */ 341 static mozilla::intl::BidiDirection FrameDirection(nsIFrame* aFrame) { 342 return GetFrameEmbeddingLevel(aFrame).Direction(); 343 } 344 345 static bool IsFrameInParagraphDirection(nsIFrame* aFrame) { 346 return ParagraphDirection(aFrame) == FrameDirection(aFrame); 347 } 348 349 // This is faster than nsBidiPresUtils::IsFrameInParagraphDirection, 350 // because it uses the frame pointer passed in without drilling down to 351 // the leaf frame. 352 static bool IsReversedDirectionFrame(const nsIFrame* aFrame) { 353 mozilla::FrameBidiData bidiData = aFrame->GetBidiData(); 354 return !bidiData.embeddingLevel.IsSameDirection(bidiData.baseLevel); 355 } 356 357 enum Mode { MODE_DRAW, MODE_MEASURE }; 358 359 /** 360 * Reorder plain text using the Unicode Bidi algorithm and send it to 361 * a processor for rendering or measuring 362 * 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) 376 */ 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); 384 385 /** 386 * Use style attributes to determine the base paragraph level to pass to the 387 * bidi algorithm. 388 * 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. 393 * 394 * Otherwise returns BidiEmbeddingLevel::LTR or BidiEmbeddingLevel::RTL 395 * depending on the value of |direction| 396 */ 397 static mozilla::intl::BidiEmbeddingLevel BidiLevelFromStyle( 398 mozilla::ComputedStyle* aComputedStyle); 399 400 private: 401 friend class BidiLineData; 402 403 static nsresult ProcessTextForRenderingContext( 404 const char16_t* aText, int32_t aLength, 405 mozilla::intl::BidiEmbeddingLevel aBaseLevel, nsPresContext* aPresContext, 406 gfxContext& aRenderingContext, DrawTarget* aTextRunConstructionDrawTarget, 407 nsFontMetrics& aFontMetrics, Mode aMode, 408 nscoord aX, // DRAW only 409 nscoord aY, // DRAW only 410 nsBidiPositionResolve* aPosResolve, /* may be null */ 411 int32_t aPosResolveCount, nscoord* aWidth /* may be null */); 412 413 /** 414 * Simplified form of ProcessText body, used when aText is a single Unicode 415 * character (one UTF-16 codepoint, or a surrogate pair), or a run that is 416 * known to have no bidi content. 417 */ 418 static void ProcessSimpleRun(const char16_t* aText, size_t aLength, 419 mozilla::intl::BidiEmbeddingLevel aBaseLevel, 420 nsPresContext* aPresContext, 421 BidiProcessor& aprocessor, Mode aMode, 422 nsBidiPositionResolve* aPosResolve, 423 int32_t aPosResolveCount, nscoord* aWidth); 424 425 /** 426 * Traverse the child frames of the block element and: 427 * Set up an array of the frames in logical order 428 * Create a string containing the text content of all the frames 429 * If we encounter content that requires us to split the element into more 430 * than one paragraph for bidi resolution, resolve the paragraph up to that 431 * point. 432 */ 433 static void TraverseFrames(nsIFrame* aCurrentFrame, BidiParagraphData* aBpd); 434 435 /** 436 * Perform a recursive "pre-traversal" of the child frames of a block or 437 * inline container frame, to determine whether full bidi resolution is 438 * actually needed. 439 * This explores the same frames as TraverseFrames (above), but is less 440 * expensive and may allow us to avoid performing the full TraverseFrames 441 * operation. 442 * @param aFirstChild frame to start traversal from 443 * @param[in/out] aCurrContent the content node that we've most recently 444 * scanned for RTL characters (so that when descendant frames refer 445 * to the same content, we can avoid repeatedly scanning it). 446 * @return true if it finds that bidi is (or may be) required, 447 * false if no potentially-bidi content is present. 448 */ 449 static bool ChildListMayRequireBidi(nsIFrame* aFirstChild, 450 nsIContent** aCurrContent); 451 452 /** 453 * Position ruby content frames (ruby base/text frame). 454 * Called from RepositionRubyFrame. 455 */ 456 static void RepositionRubyContentFrame( 457 nsIFrame* aFrame, mozilla::WritingMode aFrameWM, 458 const mozilla::LogicalMargin& aBorderPadding); 459 460 /* 461 * Position ruby frames. Called from RepositionFrame. 462 */ 463 static nscoord RepositionRubyFrame( 464 nsIFrame* aFrame, nsContinuationStates* aContinuationStates, 465 const mozilla::WritingMode aContainerWM, 466 const mozilla::LogicalMargin& aBorderPadding); 467 468 /* 469 * Position aFrame and its descendants to their visual places. Also if aFrame 470 * is not leaf, resize it to embrace its children. 471 * 472 * @param aFrame The frame which itself and its children are 473 * going to be repositioned 474 * @param aIsEvenLevel TRUE means the embedding level of this frame 475 * is even (LTR) 476 * @param aStartOrEnd The distance to the start or the end of aFrame 477 * without considering its inline margin. If the 478 * container is reordering frames in reverse 479 * direction, it's the distance to the end, 480 * otherwise, it's the distance to the start. 481 * @param aContinuationStates A map from nsIFrame* to 482 * nsFrameContinuationState 483 * @return The isize aFrame takes, including margins. 484 */ 485 static nscoord RepositionFrame(nsIFrame* aFrame, bool aIsEvenLevel, 486 nscoord aStartOrEnd, 487 nsContinuationStates* aContinuationStates, 488 mozilla::WritingMode aContainerWM, 489 bool aContainerReverseOrder, 490 const nsSize& aContainerSize); 491 492 /* 493 * Initialize the continuation state(nsFrameContinuationState) to 494 * (nullptr, 0) for aFrame and its descendants. 495 * 496 * @param aFrame The frame which itself and its descendants will 497 * be initialized 498 * @param aContinuationStates A map from nsIFrame* to 499 * nsFrameContinuationState 500 */ 501 static void InitContinuationStates(nsIFrame* aFrame, 502 nsContinuationStates* aContinuationStates); 503 504 /* 505 * Determine if aFrame is first or last, and set aIsFirst and 506 * aIsLast values. Also set continuation states of 507 * aContinuationStates. 508 * 509 * A frame is first if it's the first appearance of its continuation 510 * chain on the line and the chain is on its first line. 511 * A frame is last if it's the last appearance of its continuation 512 * chain on the line and the chain is on its last line. 513 * 514 * N.B: "First appearance" and "Last appearance" in the previous 515 * paragraph refer to the frame's inline direction, not necessarily 516 * the line's. 517 * 518 * @param aContinuationStates A map from nsIFrame* to 519 * nsFrameContinuationState 520 * @param[in] aSpanDirMatchesLineDir TRUE means that the inline 521 * direction of aFrame is the same 522 * as its container 523 * @param[out] aIsFirst TRUE means aFrame is first frame 524 * or continuation 525 * @param[out] aIsLast TRUE means aFrame is last frame 526 * or continuation 527 */ 528 static void IsFirstOrLast(nsIFrame* aFrame, 529 nsContinuationStates* aContinuationStates, 530 bool aSpanInLineOrder /* in */, 531 bool& aIsFirst /* out */, bool& aIsLast /* out */); 532 533 /** 534 * Adjust frame positions following their visual order 535 * 536 * @param aFirstChild the first kid 537 * @return total inline size 538 * 539 * @lina 04/11/2000 540 */ 541 static nscoord RepositionInlineFrames(const BidiLineData& aBld, 542 mozilla::WritingMode aLineWM, 543 const nsSize& aContainerSize, 544 nscoord aStart); 545 546 /** 547 * Helper method for Resolve() 548 * Truncate a text frame to the end of a single-directional run and possibly 549 * create a continuation frame for the remainder of its content. 550 * 551 * @param aFrame the original frame 552 * @param aLine the line box containing aFrame 553 * @param aNewFrame [OUT] the new frame that was created 554 * @param aStart [IN] the start of the content mapped by aFrame (and 555 * any fluid continuations) 556 * @param aEnd [IN] the offset of the end of the single-directional 557 * text run. 558 * @see Resolve() 559 * @see RemoveBidiContinuation() 560 */ 561 static inline void EnsureBidiContinuation(nsIFrame* aFrame, 562 const nsLineList::iterator aLine, 563 nsIFrame** aNewFrame, 564 int32_t aStart, int32_t aEnd); 565 566 /** 567 * Helper method for Resolve() 568 * Convert one or more bidi continuation frames created in a previous reflow 569 * by EnsureBidiContinuation() into fluid continuations. 570 * @param aFrame the frame whose continuations are to be removed 571 * @param aFirstIndex index of aFrame in mLogicalFrames 572 * @param aLastIndex index of the last frame to be removed 573 * 574 * @see Resolve() 575 * @see EnsureBidiContinuation() 576 */ 577 static void RemoveBidiContinuation(BidiParagraphData* aBpd, nsIFrame* aFrame, 578 int32_t aFirstIndex, int32_t aLastIndex); 579 580 static void CalculateBidiClass(const char16_t* aText, int32_t& aOffset, 581 int32_t aBidiClassLimit, int32_t& aRunLimit, 582 int32_t& aRunLength, int32_t& aRunCount, 583 mozilla::intl::BidiClass& aBidiClass, 584 mozilla::intl::BidiClass& aPrevBidiClass); 585 586 static void StripBidiControlCharacters(char16_t* aText, int32_t& aTextLength); 587 }; 588 589 #endif /* nsBidiPresUtils_h___ */