nsBidiPresUtils.cpp (95887B)
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 #include "nsBidiPresUtils.h" 8 9 #include <algorithm> 10 11 #include "RubyUtils.h" 12 #include "gfxContext.h" 13 #include "mozilla/Casting.h" 14 #include "mozilla/IntegerRange.h" 15 #include "mozilla/Maybe.h" 16 #include "mozilla/PresShell.h" 17 #include "mozilla/dom/Text.h" 18 #include "mozilla/intl/Bidi.h" 19 #include "nsBidiUtils.h" 20 #include "nsBlockFrame.h" 21 #include "nsCSSFrameConstructor.h" 22 #include "nsContainerFrame.h" 23 #include "nsFirstLetterFrame.h" 24 #include "nsFontMetrics.h" 25 #include "nsGkAtoms.h" 26 #include "nsIFrameInlines.h" 27 #include "nsInlineFrame.h" 28 #include "nsPlaceholderFrame.h" 29 #include "nsPointerHashKeys.h" 30 #include "nsPresContext.h" 31 #include "nsRubyBaseContainerFrame.h" 32 #include "nsRubyBaseFrame.h" 33 #include "nsRubyFrame.h" 34 #include "nsRubyTextContainerFrame.h" 35 #include "nsRubyTextFrame.h" 36 #include "nsStyleStructInlines.h" 37 #include "nsTextFrame.h" 38 #include "nsUnicodeProperties.h" 39 40 #undef NOISY_BIDI 41 #undef REALLY_NOISY_BIDI 42 43 using namespace mozilla; 44 45 using BidiEngine = intl::Bidi; 46 using BidiClass = intl::BidiClass; 47 using BidiDirection = intl::BidiDirection; 48 using BidiEmbeddingLevel = intl::BidiEmbeddingLevel; 49 50 static const char16_t kNextLine = 0x0085; 51 static const char16_t kZWSP = 0x200B; 52 static const char16_t kLineSeparator = 0x2028; 53 static const char16_t kParagraphSeparator = 0x2029; 54 static const char16_t kObjectSubstitute = 0xFFFC; 55 static const char16_t kLRE = 0x202A; 56 static const char16_t kRLE = 0x202B; 57 static const char16_t kLRO = 0x202D; 58 static const char16_t kRLO = 0x202E; 59 static const char16_t kPDF = 0x202C; 60 static const char16_t kLRI = 0x2066; 61 static const char16_t kRLI = 0x2067; 62 static const char16_t kFSI = 0x2068; 63 static const char16_t kPDI = 0x2069; 64 // All characters with Bidi type Segment Separator or Block Separator. 65 // This should be kept in sync with the table in ReplaceSeparators. 66 static const char16_t kSeparators[] = { 67 char16_t('\t'), char16_t('\r'), char16_t('\n'), char16_t(0xb), 68 char16_t(0x1c), char16_t(0x1d), char16_t(0x1e), char16_t(0x1f), 69 kNextLine, kParagraphSeparator, char16_t(0)}; 70 71 #define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1) 72 73 // This exists just to be a type; the value doesn't matter. 74 enum class BidiControlFrameType { Value }; 75 76 static bool IsIsolateControl(char16_t aChar) { 77 return aChar == kLRI || aChar == kRLI || aChar == kFSI; 78 } 79 80 // Given a ComputedStyle, return any bidi control character necessary to 81 // implement style properties that override directionality (i.e. if it has 82 // unicode-bidi:bidi-override, or text-orientation:upright in vertical 83 // writing mode) when applying the bidi algorithm. 84 // 85 // Returns 0 if no override control character is implied by this style. 86 static char16_t GetBidiOverride(ComputedStyle* aComputedStyle) { 87 const nsStyleVisibility* vis = aComputedStyle->StyleVisibility(); 88 if ((vis->mWritingMode == StyleWritingModeProperty::VerticalRl || 89 vis->mWritingMode == StyleWritingModeProperty::VerticalLr) && 90 vis->mTextOrientation == StyleTextOrientation::Upright) { 91 return kLRO; 92 } 93 const nsStyleTextReset* text = aComputedStyle->StyleTextReset(); 94 if (text->mUnicodeBidi == StyleUnicodeBidi::BidiOverride || 95 text->mUnicodeBidi == StyleUnicodeBidi::IsolateOverride) { 96 return StyleDirection::Rtl == vis->mDirection ? kRLO : kLRO; 97 } 98 return 0; 99 } 100 101 // Given a ComputedStyle, return any bidi control character necessary to 102 // implement style properties that affect bidi resolution (i.e. if it 103 // has unicode-bidiembed, isolate, or plaintext) when applying the bidi 104 // algorithm. 105 // 106 // Returns 0 if no control character is implied by the style. 107 // 108 // Note that GetBidiOverride and GetBidiControl need to be separate 109 // because in the case of unicode-bidi:isolate-override we need both 110 // FSI and LRO/RLO. 111 static char16_t GetBidiControl(ComputedStyle* aComputedStyle) { 112 const nsStyleVisibility* vis = aComputedStyle->StyleVisibility(); 113 const nsStyleTextReset* text = aComputedStyle->StyleTextReset(); 114 switch (text->mUnicodeBidi) { 115 case StyleUnicodeBidi::Embed: 116 return StyleDirection::Rtl == vis->mDirection ? kRLE : kLRE; 117 case StyleUnicodeBidi::Isolate: 118 // <bdi> element already has its directionality set from content so 119 // we never need to return kFSI. 120 return StyleDirection::Rtl == vis->mDirection ? kRLI : kLRI; 121 case StyleUnicodeBidi::IsolateOverride: 122 case StyleUnicodeBidi::Plaintext: 123 return kFSI; 124 case StyleUnicodeBidi::Normal: 125 case StyleUnicodeBidi::BidiOverride: 126 break; 127 } 128 129 return 0; 130 } 131 132 #ifdef DEBUG 133 static inline bool AreContinuationsInOrder(nsIFrame* aFrame1, 134 nsIFrame* aFrame2) { 135 nsIFrame* f = aFrame1; 136 do { 137 f = f->GetNextContinuation(); 138 } while (f && f != aFrame2); 139 return !!f; 140 } 141 #endif 142 143 struct MOZ_STACK_CLASS BidiParagraphData { 144 struct FrameInfo { 145 FrameInfo(nsIFrame* aFrame, nsBlockInFlowLineIterator& aLineIter) 146 : mFrame(aFrame), 147 mBlockContainer(aLineIter.GetContainer()), 148 mInOverflow(aLineIter.GetInOverflow()) {} 149 150 explicit FrameInfo(BidiControlFrameType aValue) 151 : mFrame(NS_BIDI_CONTROL_FRAME), 152 mBlockContainer(nullptr), 153 mInOverflow(false) {} 154 155 FrameInfo() 156 : mFrame(nullptr), mBlockContainer(nullptr), mInOverflow(false) {} 157 158 nsIFrame* mFrame; 159 160 // The block containing mFrame (i.e., which continuation). 161 nsBlockFrame* mBlockContainer; 162 163 // true if mFrame is in mBlockContainer's overflow lines, false if 164 // in primary lines 165 bool mInOverflow; 166 }; 167 168 nsAutoString mBuffer; 169 AutoTArray<char16_t, 16> mEmbeddingStack; 170 AutoTArray<FrameInfo, 16> mLogicalFrames; 171 nsTHashMap<nsPtrHashKey<const nsIContent>, int32_t> mContentToFrameIndex; 172 // Cached presentation context for the frames we're processing. 173 nsPresContext* mPresContext; 174 bool mIsVisual; 175 bool mRequiresBidi; 176 BidiEmbeddingLevel mParaLevel; 177 nsIContent* mPrevContent; 178 179 /** 180 * This class is designed to manage the process of mapping a frame to 181 * the line that it's in, when we know that (a) the frames we ask it 182 * about are always in the block's lines and (b) each successive frame 183 * we ask it about is the same as or after (in depth-first search 184 * order) the previous. 185 * 186 * Since we move through the lines at a different pace in Traverse and 187 * ResolveParagraph, we use one of these for each. 188 * 189 * The state of the mapping is also different between TraverseFrames 190 * and ResolveParagraph since since resolving can call functions 191 * (EnsureBidiContinuation or SplitInlineAncestors) that can create 192 * new frames and thus break lines. 193 * 194 * The TraverseFrames iterator is only used in some edge cases. 195 */ 196 struct FastLineIterator { 197 FastLineIterator() : mPrevFrame(nullptr), mNextLineStart(nullptr) {} 198 199 // These iterators *and* mPrevFrame track the line list that we're 200 // iterating over. 201 // 202 // mPrevFrame, if non-null, should be either the frame we're currently 203 // handling (in ResolveParagraph or TraverseFrames, depending on the 204 // iterator) or a frame before it, and is also guaranteed to either be in 205 // mCurrentLine or have been in mCurrentLine until recently. 206 // 207 // In case the splitting causes block frames to break lines, however, we 208 // also track the first frame of the next line. If that changes, it means 209 // we've broken lines and we have to invalidate mPrevFrame. 210 nsBlockInFlowLineIterator mLineIterator; 211 nsIFrame* mPrevFrame; 212 nsIFrame* mNextLineStart; 213 214 nsLineList::iterator GetLine() { return mLineIterator.GetLine(); } 215 216 static bool IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter, 217 nsIFrame* aPrevFrame, nsIFrame* aFrame) { 218 MOZ_ASSERT(!aPrevFrame || aLineIter->GetLine()->Contains(aPrevFrame), 219 "aPrevFrame must be in aLineIter's current line"); 220 nsIFrame* endFrame = aLineIter->IsLastLineInList() 221 ? nullptr 222 : aLineIter->GetLine().next()->mFirstChild; 223 nsIFrame* startFrame = 224 aPrevFrame ? aPrevFrame : aLineIter->GetLine()->mFirstChild; 225 for (nsIFrame* frame = startFrame; frame && frame != endFrame; 226 frame = frame->GetNextSibling()) { 227 if (frame == aFrame) { 228 return true; 229 } 230 } 231 return false; 232 } 233 234 static nsIFrame* FirstChildOfNextLine( 235 nsBlockInFlowLineIterator& aIterator) { 236 const nsLineList::iterator line = aIterator.GetLine(); 237 const nsLineList::iterator lineEnd = aIterator.End(); 238 MOZ_ASSERT(line != lineEnd, "iterator should start off valid"); 239 const nsLineList::iterator nextLine = line.next(); 240 241 return nextLine != lineEnd ? nextLine->mFirstChild : nullptr; 242 } 243 244 // Advance line iterator to the line containing aFrame, assuming 245 // that aFrame is already in the line list our iterator is iterating 246 // over. 247 void AdvanceToFrame(nsIFrame* aFrame) { 248 if (mPrevFrame && FirstChildOfNextLine(mLineIterator) != mNextLineStart) { 249 // Something has caused a line to split. We need to invalidate 250 // mPrevFrame since it may now be in a *later* line, though it may 251 // still be in this line, so we need to start searching for it from 252 // the start of this line. 253 mPrevFrame = nullptr; 254 } 255 nsIFrame* child = aFrame; 256 nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(child); 257 while (parent && !parent->IsBlockFrameOrSubclass()) { 258 child = parent; 259 parent = nsLayoutUtils::GetParentOrPlaceholderFor(child); 260 } 261 MOZ_ASSERT(parent, "aFrame is not a descendent of a block frame"); 262 while (!IsFrameInCurrentLine(&mLineIterator, mPrevFrame, child)) { 263 #ifdef DEBUG 264 bool hasNext = 265 #endif 266 mLineIterator.Next(); 267 MOZ_ASSERT(hasNext, "Can't find frame in lines!"); 268 mPrevFrame = nullptr; 269 } 270 mPrevFrame = child; 271 mNextLineStart = FirstChildOfNextLine(mLineIterator); 272 } 273 274 // Advance line iterator to the line containing aFrame, which may 275 // require moving forward into overflow lines or into a later 276 // continuation (or both). 277 void AdvanceToLinesAndFrame(const FrameInfo& aFrameInfo) { 278 if (mLineIterator.GetContainer() != aFrameInfo.mBlockContainer || 279 mLineIterator.GetInOverflow() != aFrameInfo.mInOverflow) { 280 MOZ_ASSERT( 281 mLineIterator.GetContainer() == aFrameInfo.mBlockContainer 282 ? (!mLineIterator.GetInOverflow() && aFrameInfo.mInOverflow) 283 : (!mLineIterator.GetContainer() || 284 AreContinuationsInOrder(mLineIterator.GetContainer(), 285 aFrameInfo.mBlockContainer)), 286 "must move forwards"); 287 nsBlockFrame* block = aFrameInfo.mBlockContainer; 288 nsLineList::iterator lines = 289 aFrameInfo.mInOverflow ? block->GetOverflowLines()->mLines.begin() 290 : block->LinesBegin(); 291 mLineIterator = 292 nsBlockInFlowLineIterator(block, lines, aFrameInfo.mInOverflow); 293 mPrevFrame = nullptr; 294 } 295 AdvanceToFrame(aFrameInfo.mFrame); 296 } 297 }; 298 299 FastLineIterator mCurrentTraverseLine, mCurrentResolveLine; 300 301 #ifdef DEBUG 302 // Only used for NOISY debug output. 303 // Matches the current TraverseFrames state, not the ResolveParagraph 304 // state. 305 nsBlockFrame* mCurrentBlock; 306 #endif 307 308 explicit BidiParagraphData(nsBlockFrame* aBlockFrame) 309 : mPresContext(aBlockFrame->PresContext()), 310 mIsVisual(mPresContext->IsVisualMode()), 311 mRequiresBidi(false), 312 mParaLevel(nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame->Style())), 313 mPrevContent(nullptr) 314 #ifdef DEBUG 315 , 316 mCurrentBlock(aBlockFrame) 317 #endif 318 { 319 if (mParaLevel > 0) { 320 mRequiresBidi = true; 321 } 322 323 if (mIsVisual) { 324 /** 325 * Drill up in content to detect whether this is an element that needs to 326 * be rendered with logical order even on visual pages. 327 * 328 * We always use logical order on form controls, firstly so that text 329 * entry will be in logical order, but also because visual pages were 330 * written with the assumption that even if the browser had no support 331 * for right-to-left text rendering, it would use native widgets with 332 * bidi support to display form controls. 333 * 334 * We also use logical order in XUL elements, since we expect that if a 335 * XUL element appears in a visual page, it will be generated by an XBL 336 * binding and contain localized text which will be in logical order. 337 */ 338 for (nsIContent* content = aBlockFrame->GetContent(); content; 339 content = content->GetParent()) { 340 if (content->IsXULElement() || content->IsHTMLFormControlElement()) { 341 mIsVisual = false; 342 break; 343 } 344 } 345 } 346 } 347 348 nsresult SetPara() { 349 if (mPresContext->BidiEngine().SetParagraph(mBuffer, mParaLevel).isErr()) { 350 return NS_ERROR_FAILURE; 351 }; 352 return NS_OK; 353 } 354 355 /** 356 * mParaLevel can be BidiDirection::LTR as well as 357 * BidiDirection::LTR or BidiDirection::RTL. 358 * GetParagraphEmbeddingLevel() returns the actual (resolved) paragraph level 359 * which is always either BidiDirection::LTR or 360 * BidiDirection::RTL 361 */ 362 BidiEmbeddingLevel GetParagraphEmbeddingLevel() { 363 BidiEmbeddingLevel paraLevel = mParaLevel; 364 if (paraLevel == BidiEmbeddingLevel::DefaultLTR() || 365 paraLevel == BidiEmbeddingLevel::DefaultRTL()) { 366 paraLevel = mPresContext->BidiEngine().GetParagraphEmbeddingLevel(); 367 } 368 return paraLevel; 369 } 370 371 BidiEngine::ParagraphDirection GetParagraphDirection() { 372 return mPresContext->BidiEngine().GetParagraphDirection(); 373 } 374 375 nsresult CountRuns(int32_t* runCount) { 376 auto result = mPresContext->BidiEngine().CountRuns(); 377 if (result.isErr()) { 378 return NS_ERROR_FAILURE; 379 } 380 *runCount = result.unwrap(); 381 return NS_OK; 382 } 383 384 void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit, 385 BidiEmbeddingLevel* aLevel) { 386 mPresContext->BidiEngine().GetLogicalRun(aLogicalStart, aLogicalLimit, 387 aLevel); 388 if (mIsVisual) { 389 *aLevel = GetParagraphEmbeddingLevel(); 390 } 391 } 392 393 void ResetData() { 394 mLogicalFrames.Clear(); 395 mContentToFrameIndex.Clear(); 396 mBuffer.SetLength(0); 397 mPrevContent = nullptr; 398 for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) { 399 mBuffer.Append(mEmbeddingStack[i]); 400 mLogicalFrames.AppendElement(FrameInfo(BidiControlFrameType::Value)); 401 } 402 } 403 404 void AppendFrame(nsIFrame* aFrame, FastLineIterator& aLineIter, 405 nsIContent* aContent = nullptr) { 406 if (aContent) { 407 mContentToFrameIndex.InsertOrUpdate(aContent, FrameCount()); 408 } 409 410 // We don't actually need to advance aLineIter to aFrame, since all we use 411 // from it is the block and is-overflow state, which are correct already. 412 mLogicalFrames.AppendElement(FrameInfo(aFrame, aLineIter.mLineIterator)); 413 } 414 415 void AdvanceAndAppendFrame(nsIFrame** aFrame, FastLineIterator& aLineIter, 416 nsIFrame** aNextSibling) { 417 nsIFrame* frame = *aFrame; 418 nsIFrame* nextSibling = *aNextSibling; 419 420 frame = frame->GetNextContinuation(); 421 if (frame) { 422 AppendFrame(frame, aLineIter, nullptr); 423 424 /* 425 * If we have already overshot the saved next-sibling while 426 * scanning the frame's continuations, advance it. 427 */ 428 if (frame == nextSibling) { 429 nextSibling = frame->GetNextSibling(); 430 } 431 } 432 433 *aFrame = frame; 434 *aNextSibling = nextSibling; 435 } 436 437 int32_t GetLastFrameForContent(nsIContent* aContent) { 438 return mContentToFrameIndex.Get(aContent); 439 } 440 441 int32_t FrameCount() { return mLogicalFrames.Length(); } 442 443 int32_t BufferLength() { return mBuffer.Length(); } 444 445 nsIFrame* FrameAt(int32_t aIndex) { return mLogicalFrames[aIndex].mFrame; } 446 447 const FrameInfo& FrameInfoAt(int32_t aIndex) { 448 return mLogicalFrames[aIndex]; 449 } 450 451 void AppendUnichar(char16_t aCh) { mBuffer.Append(aCh); } 452 453 void AppendString(const nsDependentSubstring& aString) { 454 mBuffer.Append(aString); 455 } 456 457 void AppendControlChar(char16_t aCh) { 458 mLogicalFrames.AppendElement(FrameInfo(BidiControlFrameType::Value)); 459 AppendUnichar(aCh); 460 } 461 462 void PushBidiControl(char16_t aCh) { 463 AppendControlChar(aCh); 464 mEmbeddingStack.AppendElement(aCh); 465 } 466 467 void AppendPopChar(char16_t aCh) { 468 AppendControlChar(IsIsolateControl(aCh) ? kPDI : kPDF); 469 } 470 471 void PopBidiControl(char16_t aCh) { 472 MOZ_ASSERT(mEmbeddingStack.Length(), "embedding/override underflow"); 473 MOZ_ASSERT(aCh == mEmbeddingStack.LastElement()); 474 AppendPopChar(aCh); 475 mEmbeddingStack.RemoveLastElement(); 476 } 477 478 void ClearBidiControls() { 479 for (char16_t c : Reversed(mEmbeddingStack)) { 480 AppendPopChar(c); 481 } 482 } 483 }; 484 485 class MOZ_STACK_CLASS BidiLineData { 486 public: 487 BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) { 488 // Initialize the logically-ordered array of frames using the top-level 489 // frames of a single line 490 auto appendFrame = [&](nsIFrame* frame, BidiEmbeddingLevel level) { 491 mLogicalFrames.AppendElement(frame); 492 mLevels.AppendElement(level); 493 mIndexMap.AppendElement(0); 494 }; 495 496 for (nsIFrame* frame = aFirstFrameOnLine; frame && aNumFramesOnLine--; 497 frame = frame->GetNextSibling()) { 498 FrameBidiData bidiData = nsBidiPresUtils::GetFrameBidiData(frame); 499 if (bidiData.precedingControl != kBidiLevelNone) { 500 appendFrame(NS_BIDI_CONTROL_FRAME, bidiData.precedingControl); 501 } 502 appendFrame(frame, bidiData.embeddingLevel); 503 } 504 505 // Reorder the line 506 BidiEngine::ReorderVisual(mLevels.Elements(), mLevels.Length(), 507 mIndexMap.Elements()); 508 509 // Collect the frames in visual order, omitting virtual controls 510 // and noting whether frames are reordered. 511 for (uint32_t i = 0; i < mIndexMap.Length(); i++) { 512 nsIFrame* frame = mLogicalFrames[mIndexMap[i]]; 513 if (frame == NS_BIDI_CONTROL_FRAME) { 514 continue; 515 } 516 mVisualFrameIndex.AppendElement(mIndexMap[i]); 517 if (int32_t(i) != mIndexMap[i]) { 518 mIsReordered = true; 519 } 520 } 521 } 522 523 uint32_t LogicalFrameCount() const { return mLogicalFrames.Length(); } 524 uint32_t VisualFrameCount() const { return mVisualFrameIndex.Length(); } 525 526 nsIFrame* LogicalFrameAt(uint32_t aIndex) const { 527 return mLogicalFrames[aIndex]; 528 } 529 530 nsIFrame* VisualFrameAt(uint32_t aIndex) const { 531 return mLogicalFrames[mVisualFrameIndex[aIndex]]; 532 } 533 534 std::pair<nsIFrame*, BidiEmbeddingLevel> VisualFrameAndLevelAt( 535 uint32_t aIndex) const { 536 int32_t index = mVisualFrameIndex[aIndex]; 537 return std::pair(mLogicalFrames[index], mLevels[index]); 538 } 539 540 bool IsReordered() const { return mIsReordered; } 541 542 void InitContinuationStates(nsContinuationStates* aContinuationStates) const { 543 for (auto* frame : mLogicalFrames) { 544 if (frame != NS_BIDI_CONTROL_FRAME) { 545 nsBidiPresUtils::InitContinuationStates(frame, aContinuationStates); 546 } 547 } 548 } 549 550 private: 551 AutoTArray<nsIFrame*, 16> mLogicalFrames; 552 AutoTArray<int32_t, 16> mVisualFrameIndex; 553 AutoTArray<int32_t, 16> mIndexMap; 554 AutoTArray<BidiEmbeddingLevel, 16> mLevels; 555 bool mIsReordered = false; 556 }; 557 558 #ifdef DEBUG 559 extern "C" { 560 void MOZ_EXPORT DumpBidiLine(BidiLineData* aData, bool aVisualOrder) { 561 auto dump = [](nsIFrame* frame) { 562 if (frame == NS_BIDI_CONTROL_FRAME) { 563 fprintf_stderr(stderr, "(Bidi control frame)\n"); 564 } else { 565 frame->List(); 566 } 567 }; 568 569 if (aVisualOrder) { 570 for (uint32_t i = 0; i < aData->VisualFrameCount(); i++) { 571 dump(aData->VisualFrameAt(i)); 572 } 573 } else { 574 for (uint32_t i = 0; i < aData->LogicalFrameCount(); i++) { 575 dump(aData->LogicalFrameAt(i)); 576 } 577 } 578 } 579 } 580 #endif 581 582 /* Some helper methods for Resolve() */ 583 584 // Should this frame be split between text runs? 585 static bool IsBidiSplittable(nsIFrame* aFrame) { 586 MOZ_ASSERT(aFrame); 587 // Bidi inline containers should be split, unless they're line frames. 588 LayoutFrameType frameType = aFrame->Type(); 589 return (aFrame->IsBidiInlineContainer() && 590 frameType != LayoutFrameType::Line) || 591 frameType == LayoutFrameType::Text; 592 } 593 594 // Should this frame be treated as a leaf (e.g. when building mLogicalFrames)? 595 static bool IsBidiLeaf(const nsIFrame* aFrame) { 596 nsIFrame* kid = aFrame->PrincipalChildList().FirstChild(); 597 if (kid) { 598 if (aFrame->IsBidiInlineContainer() || 599 RubyUtils::IsRubyBox(aFrame->Type())) { 600 return false; 601 } 602 } 603 return true; 604 } 605 606 /** 607 * Create non-fluid continuations for the ancestors of a given frame all the way 608 * up the frame tree until we hit a non-splittable frame (a line or a block). 609 * 610 * @param aParent the first parent frame to be split 611 * @param aFrame the child frames after this frame are reparented to the 612 * newly-created continuation of aParent. 613 * If aFrame is null, all the children of aParent are reparented. 614 */ 615 static void SplitInlineAncestors(nsContainerFrame* aParent, 616 nsLineList::iterator aLine, nsIFrame* aFrame) { 617 PresShell* presShell = aParent->PresShell(); 618 nsIFrame* frame = aFrame; 619 nsContainerFrame* parent = aParent; 620 nsContainerFrame* newParent; 621 622 while (IsBidiSplittable(parent)) { 623 nsContainerFrame* grandparent = parent->GetParent(); 624 NS_ASSERTION(grandparent, 625 "Couldn't get parent's parent in " 626 "nsBidiPresUtils::SplitInlineAncestors"); 627 628 // Split the child list after |frame|, unless it is the last child. 629 if (!frame || frame->GetNextSibling()) { 630 newParent = static_cast<nsContainerFrame*>( 631 presShell->FrameConstructor()->CreateContinuingFrame( 632 parent, grandparent, false)); 633 634 nsFrameList tail = parent->StealFramesAfter(frame); 635 636 // The parent's continuation adopts the siblings after the split. 637 MOZ_ASSERT(!newParent->IsBlockFrameOrSubclass(), 638 "blocks should not be IsBidiSplittable"); 639 newParent->InsertFrames(FrameChildListID::NoReflowPrincipal, nullptr, 640 nullptr, std::move(tail)); 641 642 // While passing &aLine to InsertFrames for a non-block isn't harmful 643 // because it's a no-op, it doesn't really make sense. However, the 644 // MOZ_ASSERT() we need to guarantee that it's safe only works if the 645 // parent is actually the block. 646 const nsLineList::iterator* parentLine; 647 if (grandparent->IsBlockFrameOrSubclass()) { 648 MOZ_ASSERT(aLine->Contains(parent)); 649 parentLine = &aLine; 650 } else { 651 parentLine = nullptr; 652 } 653 654 // The list name FrameChildListID::NoReflowPrincipal would indicate we 655 // don't want reflow 656 grandparent->InsertFrames(FrameChildListID::NoReflowPrincipal, parent, 657 parentLine, nsFrameList(newParent, newParent)); 658 } 659 660 frame = parent; 661 parent = grandparent; 662 } 663 } 664 665 static void MakeContinuationFluid(nsIFrame* aFrame, nsIFrame* aNext) { 666 NS_ASSERTION(!aFrame->GetNextInFlow() || aFrame->GetNextInFlow() == aNext, 667 "next-in-flow is not next continuation!"); 668 aFrame->SetNextInFlow(aNext); 669 670 NS_ASSERTION(!aNext->GetPrevInFlow() || aNext->GetPrevInFlow() == aFrame, 671 "prev-in-flow is not prev continuation!"); 672 aNext->SetPrevInFlow(aFrame); 673 } 674 675 static void MakeContinuationsNonFluidUpParentChain(nsIFrame* aFrame, 676 nsIFrame* aNext) { 677 nsIFrame* frame; 678 nsIFrame* next; 679 680 for (frame = aFrame, next = aNext; 681 frame && next && next != frame && next == frame->GetNextInFlow() && 682 IsBidiSplittable(frame); 683 frame = frame->GetParent(), next = next->GetParent()) { 684 frame->SetNextContinuation(next); 685 next->SetPrevContinuation(frame); 686 } 687 } 688 689 // If aFrame is the last child of its parent, convert bidi continuations to 690 // fluid continuations for all of its inline ancestors. 691 // If it isn't the last child, make sure that its continuation is fluid. 692 static void JoinInlineAncestors(nsIFrame* aFrame) { 693 nsIFrame* frame = aFrame; 694 while (frame && IsBidiSplittable(frame)) { 695 nsIFrame* next = frame->GetNextContinuation(); 696 if (next) { 697 MakeContinuationFluid(frame, next); 698 } 699 // Join the parent only as long as we're its last child. 700 if (frame->GetNextSibling()) { 701 break; 702 } 703 frame = frame->GetParent(); 704 } 705 } 706 707 static void CreateContinuation(nsIFrame* aFrame, 708 const nsLineList::iterator aLine, 709 nsIFrame** aNewFrame, bool aIsFluid) { 710 MOZ_ASSERT(aNewFrame, "null OUT ptr"); 711 MOZ_ASSERT(aFrame, "null ptr"); 712 713 *aNewFrame = nullptr; 714 715 nsPresContext* presContext = aFrame->PresContext(); 716 PresShell* presShell = presContext->PresShell(); 717 NS_ASSERTION(presShell, 718 "PresShell must be set on PresContext before calling " 719 "nsBidiPresUtils::CreateContinuation"); 720 721 nsContainerFrame* parent = aFrame->GetParent(); 722 NS_ASSERTION( 723 parent, 724 "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation"); 725 726 // While passing &aLine to InsertFrames for a non-block isn't harmful 727 // because it's a no-op, it doesn't really make sense. However, the 728 // MOZ_ASSERT() we need to guarantee that it's safe only works if the 729 // parent is actually the block. 730 const nsLineList::iterator* parentLine; 731 if (parent->IsBlockFrameOrSubclass()) { 732 MOZ_ASSERT(aLine->Contains(aFrame)); 733 parentLine = &aLine; 734 } else { 735 parentLine = nullptr; 736 } 737 738 // Have to special case floating first letter frames because the continuation 739 // doesn't go in the first letter frame. The continuation goes with the rest 740 // of the text that the first letter frame was made out of. 741 if (parent->IsLetterFrame() && parent->IsFloating()) { 742 nsFirstLetterFrame* letterFrame = do_QueryFrame(parent); 743 letterFrame->CreateContinuationForFloatingParent(aFrame, aNewFrame, 744 aIsFluid); 745 return; 746 } 747 748 *aNewFrame = presShell->FrameConstructor()->CreateContinuingFrame( 749 aFrame, parent, aIsFluid); 750 751 // The list name FrameChildListID::NoReflowPrincipal would indicate we don't 752 // want reflow 753 // XXXbz this needs higher-level framelist love 754 parent->InsertFrames(FrameChildListID::NoReflowPrincipal, aFrame, parentLine, 755 nsFrameList(*aNewFrame, *aNewFrame)); 756 757 if (!aIsFluid) { 758 // Split inline ancestor frames 759 SplitInlineAncestors(parent, aLine, aFrame); 760 } 761 } 762 763 /* 764 * Overview of the implementation of Resolve(): 765 * 766 * Walk through the descendants of aBlockFrame and build: 767 * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order 768 * * mBuffer: an nsString containing a representation of 769 * the content of the frames. 770 * In the case of text frames, this is the actual text context of the 771 * frames, but some other elements are represented in a symbolic form which 772 * will make the Unicode Bidi Algorithm give the correct results. 773 * Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo> 774 * elements are represented by the corresponding Unicode control characters. 775 * <br> elements are represented by U+2028 LINE SEPARATOR 776 * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT 777 * CHARACTER 778 * 779 * Then pass mBuffer to the Bidi engine for resolving of embedding levels 780 * by nsBidi::SetPara() and division into directional runs by 781 * nsBidi::CountRuns(). 782 * 783 * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and 784 * correlate them with the frames indexed in mLogicalFrames, setting the 785 * baseLevel and embeddingLevel properties according to the results returned 786 * by the Bidi engine. 787 * 788 * The rendering layer requires each text frame to contain text in only one 789 * direction, so we may need to call EnsureBidiContinuation() to split frames. 790 * We may also need to call RemoveBidiContinuation() to convert frames created 791 * by EnsureBidiContinuation() in previous reflows into fluid continuations. 792 */ 793 nsresult nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame) { 794 BidiParagraphData bpd(aBlockFrame); 795 796 // Handle bidi-override being set on the block itself before calling 797 // TraverseFrames. 798 // No need to call GetBidiControl as well, because isolate and embed 799 // values of unicode-bidi property are redundant on block elements. 800 // unicode-bidi:plaintext on a block element is handled by block frame 801 // via using nsIFrame::GetWritingMode(nsIFrame*). 802 char16_t ch = GetBidiOverride(aBlockFrame->Style()); 803 if (ch != 0) { 804 bpd.PushBidiControl(ch); 805 bpd.mRequiresBidi = true; 806 } else { 807 // If there are no unicode-bidi properties and no RTL characters in the 808 // block's content, then it is pure LTR and we can skip the rest of bidi 809 // resolution. 810 nsIContent* currContent = nullptr; 811 for (nsBlockFrame* block = aBlockFrame; block; 812 block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) { 813 block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); 814 if (!bpd.mRequiresBidi && 815 ChildListMayRequireBidi(block->PrincipalChildList().FirstChild(), 816 &currContent)) { 817 bpd.mRequiresBidi = true; 818 } 819 if (!bpd.mRequiresBidi) { 820 nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines(); 821 if (overflowLines) { 822 if (ChildListMayRequireBidi(overflowLines->mFrames.FirstChild(), 823 &currContent)) { 824 bpd.mRequiresBidi = true; 825 } 826 } 827 } 828 } 829 if (!bpd.mRequiresBidi) { 830 return NS_OK; 831 } 832 } 833 834 for (nsBlockFrame* block = aBlockFrame; block; 835 block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) { 836 #ifdef DEBUG 837 bpd.mCurrentBlock = block; 838 #endif 839 block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); 840 bpd.mCurrentTraverseLine.mLineIterator = 841 nsBlockInFlowLineIterator(block, block->LinesBegin()); 842 bpd.mCurrentTraverseLine.mPrevFrame = nullptr; 843 TraverseFrames(block->PrincipalChildList().FirstChild(), &bpd); 844 nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines(); 845 if (overflowLines) { 846 bpd.mCurrentTraverseLine.mLineIterator = 847 nsBlockInFlowLineIterator(block, overflowLines->mLines.begin(), true); 848 bpd.mCurrentTraverseLine.mPrevFrame = nullptr; 849 TraverseFrames(overflowLines->mFrames.FirstChild(), &bpd); 850 } 851 } 852 853 if (ch != 0) { 854 bpd.PopBidiControl(ch); 855 } 856 857 return ResolveParagraph(&bpd); 858 } 859 860 // In ResolveParagraph, we previously used ReplaceChar(kSeparators, kSpace) 861 // to convert separators to spaces, but this hard-coded implementation is 862 // substantially faster than the general-purpose ReplaceChar function. 863 // This must be kept in sync with the definition of kSeparators. 864 static inline void ReplaceSeparators(nsString& aText, size_t aStartIndex = 0) { 865 for (char16_t* cp = aText.BeginWriting() + aStartIndex; 866 cp < aText.EndWriting(); cp++) { 867 if (MOZ_UNLIKELY(*cp < char16_t(' '))) { 868 static constexpr char16_t SeparatorToSpace[32] = { 869 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ' ', ' ', 870 ' ', 0x0c, ' ', 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 871 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, ' ', ' ', ' ', ' ', 872 }; 873 *cp = SeparatorToSpace[*cp]; 874 } else if (MOZ_UNLIKELY(*cp == kNextLine || *cp == kParagraphSeparator)) { 875 *cp = ' '; 876 } 877 } 878 } 879 880 nsresult nsBidiPresUtils::ResolveParagraph(BidiParagraphData* aBpd) { 881 if (aBpd->BufferLength() < 1) { 882 return NS_OK; 883 } 884 885 ReplaceSeparators(aBpd->mBuffer); 886 887 int32_t runCount; 888 889 nsresult rv = aBpd->SetPara(); 890 NS_ENSURE_SUCCESS(rv, rv); 891 892 BidiEmbeddingLevel embeddingLevel = aBpd->GetParagraphEmbeddingLevel(); 893 894 rv = aBpd->CountRuns(&runCount); 895 NS_ENSURE_SUCCESS(rv, rv); 896 897 int32_t runLength = 0; // the length of the current run of text 898 int32_t logicalLimit = 0; // the end of the current run + 1 899 int32_t numRun = -1; 900 int32_t fragmentLength = 0; // the length of the current text frame 901 int32_t frameIndex = -1; // index to the frames in mLogicalFrames 902 int32_t frameCount = aBpd->FrameCount(); 903 int32_t contentOffset = 0; // offset of current frame in its content node 904 bool isTextFrame = false; 905 nsIFrame* frame = nullptr; 906 BidiParagraphData::FrameInfo frameInfo; 907 nsIContent* content = nullptr; 908 int32_t contentTextLength = 0; 909 910 #ifdef DEBUG 911 # ifdef NOISY_BIDI 912 printf( 913 "Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, " 914 "runCount=%d\n", 915 (void*)aBpd->mCurrentBlock, NS_ConvertUTF16toUTF8(aBpd->mBuffer).get(), 916 frameCount, runCount); 917 # ifdef REALLY_NOISY_BIDI 918 printf(" block frame tree=:\n"); 919 aBpd->mCurrentBlock->List(stdout); 920 # endif 921 # endif 922 #endif 923 924 if (runCount == 1 && frameCount == 1 && 925 aBpd->GetParagraphDirection() == BidiEngine::ParagraphDirection::LTR && 926 aBpd->GetParagraphEmbeddingLevel() == 0) { 927 // We have a single left-to-right frame in a left-to-right paragraph, 928 // without bidi isolation from the surrounding text. 929 // Make sure that the embedding level and base level frame properties aren't 930 // set (because if they are this frame used to have some other direction, 931 // so we can't do this optimization), and we're done. 932 nsIFrame* frame = aBpd->FrameAt(0); 933 if (frame != NS_BIDI_CONTROL_FRAME) { 934 FrameBidiData bidiData = frame->GetBidiData(); 935 if (!bidiData.embeddingLevel && !bidiData.baseLevel) { 936 #ifdef DEBUG 937 # ifdef NOISY_BIDI 938 printf("early return for single direction frame %p\n", (void*)frame); 939 # endif 940 #endif 941 frame->AddStateBits(NS_FRAME_IS_BIDI); 942 return NS_OK; 943 } 944 } 945 } 946 947 BidiParagraphData::FrameInfo lastRealFrame; 948 BidiEmbeddingLevel lastEmbeddingLevel = kBidiLevelNone; 949 BidiEmbeddingLevel precedingControl = kBidiLevelNone; 950 951 auto storeBidiDataToFrame = [&]() { 952 FrameBidiData bidiData; 953 bidiData.embeddingLevel = embeddingLevel; 954 bidiData.baseLevel = aBpd->GetParagraphEmbeddingLevel(); 955 // If a control character doesn't have a lower embedding level than 956 // both the preceding and the following frame, it isn't something 957 // needed for getting the correct result. This optimization should 958 // remove almost all of embeds and overrides, and some of isolates. 959 if (precedingControl >= embeddingLevel || 960 precedingControl >= lastEmbeddingLevel) { 961 bidiData.precedingControl = kBidiLevelNone; 962 } else { 963 bidiData.precedingControl = precedingControl; 964 } 965 precedingControl = kBidiLevelNone; 966 lastEmbeddingLevel = embeddingLevel; 967 frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData); 968 }; 969 970 for (;;) { 971 if (fragmentLength <= 0) { 972 // Get the next frame from mLogicalFrames 973 if (++frameIndex >= frameCount) { 974 break; 975 } 976 frameInfo = aBpd->FrameInfoAt(frameIndex); 977 frame = frameInfo.mFrame; 978 if (frame == NS_BIDI_CONTROL_FRAME || !frame->IsTextFrame()) { 979 /* 980 * Any non-text frame corresponds to a single character in the text 981 * buffer (a bidi control character, LINE SEPARATOR, or OBJECT 982 * SUBSTITUTE) 983 */ 984 isTextFrame = false; 985 fragmentLength = 1; 986 } else { 987 aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(frameInfo); 988 content = frame->GetContent(); 989 if (!content) { 990 rv = NS_OK; 991 break; 992 } 993 contentTextLength = content->TextLength(); 994 auto [start, end] = frame->GetOffsets(); 995 NS_ASSERTION(!(contentTextLength < end - start), 996 "Frame offsets don't fit in content"); 997 fragmentLength = std::min(contentTextLength, end - start); 998 contentOffset = start; 999 isTextFrame = true; 1000 } 1001 } // if (fragmentLength <= 0) 1002 1003 if (runLength <= 0) { 1004 // Get the next run of text from the Bidi engine 1005 if (++numRun >= runCount) { 1006 // We've run out of runs of text; but don't forget to store bidi data 1007 // to the frame before breaking out of the loop (bug 1426042). 1008 if (frame != NS_BIDI_CONTROL_FRAME) { 1009 storeBidiDataToFrame(); 1010 if (isTextFrame) { 1011 frame->AdjustOffsetsForBidi(contentOffset, 1012 contentOffset + fragmentLength); 1013 } 1014 } 1015 break; 1016 } 1017 int32_t lineOffset = logicalLimit; 1018 aBpd->GetLogicalRun(lineOffset, &logicalLimit, &embeddingLevel); 1019 runLength = logicalLimit - lineOffset; 1020 } // if (runLength <= 0) 1021 1022 if (frame == NS_BIDI_CONTROL_FRAME) { 1023 // In theory, we only need to do this for isolates. However, it is 1024 // easier to do this for all here because we do not maintain the 1025 // index to get corresponding character from buffer. Since we do 1026 // have proper embedding level for all those characters, including 1027 // them wouldn't affect the final result. 1028 precedingControl = std::min(precedingControl, embeddingLevel); 1029 } else { 1030 storeBidiDataToFrame(); 1031 if (isTextFrame) { 1032 if (contentTextLength == 0) { 1033 // Set the base level and embedding level of the current run even 1034 // on an empty frame. Otherwise frame reordering will not be correct. 1035 frame->AdjustOffsetsForBidi(0, 0); 1036 // Nothing more to do for an empty frame, except update 1037 // lastRealFrame like we do below. 1038 lastRealFrame = frameInfo; 1039 continue; 1040 } 1041 nsLineList::iterator currentLine = aBpd->mCurrentResolveLine.GetLine(); 1042 if ((runLength > 0) && (runLength < fragmentLength)) { 1043 /* 1044 * The text in this frame continues beyond the end of this directional 1045 * run. Create a non-fluid continuation frame for the next directional 1046 * run. 1047 */ 1048 currentLine->MarkDirty(); 1049 nsIFrame* nextBidi; 1050 int32_t runEnd = contentOffset + runLength; 1051 EnsureBidiContinuation(frame, currentLine, &nextBidi, contentOffset, 1052 runEnd); 1053 nextBidi->AdjustOffsetsForBidi(runEnd, 1054 contentOffset + fragmentLength); 1055 frame = nextBidi; 1056 frameInfo.mFrame = frame; 1057 contentOffset = runEnd; 1058 1059 aBpd->mCurrentResolveLine.AdvanceToFrame(frame); 1060 } // if (runLength < fragmentLength) 1061 else { 1062 if (contentOffset + fragmentLength == contentTextLength) { 1063 /* 1064 * We have finished all the text in this content node. Convert any 1065 * further non-fluid continuations to fluid continuations and 1066 * advance frameIndex to the last frame in the content node 1067 */ 1068 int32_t newIndex = aBpd->GetLastFrameForContent(content); 1069 if (newIndex > frameIndex) { 1070 currentLine->MarkDirty(); 1071 RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex); 1072 frameIndex = newIndex; 1073 frameInfo = aBpd->FrameInfoAt(frameIndex); 1074 frame = frameInfo.mFrame; 1075 } 1076 } else if (fragmentLength > 0 && runLength > fragmentLength) { 1077 /* 1078 * There is more text that belongs to this directional run in the 1079 * next text frame: make sure it is a fluid continuation of the 1080 * current frame. Do not advance frameIndex, because the next frame 1081 * may contain multi-directional text and need to be split 1082 */ 1083 int32_t newIndex = frameIndex; 1084 do { 1085 } while (++newIndex < frameCount && 1086 aBpd->FrameAt(newIndex) == NS_BIDI_CONTROL_FRAME); 1087 if (newIndex < frameCount) { 1088 currentLine->MarkDirty(); 1089 RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex); 1090 } 1091 } else if (runLength == fragmentLength) { 1092 /* 1093 * If the directional run ends at the end of the frame, make sure 1094 * that any continuation is non-fluid, and do the same up the 1095 * parent chain 1096 */ 1097 nsIFrame* next = frame->GetNextInFlow(); 1098 if (next) { 1099 currentLine->MarkDirty(); 1100 MakeContinuationsNonFluidUpParentChain(frame, next); 1101 } 1102 } 1103 frame->AdjustOffsetsForBidi(contentOffset, 1104 contentOffset + fragmentLength); 1105 } 1106 } // isTextFrame 1107 } // not bidi control frame 1108 int32_t temp = runLength; 1109 runLength -= fragmentLength; 1110 fragmentLength -= temp; 1111 1112 // Record last real frame so that we can do splitting properly even 1113 // if a run ends after a virtual bidi control frame. 1114 if (frame != NS_BIDI_CONTROL_FRAME) { 1115 lastRealFrame = frameInfo; 1116 } 1117 if (lastRealFrame.mFrame && fragmentLength <= 0) { 1118 // If the frame is at the end of a run, and this is not the end of our 1119 // paragraph, split all ancestor inlines that need splitting. 1120 // To determine whether we're at the end of the run, we check that we've 1121 // finished processing the current run, and that the current frame 1122 // doesn't have a fluid continuation (it could have a fluid continuation 1123 // of zero length, so testing runLength alone is not sufficient). 1124 if (runLength <= 0 && !lastRealFrame.mFrame->GetNextInFlow()) { 1125 if (numRun + 1 < runCount) { 1126 nsIFrame* child = lastRealFrame.mFrame; 1127 nsContainerFrame* parent = child->GetParent(); 1128 // As long as we're on the last sibling, the parent doesn't have to 1129 // be split. 1130 // However, if the parent has a fluid continuation, we do have to make 1131 // it non-fluid. This can happen e.g. when we have a first-letter 1132 // frame and the end of the first-letter coincides with the end of a 1133 // directional run. 1134 while (parent && IsBidiSplittable(parent) && 1135 !child->GetNextSibling()) { 1136 nsIFrame* next = parent->GetNextInFlow(); 1137 if (next) { 1138 parent->SetNextContinuation(next); 1139 next->SetPrevContinuation(parent); 1140 } 1141 child = parent; 1142 parent = child->GetParent(); 1143 } 1144 if (parent && IsBidiSplittable(parent)) { 1145 aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame); 1146 SplitInlineAncestors(parent, aBpd->mCurrentResolveLine.GetLine(), 1147 child); 1148 1149 aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame); 1150 } 1151 } 1152 } else if (frame != NS_BIDI_CONTROL_FRAME) { 1153 // We're not at an end of a run. If |frame| is the last child of its 1154 // parent, and its ancestors happen to have bidi continuations, convert 1155 // them into fluid continuations. 1156 JoinInlineAncestors(frame); 1157 } 1158 } 1159 } // for 1160 1161 #ifdef DEBUG 1162 # ifdef REALLY_NOISY_BIDI 1163 printf("---\nAfter Resolve(), frameTree =:\n"); 1164 aBpd->mCurrentBlock->List(stdout); 1165 printf("===\n"); 1166 # endif 1167 #endif 1168 1169 return rv; 1170 } 1171 1172 void nsBidiPresUtils::TraverseFrames(nsIFrame* aCurrentFrame, 1173 BidiParagraphData* aBpd) { 1174 if (!aCurrentFrame) { 1175 return; 1176 } 1177 1178 #ifdef DEBUG 1179 nsBlockFrame* initialLineContainer = 1180 aBpd->mCurrentTraverseLine.mLineIterator.GetContainer(); 1181 #endif 1182 1183 nsIFrame* childFrame = aCurrentFrame; 1184 do { 1185 /* 1186 * It's important to get the next sibling and next continuation *before* 1187 * handling the frame: If we encounter a forced paragraph break and call 1188 * ResolveParagraph within this loop, doing GetNextSibling and 1189 * GetNextContinuation after that could return a bidi continuation that had 1190 * just been split from the original childFrame and we would process it 1191 * twice. 1192 */ 1193 nsIFrame* nextSibling = childFrame->GetNextSibling(); 1194 1195 // If the real frame for a placeholder is a first letter frame, we need to 1196 // drill down into it and include its contents in Bidi resolution. 1197 // If not, we just use the placeholder. 1198 nsIFrame* frame = childFrame; 1199 if (childFrame->IsPlaceholderFrame()) { 1200 nsIFrame* realFrame = 1201 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame); 1202 if (realFrame->IsLetterFrame()) { 1203 frame = realFrame; 1204 } 1205 } 1206 1207 auto DifferentBidiValues = [](ComputedStyle* aSC1, nsIFrame* aFrame2) { 1208 ComputedStyle* sc2 = aFrame2->Style(); 1209 return GetBidiControl(aSC1) != GetBidiControl(sc2) || 1210 GetBidiOverride(aSC1) != GetBidiOverride(sc2); 1211 }; 1212 1213 ComputedStyle* sc = frame->Style(); 1214 nsIFrame* nextContinuation = frame->GetNextContinuation(); 1215 nsIFrame* prevContinuation = frame->GetPrevContinuation(); 1216 bool isLastFrame = 1217 !nextContinuation || DifferentBidiValues(sc, nextContinuation); 1218 bool isFirstFrame = 1219 !prevContinuation || DifferentBidiValues(sc, prevContinuation); 1220 1221 char16_t controlChar = 0; 1222 char16_t overrideChar = 0; 1223 LayoutFrameType frameType = frame->Type(); 1224 if (frame->IsBidiInlineContainer() || RubyUtils::IsRubyBox(frameType)) { 1225 if (!frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 1226 nsContainerFrame* c = static_cast<nsContainerFrame*>(frame); 1227 MOZ_ASSERT(c == do_QueryFrame(frame), 1228 "eBidiInlineContainer and ruby frame must be" 1229 " a nsContainerFrame subclass"); 1230 c->DrainSelfOverflowList(); 1231 } 1232 1233 controlChar = GetBidiControl(sc); 1234 overrideChar = GetBidiOverride(sc); 1235 1236 // Add dummy frame pointers representing bidi control codes before 1237 // the first frames of elements specifying override, isolation, or 1238 // plaintext. 1239 if (isFirstFrame) { 1240 if (controlChar != 0) { 1241 aBpd->PushBidiControl(controlChar); 1242 } 1243 if (overrideChar != 0) { 1244 aBpd->PushBidiControl(overrideChar); 1245 } 1246 } 1247 } 1248 1249 if (IsBidiLeaf(frame)) { 1250 /* Bidi leaf frame: add the frame to the mLogicalFrames array, 1251 * and add its index to the mContentToFrameIndex hashtable. This 1252 * will be used in RemoveBidiContinuation() to identify the last 1253 * frame in the array with a given content. 1254 */ 1255 nsIContent* content = frame->GetContent(); 1256 aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine, content); 1257 1258 // Append the content of the frame to the paragraph buffer 1259 if (LayoutFrameType::Text == frameType) { 1260 if (content != aBpd->mPrevContent) { 1261 aBpd->mPrevContent = content; 1262 if (!frame->StyleText()->NewlineIsSignificant( 1263 static_cast<nsTextFrame*>(frame))) { 1264 content->GetAsText()->AppendTextTo(aBpd->mBuffer); 1265 } else { 1266 /* 1267 * For preformatted text we have to do bidi resolution on each line 1268 * separately. 1269 */ 1270 nsAutoString text; 1271 content->GetAsText()->AppendTextTo(text); 1272 nsIFrame* next; 1273 do { 1274 next = nullptr; 1275 1276 auto [start, end] = frame->GetOffsets(); 1277 int32_t endLine = text.FindChar('\n', start); 1278 if (endLine == -1) { 1279 /* 1280 * If there is no newline in the text content, just save the 1281 * text from this frame and its continuations, and do bidi 1282 * resolution later 1283 */ 1284 aBpd->AppendString(Substring(text, start)); 1285 while (frame && nextSibling) { 1286 aBpd->AdvanceAndAppendFrame( 1287 &frame, aBpd->mCurrentTraverseLine, &nextSibling); 1288 } 1289 break; 1290 } 1291 1292 /* 1293 * If there is a newline in the frame, break the frame after the 1294 * newline, do bidi resolution and repeat until the last sibling 1295 */ 1296 ++endLine; 1297 1298 /* 1299 * If the frame ends before the new line, save the text and move 1300 * into the next continuation 1301 */ 1302 aBpd->AppendString( 1303 Substring(text, start, std::min(end, endLine) - start)); 1304 while (end < endLine && nextSibling) { 1305 aBpd->AdvanceAndAppendFrame(&frame, aBpd->mCurrentTraverseLine, 1306 &nextSibling); 1307 NS_ASSERTION(frame, "Premature end of continuation chain"); 1308 std::tie(start, end) = frame->GetOffsets(); 1309 aBpd->AppendString( 1310 Substring(text, start, std::min(end, endLine) - start)); 1311 } 1312 1313 if (end < endLine) { 1314 aBpd->mPrevContent = nullptr; 1315 break; 1316 } 1317 1318 bool createdContinuation = false; 1319 if (uint32_t(endLine) < text.Length()) { 1320 /* 1321 * Timing is everything here: if the frame already has a bidi 1322 * continuation, we need to make the continuation fluid *before* 1323 * resetting the length of the current frame. Otherwise 1324 * nsTextFrame::SetLength won't set the continuation frame's 1325 * text offsets correctly. 1326 * 1327 * On the other hand, if the frame doesn't have a continuation, 1328 * we need to create one *after* resetting the length, or 1329 * CreateContinuingFrame will complain that there is no more 1330 * content for the continuation. 1331 */ 1332 next = frame->GetNextInFlow(); 1333 if (!next) { 1334 // If the frame already has a bidi continuation, make it fluid 1335 next = frame->GetNextContinuation(); 1336 if (next) { 1337 MakeContinuationFluid(frame, next); 1338 JoinInlineAncestors(frame); 1339 } 1340 } 1341 1342 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame); 1343 textFrame->SetLength(endLine - start, nullptr); 1344 1345 // If it weren't for CreateContinuation needing this to 1346 // be current, we could restructure the marking dirty 1347 // below to use mCurrentResolveLine and eliminate 1348 // mCurrentTraverseLine entirely. 1349 aBpd->mCurrentTraverseLine.AdvanceToFrame(frame); 1350 1351 if (!next) { 1352 // If the frame has no next in flow, create one. 1353 CreateContinuation( 1354 frame, aBpd->mCurrentTraverseLine.GetLine(), &next, true); 1355 createdContinuation = true; 1356 } 1357 // Mark the line before the newline as dirty. 1358 aBpd->mCurrentTraverseLine.GetLine()->MarkDirty(); 1359 } 1360 ResolveParagraphWithinBlock(aBpd); 1361 1362 if (!nextSibling && !createdContinuation) { 1363 break; 1364 } 1365 if (next) { 1366 frame = next; 1367 aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine); 1368 // Mark the line after the newline as dirty. 1369 aBpd->mCurrentTraverseLine.AdvanceToFrame(frame); 1370 aBpd->mCurrentTraverseLine.GetLine()->MarkDirty(); 1371 } 1372 1373 /* 1374 * If we have already overshot the saved next-sibling while 1375 * scanning the frame's continuations, advance it. 1376 */ 1377 if (frame && frame == nextSibling) { 1378 nextSibling = frame->GetNextSibling(); 1379 } 1380 1381 } while (next); 1382 } 1383 } 1384 } else if (LayoutFrameType::Br == frameType) { 1385 // break frame -- append line separator 1386 aBpd->AppendUnichar(kLineSeparator); 1387 ResolveParagraphWithinBlock(aBpd); 1388 } else { 1389 // other frame type -- see the Unicode Bidi Algorithm: 1390 // "...inline objects (such as graphics) are treated as if they are ... 1391 // U+FFFC" 1392 // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See 1393 // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1. 1394 // Empty inline frames are also treated as kZWSP, to avoid unexpected 1395 // bidi reordering of the surrounding content. 1396 aBpd->AppendUnichar(content->IsHTMLElement(nsGkAtoms::wbr) || 1397 (frame->IsInlineFrame() && frame->IsEmpty()) 1398 ? kZWSP 1399 : kObjectSubstitute); 1400 if (!frame->IsInlineOutside()) { 1401 // if it is not inline, end the paragraph 1402 ResolveParagraphWithinBlock(aBpd); 1403 } 1404 } 1405 } else { 1406 // For a non-leaf frame, recurse into TraverseFrames 1407 nsIFrame* kid = frame->PrincipalChildList().FirstChild(); 1408 MOZ_ASSERT(!frame->GetChildList(FrameChildListID::Overflow).FirstChild(), 1409 "should have drained the overflow list above"); 1410 if (kid) { 1411 TraverseFrames(kid, aBpd); 1412 } 1413 } 1414 1415 // If the element is attributed by dir, indicate direction pop (add PDF 1416 // frame) 1417 if (isLastFrame) { 1418 // Add a dummy frame pointer representing a bidi control code after the 1419 // last frame of an element specifying embedding or override 1420 if (overrideChar != 0) { 1421 aBpd->PopBidiControl(overrideChar); 1422 } 1423 if (controlChar != 0) { 1424 aBpd->PopBidiControl(controlChar); 1425 } 1426 } 1427 childFrame = nextSibling; 1428 } while (childFrame); 1429 1430 MOZ_ASSERT(initialLineContainer == 1431 aBpd->mCurrentTraverseLine.mLineIterator.GetContainer()); 1432 } 1433 1434 bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame* aFirstChild, 1435 nsIContent** aCurrContent) { 1436 MOZ_ASSERT(!aFirstChild || !aFirstChild->GetPrevSibling(), 1437 "Expecting to traverse from the start of a child list"); 1438 1439 for (nsIFrame* childFrame = aFirstChild; childFrame; 1440 childFrame = childFrame->GetNextSibling()) { 1441 nsIFrame* frame = childFrame; 1442 1443 // If the real frame for a placeholder is a first-letter frame, we need to 1444 // consider its contents for potential Bidi resolution. 1445 if (childFrame->IsPlaceholderFrame()) { 1446 nsIFrame* realFrame = 1447 nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame); 1448 if (realFrame->IsLetterFrame()) { 1449 frame = realFrame; 1450 } 1451 } 1452 1453 // If unicode-bidi properties are present, we should do bidi resolution. 1454 ComputedStyle* sc = frame->Style(); 1455 if (GetBidiControl(sc) || GetBidiOverride(sc)) { 1456 return true; 1457 } 1458 1459 if (IsBidiLeaf(frame)) { 1460 if (frame->IsTextFrame()) { 1461 // If the frame already has a BidiDataProperty, we know we need to 1462 // perform bidi resolution (even if no bidi content is NOW present -- 1463 // we might need to remove the property set by a previous reflow, if 1464 // content has changed; see bug 1366623). 1465 if (frame->HasProperty(nsIFrame::BidiDataProperty())) { 1466 return true; 1467 } 1468 1469 // Check whether the text frame has any RTL characters; if so, bidi 1470 // resolution will be needed. 1471 dom::Text* content = frame->GetContent()->AsText(); 1472 if (content != *aCurrContent) { 1473 *aCurrContent = content; 1474 const dom::CharacterDataBuffer* txt = &content->DataBuffer(); 1475 if (txt->Is2b() && 1476 HasRTLChars(Span(txt->Get2b(), txt->GetLength()))) { 1477 return true; 1478 } 1479 } 1480 } 1481 } else if (ChildListMayRequireBidi(frame->PrincipalChildList().FirstChild(), 1482 aCurrContent)) { 1483 return true; 1484 } 1485 } 1486 1487 return false; 1488 } 1489 1490 void nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData* aBpd) { 1491 aBpd->ClearBidiControls(); 1492 ResolveParagraph(aBpd); 1493 aBpd->ResetData(); 1494 } 1495 1496 /* static */ 1497 nscoord nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine, 1498 int32_t aNumFramesOnLine, 1499 WritingMode aLineWM, 1500 const nsSize& aContainerSize, 1501 nscoord aStart) { 1502 nsSize containerSize(aContainerSize); 1503 1504 // If this line consists of a line frame, reorder the line frame's children. 1505 if (aFirstFrameOnLine->IsLineFrame()) { 1506 // The line frame is positioned at the start-edge, so use its size 1507 // as the container size. 1508 containerSize = aFirstFrameOnLine->GetSize(); 1509 1510 aFirstFrameOnLine = aFirstFrameOnLine->PrincipalChildList().FirstChild(); 1511 if (!aFirstFrameOnLine) { 1512 return 0; 1513 } 1514 // All children of the line frame are on the first line. Setting 1515 // aNumFramesOnLine to -1 makes InitLogicalArrayFromLine look at all of 1516 // them. 1517 aNumFramesOnLine = -1; 1518 // As the line frame itself has been adjusted at its inline-start position 1519 // by the caller, we do not want to apply this to its children. 1520 aStart = 0; 1521 } 1522 1523 // No need to bidi-reorder the line if there's only a single frame. 1524 if (aNumFramesOnLine == 1) { 1525 auto bidiData = nsBidiPresUtils::GetFrameBidiData(aFirstFrameOnLine); 1526 nsContinuationStates continuationStates; 1527 InitContinuationStates(aFirstFrameOnLine, &continuationStates); 1528 return aStart + RepositionFrame(aFirstFrameOnLine, 1529 bidiData.embeddingLevel.IsLTR(), aStart, 1530 &continuationStates, aLineWM, false, 1531 containerSize); 1532 } 1533 1534 BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); 1535 return RepositionInlineFrames(bld, aLineWM, containerSize, aStart); 1536 } 1537 1538 nsIFrame* nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame) { 1539 nsIFrame* firstLeaf = aFrame; 1540 while (!IsBidiLeaf(firstLeaf)) { 1541 nsIFrame* firstChild = firstLeaf->PrincipalChildList().FirstChild(); 1542 nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(firstChild); 1543 firstLeaf = (realFrame->IsLetterFrame()) ? realFrame : firstChild; 1544 } 1545 return firstLeaf; 1546 } 1547 1548 FrameBidiData nsBidiPresUtils::GetFrameBidiData(nsIFrame* aFrame) { 1549 return GetFirstLeaf(aFrame)->GetBidiData(); 1550 } 1551 1552 BidiEmbeddingLevel nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) { 1553 return GetFirstLeaf(aFrame)->GetEmbeddingLevel(); 1554 } 1555 1556 BidiEmbeddingLevel nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame* aFrame) { 1557 const nsIFrame* firstLeaf = aFrame; 1558 while (!IsBidiLeaf(firstLeaf)) { 1559 firstLeaf = firstLeaf->PrincipalChildList().FirstChild(); 1560 } 1561 return firstLeaf->GetBaseLevel(); 1562 } 1563 1564 void nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame, 1565 nsContinuationStates* aContinuationStates, 1566 bool aSpanDirMatchesLineDir, 1567 bool& aIsFirst /* out */, 1568 bool& aIsLast /* out */) { 1569 /* 1570 * Since we lay out frames in the line's direction, visiting a frame with 1571 * 'mFirstVisualFrame == nullptr', means it's the first appearance of one 1572 * of its continuation chain frames on the line. 1573 * To determine if it's the last visual frame of its continuation chain on 1574 * the line or not, we count the number of frames of the chain on the line, 1575 * and then reduce it when we lay out a frame of the chain. If this value 1576 * becomes 1 it means that it's the last visual frame of its continuation 1577 * chain on this line. 1578 */ 1579 1580 bool firstInLineOrder, lastInLineOrder; 1581 nsFrameContinuationState* frameState = aContinuationStates->Get(aFrame); 1582 nsFrameContinuationState* firstFrameState; 1583 1584 if (!frameState->mFirstVisualFrame) { 1585 // aFrame is the first visual frame of its continuation chain 1586 nsFrameContinuationState* contState; 1587 nsIFrame* frame; 1588 1589 frameState->mFrameCount = 1; 1590 frameState->mFirstVisualFrame = aFrame; 1591 1592 /** 1593 * Traverse continuation chain of aFrame in both backward and forward 1594 * directions while the frames are on this line. Count the frames and 1595 * set their mFirstVisualFrame to aFrame. 1596 */ 1597 // Traverse continuation chain backward 1598 for (frame = aFrame->GetPrevContinuation(); 1599 frame && (contState = aContinuationStates->Get(frame)); 1600 frame = frame->GetPrevContinuation()) { 1601 frameState->mFrameCount++; 1602 contState->mFirstVisualFrame = aFrame; 1603 } 1604 frameState->mHasContOnPrevLines = (frame != nullptr); 1605 1606 // Traverse continuation chain forward 1607 for (frame = aFrame->GetNextContinuation(); 1608 frame && (contState = aContinuationStates->Get(frame)); 1609 frame = frame->GetNextContinuation()) { 1610 frameState->mFrameCount++; 1611 contState->mFirstVisualFrame = aFrame; 1612 } 1613 frameState->mHasContOnNextLines = (frame != nullptr); 1614 1615 firstInLineOrder = true; 1616 firstFrameState = frameState; 1617 } else { 1618 // aFrame is not the first visual frame of its continuation chain 1619 firstInLineOrder = false; 1620 firstFrameState = aContinuationStates->Get(frameState->mFirstVisualFrame); 1621 } 1622 1623 lastInLineOrder = (firstFrameState->mFrameCount == 1); 1624 1625 if (aSpanDirMatchesLineDir) { 1626 aIsFirst = firstInLineOrder; 1627 aIsLast = lastInLineOrder; 1628 } else { 1629 aIsFirst = lastInLineOrder; 1630 aIsLast = firstInLineOrder; 1631 } 1632 1633 if (frameState->mHasContOnPrevLines) { 1634 aIsFirst = false; 1635 } 1636 if (firstFrameState->mHasContOnNextLines) { 1637 aIsLast = false; 1638 } 1639 1640 if ((aIsFirst || aIsLast) && 1641 aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { 1642 // For ib splits, don't treat anything except the last part as 1643 // endmost or anything except the first part as startmost. 1644 // As an optimization, only get the first continuation once. 1645 nsIFrame* firstContinuation = aFrame->FirstContinuation(); 1646 if (firstContinuation->FrameIsNonLastInIBSplit()) { 1647 // We are not endmost 1648 aIsLast = false; 1649 } 1650 if (firstContinuation->FrameIsNonFirstInIBSplit()) { 1651 // We are not startmost 1652 aIsFirst = false; 1653 } 1654 } 1655 1656 // Reduce number of remaining frames of the continuation chain on the line. 1657 firstFrameState->mFrameCount--; 1658 1659 nsInlineFrame* testFrame = do_QueryFrame(aFrame); 1660 1661 if (testFrame) { 1662 aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET); 1663 1664 if (aIsFirst) { 1665 aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST); 1666 } else { 1667 aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST); 1668 } 1669 1670 if (aIsLast) { 1671 aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST); 1672 } else { 1673 aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST); 1674 } 1675 } 1676 } 1677 1678 /* static */ 1679 void nsBidiPresUtils::RepositionRubyContentFrame( 1680 nsIFrame* aFrame, WritingMode aFrameWM, 1681 const LogicalMargin& aBorderPadding) { 1682 const nsFrameList& childList = aFrame->PrincipalChildList(); 1683 if (childList.IsEmpty()) { 1684 return; 1685 } 1686 1687 // Reorder the children. 1688 nscoord isize = 1689 ReorderFrames(childList.FirstChild(), childList.GetLength(), aFrameWM, 1690 aFrame->GetSize(), aBorderPadding.IStart(aFrameWM)); 1691 isize += aBorderPadding.IEnd(aFrameWM); 1692 1693 if (aFrame->StyleText()->mRubyAlign == StyleRubyAlign::Start) { 1694 return; 1695 } 1696 nscoord residualISize = aFrame->ISize(aFrameWM) - isize; 1697 if (residualISize <= 0) { 1698 return; 1699 } 1700 1701 // When ruby-align is not "start", if the content does not fill this 1702 // frame, we need to center the children. 1703 const nsSize dummyContainerSize; 1704 for (nsIFrame* child : childList) { 1705 LogicalRect rect = child->GetLogicalRect(aFrameWM, dummyContainerSize); 1706 rect.IStart(aFrameWM) += residualISize / 2; 1707 child->SetRect(aFrameWM, rect, dummyContainerSize); 1708 } 1709 } 1710 1711 /* static */ 1712 nscoord nsBidiPresUtils::RepositionRubyFrame( 1713 nsIFrame* aFrame, nsContinuationStates* aContinuationStates, 1714 const WritingMode aContainerWM, const LogicalMargin& aBorderPadding) { 1715 LayoutFrameType frameType = aFrame->Type(); 1716 MOZ_ASSERT(RubyUtils::IsRubyBox(frameType)); 1717 1718 nscoord icoord = 0; 1719 WritingMode frameWM = aFrame->GetWritingMode(); 1720 bool isLTR = frameWM.IsBidiLTR(); 1721 nsSize frameSize = aFrame->GetSize(); 1722 if (frameType == LayoutFrameType::Ruby) { 1723 icoord += aBorderPadding.IStart(frameWM); 1724 // Reposition ruby segments in a ruby container 1725 for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(aFrame)); !e.AtEnd(); 1726 e.Next()) { 1727 nsRubyBaseContainerFrame* rbc = e.GetBaseContainer(); 1728 AutoRubyTextContainerArray textContainers(rbc); 1729 1730 nscoord segmentISize = RepositionFrame( 1731 rbc, isLTR, icoord, aContinuationStates, frameWM, false, frameSize); 1732 for (nsRubyTextContainerFrame* rtc : textContainers) { 1733 nscoord isize = RepositionFrame(rtc, isLTR, icoord, aContinuationStates, 1734 frameWM, false, frameSize); 1735 segmentISize = std::max(segmentISize, isize); 1736 } 1737 icoord += segmentISize; 1738 } 1739 icoord += aBorderPadding.IEnd(frameWM); 1740 } else if (frameType == LayoutFrameType::RubyBaseContainer) { 1741 // Reposition ruby columns in a ruby segment 1742 auto rbc = static_cast<nsRubyBaseContainerFrame*>(aFrame); 1743 AutoRubyTextContainerArray textContainers(rbc); 1744 1745 for (RubyColumnEnumerator e(rbc, textContainers); !e.AtEnd(); e.Next()) { 1746 RubyColumn column; 1747 e.GetColumn(column); 1748 1749 nscoord columnISize = 1750 RepositionFrame(column.mBaseFrame, isLTR, icoord, aContinuationStates, 1751 frameWM, false, frameSize); 1752 for (nsRubyTextFrame* rt : column.mTextFrames) { 1753 nscoord isize = RepositionFrame(rt, isLTR, icoord, aContinuationStates, 1754 frameWM, false, frameSize); 1755 columnISize = std::max(columnISize, isize); 1756 } 1757 icoord += columnISize; 1758 } 1759 } else { 1760 if (frameType == LayoutFrameType::RubyBase || 1761 frameType == LayoutFrameType::RubyText) { 1762 RepositionRubyContentFrame(aFrame, frameWM, aBorderPadding); 1763 } 1764 // Note that, ruby text container is not present in all conditions 1765 // above. It is intended, because the children of rtc are reordered 1766 // with the children of ruby base container simultaneously. We only 1767 // need to return its isize here, as it should not be changed. 1768 icoord += aFrame->ISize(aContainerWM); 1769 } 1770 return icoord; 1771 } 1772 1773 /* static */ 1774 nscoord nsBidiPresUtils::RepositionFrame( 1775 nsIFrame* aFrame, bool aIsEvenLevel, nscoord aStartOrEnd, 1776 nsContinuationStates* aContinuationStates, WritingMode aContainerWM, 1777 bool aContainerReverseDir, const nsSize& aContainerSize) { 1778 nscoord lineSize = 1779 aContainerWM.IsVertical() ? aContainerSize.height : aContainerSize.width; 1780 NS_ASSERTION(lineSize != NS_UNCONSTRAINEDSIZE, 1781 "Unconstrained inline line size in bidi frame reordering"); 1782 if (!aFrame) { 1783 return 0; 1784 } 1785 1786 bool isFirst, isLast; 1787 WritingMode frameWM = aFrame->GetWritingMode(); 1788 IsFirstOrLast(aFrame, aContinuationStates, 1789 aContainerWM.IsBidiLTR() == frameWM.IsBidiLTR(), 1790 isFirst /* out */, isLast /* out */); 1791 1792 // We only need the margin if the frame is first or last in its own 1793 // writing mode, but we're traversing the frames in the order of the 1794 // container's writing mode. To get the right values, we set start and 1795 // end margins on a logical margin in the frame's writing mode, and 1796 // then convert the margin to the container's writing mode to set the 1797 // coordinates. 1798 1799 // This method is called from nsBlockFrame::PlaceLine via the call to 1800 // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines 1801 // have been reflowed, which is required for GetUsedMargin/Border/Padding 1802 nscoord frameISize = aFrame->ISize(); 1803 LogicalMargin frameMargin = aFrame->GetLogicalUsedMargin(frameWM); 1804 LogicalMargin borderPadding = aFrame->GetLogicalUsedBorderAndPadding(frameWM); 1805 // Since the visual order of frame could be different from the continuation 1806 // order, we need to remove any inline border/padding [that is already applied 1807 // based on continuation order] and then add it back based on the visual order 1808 // (i.e. isFirst/isLast) to get the correct isize for the current frame. 1809 // We don't need to do that for 'box-decoration-break:clone' because then all 1810 // continuations have border/padding/margin applied. 1811 if (aFrame->StyleBorder()->mBoxDecorationBreak == 1812 StyleBoxDecorationBreak::Slice) { 1813 // First remove the border/padding that was applied based on logical order. 1814 if (!aFrame->GetPrevContinuation()) { 1815 frameISize -= borderPadding.IStart(frameWM); 1816 } 1817 if (!aFrame->GetNextContinuation()) { 1818 frameISize -= borderPadding.IEnd(frameWM); 1819 } 1820 // Set margin/border/padding based on visual order. 1821 if (!isFirst) { 1822 frameMargin.IStart(frameWM) = 0; 1823 borderPadding.IStart(frameWM) = 0; 1824 } 1825 if (!isLast) { 1826 frameMargin.IEnd(frameWM) = 0; 1827 borderPadding.IEnd(frameWM) = 0; 1828 } 1829 // Add the border/padding which is now based on visual order. 1830 frameISize += borderPadding.IStartEnd(frameWM); 1831 } 1832 1833 nscoord icoord = 0; 1834 if (IsBidiLeaf(aFrame)) { 1835 icoord += 1836 frameWM.IsOrthogonalTo(aContainerWM) ? aFrame->BSize() : frameISize; 1837 } else if (RubyUtils::IsRubyBox(aFrame->Type())) { 1838 icoord += RepositionRubyFrame(aFrame, aContinuationStates, aContainerWM, 1839 borderPadding); 1840 } else { 1841 bool reverseDir = aIsEvenLevel != frameWM.IsBidiLTR(); 1842 icoord += reverseDir ? borderPadding.IEnd(frameWM) 1843 : borderPadding.IStart(frameWM); 1844 LogicalSize logicalSize(frameWM, frameISize, aFrame->BSize()); 1845 nsSize frameSize = logicalSize.GetPhysicalSize(frameWM); 1846 // Reposition the child frames 1847 for (nsIFrame* f : aFrame->PrincipalChildList()) { 1848 icoord += RepositionFrame(f, aIsEvenLevel, icoord, aContinuationStates, 1849 frameWM, reverseDir, frameSize); 1850 } 1851 icoord += reverseDir ? borderPadding.IStart(frameWM) 1852 : borderPadding.IEnd(frameWM); 1853 } 1854 1855 // In the following variables, if aContainerReverseDir is true, i.e. 1856 // the container is positioning its children in reverse of its logical 1857 // direction, the "StartOrEnd" refers to the distance from the frame 1858 // to the inline end edge of the container, elsewise, it refers to the 1859 // distance to the inline start edge. 1860 const LogicalMargin margin = frameMargin.ConvertTo(aContainerWM, frameWM); 1861 nscoord marginStartOrEnd = aContainerReverseDir ? margin.IEnd(aContainerWM) 1862 : margin.IStart(aContainerWM); 1863 nscoord frameStartOrEnd = aStartOrEnd + marginStartOrEnd; 1864 1865 LogicalRect rect = aFrame->GetLogicalRect(aContainerWM, aContainerSize); 1866 rect.ISize(aContainerWM) = icoord; 1867 rect.IStart(aContainerWM) = aContainerReverseDir 1868 ? lineSize - frameStartOrEnd - icoord 1869 : frameStartOrEnd; 1870 aFrame->SetRect(aContainerWM, rect, aContainerSize); 1871 1872 return icoord + margin.IStartEnd(aContainerWM); 1873 } 1874 1875 void nsBidiPresUtils::InitContinuationStates( 1876 nsIFrame* aFrame, nsContinuationStates* aContinuationStates) { 1877 aContinuationStates->Insert(aFrame); 1878 if (!IsBidiLeaf(aFrame)) { 1879 // Continue for child frames 1880 for (nsIFrame* frame : aFrame->PrincipalChildList()) { 1881 InitContinuationStates(frame, aContinuationStates); 1882 } 1883 } 1884 } 1885 1886 /* static */ 1887 nscoord nsBidiPresUtils::RepositionInlineFrames(const BidiLineData& aBld, 1888 WritingMode aLineWM, 1889 const nsSize& aContainerSize, 1890 nscoord aStart) { 1891 nsContinuationStates continuationStates; 1892 aBld.InitContinuationStates(&continuationStates); 1893 1894 if (aLineWM.IsBidiLTR()) { 1895 for (auto index : IntegerRange(aBld.VisualFrameCount())) { 1896 auto [frame, level] = aBld.VisualFrameAndLevelAt(index); 1897 aStart += 1898 RepositionFrame(frame, level.IsLTR(), aStart, &continuationStates, 1899 aLineWM, false, aContainerSize); 1900 } 1901 } else { 1902 for (auto index : Reversed(IntegerRange(aBld.VisualFrameCount()))) { 1903 auto [frame, level] = aBld.VisualFrameAndLevelAt(index); 1904 aStart += 1905 RepositionFrame(frame, level.IsLTR(), aStart, &continuationStates, 1906 aLineWM, false, aContainerSize); 1907 } 1908 } 1909 1910 return aStart; 1911 } 1912 1913 bool nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine, 1914 int32_t aNumFramesOnLine, 1915 nsIFrame** aFirstVisual, 1916 nsIFrame** aLastVisual) { 1917 BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); 1918 1919 if (aFirstVisual) { 1920 *aFirstVisual = bld.VisualFrameAt(0); 1921 } 1922 if (aLastVisual) { 1923 *aLastVisual = bld.VisualFrameAt(bld.VisualFrameCount() - 1); 1924 } 1925 1926 return bld.IsReordered(); 1927 } 1928 1929 nsIFrame* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame, 1930 nsIFrame* aFirstFrameOnLine, 1931 int32_t aNumFramesOnLine) { 1932 BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); 1933 1934 int32_t count = bld.VisualFrameCount(); 1935 1936 if (!aFrame && count) { 1937 return bld.VisualFrameAt(0); 1938 } 1939 1940 for (int32_t i = 0; i < count - 1; i++) { 1941 if (bld.VisualFrameAt(i) == aFrame) { 1942 return bld.VisualFrameAt(i + 1); 1943 } 1944 } 1945 1946 return nullptr; 1947 } 1948 1949 nsIFrame* nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame* aFrame, 1950 nsIFrame* aFirstFrameOnLine, 1951 int32_t aNumFramesOnLine) { 1952 BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); 1953 1954 int32_t count = bld.VisualFrameCount(); 1955 1956 if (!aFrame && count) { 1957 return bld.VisualFrameAt(count - 1); 1958 } 1959 1960 for (int32_t i = 1; i < count; i++) { 1961 if (bld.VisualFrameAt(i) == aFrame) { 1962 return bld.VisualFrameAt(i - 1); 1963 } 1964 } 1965 1966 return nullptr; 1967 } 1968 1969 inline void nsBidiPresUtils::EnsureBidiContinuation( 1970 nsIFrame* aFrame, const nsLineList::iterator aLine, nsIFrame** aNewFrame, 1971 int32_t aStart, int32_t aEnd) { 1972 MOZ_ASSERT(aNewFrame, "null OUT ptr"); 1973 MOZ_ASSERT(aFrame, "aFrame is null"); 1974 1975 aFrame->AdjustOffsetsForBidi(aStart, aEnd); 1976 CreateContinuation(aFrame, aLine, aNewFrame, false); 1977 } 1978 1979 void nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData* aBpd, 1980 nsIFrame* aFrame, 1981 int32_t aFirstIndex, 1982 int32_t aLastIndex) { 1983 // If we're only processing one frame, and it is already a fluid continuation 1984 // (next-in-flow), there's nothing to do. 1985 if (aLastIndex == aFirstIndex + 1 && 1986 aFrame->GetNextInFlow() == aFrame->GetNextContinuation()) { 1987 return; 1988 } 1989 FrameBidiData bidiData = aFrame->GetBidiData(); 1990 bidiData.precedingControl = kBidiLevelNone; 1991 for (int32_t index = aFirstIndex + 1; index <= aLastIndex; index++) { 1992 nsIFrame* frame = aBpd->FrameAt(index); 1993 if (frame != NS_BIDI_CONTROL_FRAME) { 1994 // Make the frame and its continuation ancestors fluid, 1995 // so they can be reused or deleted by normal reflow code 1996 frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData); 1997 frame->AddStateBits(NS_FRAME_IS_BIDI); 1998 while (frame && IsBidiSplittable(frame)) { 1999 nsIFrame* prev = frame->GetPrevContinuation(); 2000 if (prev) { 2001 MakeContinuationFluid(prev, frame); 2002 frame = frame->GetParent(); 2003 } else { 2004 break; 2005 } 2006 } 2007 } 2008 } 2009 2010 // Make sure that the last continuation we made fluid does not itself have a 2011 // fluid continuation (this can happen when re-resolving after dynamic changes 2012 // to content) 2013 nsIFrame* lastFrame = aBpd->FrameAt(aLastIndex); 2014 MakeContinuationsNonFluidUpParentChain(lastFrame, lastFrame->GetNextInFlow()); 2015 } 2016 2017 nsresult nsBidiPresUtils::FormatUnicodeText(nsPresContext* aPresContext, 2018 char16_t* aText, 2019 int32_t& aTextLength, 2020 BidiClass aBidiClass) { 2021 nsresult rv = NS_OK; 2022 // ahmed 2023 // adjusted for correct numeral shaping 2024 uint32_t bidiOptions = aPresContext->GetBidi(); 2025 switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) { 2026 case IBMBIDI_NUMERAL_HINDI: 2027 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI); 2028 break; 2029 2030 case IBMBIDI_NUMERAL_ARABIC: 2031 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC); 2032 break; 2033 2034 case IBMBIDI_NUMERAL_PERSIAN: 2035 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN); 2036 break; 2037 2038 case IBMBIDI_NUMERAL_REGULAR: 2039 2040 switch (aBidiClass) { 2041 case BidiClass::EuropeanNumber: 2042 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC); 2043 break; 2044 2045 case BidiClass::ArabicNumber: 2046 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI); 2047 break; 2048 2049 default: 2050 break; 2051 } 2052 break; 2053 2054 case IBMBIDI_NUMERAL_HINDICONTEXT: 2055 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions) == 2056 IBMBIDI_TEXTDIRECTION_RTL) && 2057 (IS_ARABIC_DIGIT(aText[0]))) || 2058 (BidiClass::ArabicNumber == aBidiClass)) { 2059 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI); 2060 } else if (BidiClass::EuropeanNumber == aBidiClass) { 2061 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC); 2062 } 2063 break; 2064 2065 case IBMBIDI_NUMERAL_PERSIANCONTEXT: 2066 if (((GET_BIDI_OPTION_DIRECTION(bidiOptions) == 2067 IBMBIDI_TEXTDIRECTION_RTL) && 2068 (IS_ARABIC_DIGIT(aText[0]))) || 2069 (BidiClass::ArabicNumber == aBidiClass)) { 2070 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN); 2071 } else if (BidiClass::EuropeanNumber == aBidiClass) { 2072 HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC); 2073 } 2074 break; 2075 2076 case IBMBIDI_NUMERAL_NOMINAL: 2077 default: 2078 break; 2079 } 2080 2081 StripBidiControlCharacters(aText, aTextLength); 2082 return rv; 2083 } 2084 2085 void nsBidiPresUtils::StripBidiControlCharacters(char16_t* aText, 2086 int32_t& aTextLength) { 2087 if ((nullptr == aText) || (aTextLength < 1)) { 2088 return; 2089 } 2090 2091 int32_t stripLen = 0; 2092 2093 for (int32_t i = 0; i < aTextLength; i++) { 2094 // XXX: This silently ignores surrogate characters. 2095 // As of Unicode 4.0, all Bidi control characters are within the BMP. 2096 if (IsBidiControl((uint32_t)aText[i])) { 2097 ++stripLen; 2098 } else { 2099 aText[i - stripLen] = aText[i]; 2100 } 2101 } 2102 aTextLength -= stripLen; 2103 } 2104 2105 void nsBidiPresUtils::CalculateBidiClass( 2106 const char16_t* aText, int32_t& aOffset, int32_t aBidiClassLimit, 2107 int32_t& aRunLimit, int32_t& aRunLength, int32_t& aRunCount, 2108 BidiClass& aBidiClass, BidiClass& aPrevBidiClass) { 2109 bool strongTypeFound = false; 2110 int32_t offset; 2111 BidiClass bidiClass; 2112 2113 aBidiClass = BidiClass::OtherNeutral; 2114 2115 int32_t charLen; 2116 for (offset = aOffset; offset < aBidiClassLimit; offset += charLen) { 2117 // Make sure we give RTL chartype to all characters that would be classified 2118 // as Right-To-Left by a bidi platform. 2119 // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.) 2120 charLen = 1; 2121 uint32_t ch = aText[offset]; 2122 if (IS_HEBREW_CHAR(ch)) { 2123 bidiClass = BidiClass::RightToLeft; 2124 } else if (IS_ARABIC_ALPHABETIC(ch)) { 2125 bidiClass = BidiClass::RightToLeftArabic; 2126 } else { 2127 if (offset + 1 < aBidiClassLimit && 2128 NS_IS_SURROGATE_PAIR(ch, aText[offset + 1])) { 2129 ch = SURROGATE_TO_UCS4(ch, aText[offset + 1]); 2130 charLen = 2; 2131 } 2132 bidiClass = intl::UnicodeProperties::GetBidiClass(ch); 2133 } 2134 2135 if (!BIDICLASS_IS_WEAK(bidiClass)) { 2136 if (strongTypeFound && (bidiClass != aPrevBidiClass) && 2137 (BIDICLASS_IS_RTL(bidiClass) || BIDICLASS_IS_RTL(aPrevBidiClass))) { 2138 // Stop at this point to ensure uni-directionality of the text 2139 // (from platform's point of view). 2140 // Also, don't mix Arabic and Hebrew content (since platform may 2141 // provide BIDI support to one of them only). 2142 aRunLength = offset - aOffset; 2143 aRunLimit = offset; 2144 ++aRunCount; 2145 break; 2146 } 2147 2148 if ((BidiClass::RightToLeftArabic == aPrevBidiClass || 2149 BidiClass::ArabicNumber == aPrevBidiClass) && 2150 BidiClass::EuropeanNumber == bidiClass) { 2151 bidiClass = BidiClass::ArabicNumber; 2152 } 2153 2154 // Set PrevBidiClass to the last strong type in this frame 2155 // (for correct numeric shaping) 2156 aPrevBidiClass = bidiClass; 2157 2158 strongTypeFound = true; 2159 aBidiClass = bidiClass; 2160 } 2161 } 2162 aOffset = offset; 2163 } 2164 2165 nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, size_t aLength, 2166 BidiEmbeddingLevel aBaseLevel, 2167 nsPresContext* aPresContext, 2168 BidiProcessor& aprocessor, Mode aMode, 2169 nsBidiPositionResolve* aPosResolve, 2170 int32_t aPosResolveCount, nscoord* aWidth, 2171 BidiEngine& aBidiEngine) { 2172 MOZ_ASSERT((aPosResolve == nullptr) != (aPosResolveCount > 0), 2173 "Incorrect aPosResolve / aPosResolveCount arguments"); 2174 2175 // Caller should have already replaced any separators in the original text 2176 // with <space> characters. 2177 MOZ_ASSERT(nsDependentSubstring(aText, aLength).FindCharInSet(kSeparators) == 2178 kNotFound); 2179 2180 for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) { 2181 aPosResolve[nPosResolve].visualIndex = kNotFound; 2182 aPosResolve[nPosResolve].visualLeftTwips = kNotFound; 2183 aPosResolve[nPosResolve].visualWidth = kNotFound; 2184 } 2185 2186 // For a single-char string, or a string that is purely LTR, use a simplified 2187 // path as it cannot have multiple direction or bidi-class runs. 2188 if (aLength == 1 || 2189 (aLength == 2 && NS_IS_SURROGATE_PAIR(aText[0], aText[1])) || 2190 (aBaseLevel.Direction() == BidiDirection::LTR && 2191 !encoding_mem_is_utf16_bidi(aText, aLength))) { 2192 ProcessSimpleRun(aText, aLength, aBaseLevel, aPresContext, aprocessor, 2193 aMode, aPosResolve, aPosResolveCount, aWidth); 2194 return NS_OK; 2195 } 2196 2197 if (aBidiEngine.SetParagraph(Span(aText, aLength), aBaseLevel).isErr()) { 2198 return NS_ERROR_FAILURE; 2199 } 2200 2201 auto result = aBidiEngine.CountRuns(); 2202 if (result.isErr()) { 2203 return NS_ERROR_FAILURE; 2204 } 2205 int32_t runCount = result.unwrap(); 2206 2207 nscoord xOffset = 0; 2208 nscoord width, xEndRun = 0; 2209 nscoord totalWidth = 0; 2210 int32_t i, start, limit, length; 2211 uint32_t visualStart = 0; 2212 BidiClass bidiClass; 2213 BidiClass prevClass = BidiClass::LeftToRight; 2214 2215 for (i = 0; i < runCount; i++) { 2216 aBidiEngine.GetVisualRun(i, &start, &length); 2217 2218 BidiEmbeddingLevel level; 2219 aBidiEngine.GetLogicalRun(start, &limit, &level); 2220 2221 BidiDirection dir = level.Direction(); 2222 int32_t subRunLength = limit - start; 2223 int32_t lineOffset = start; 2224 int32_t typeLimit = std::min(limit, AssertedCast<int32_t>(aLength)); 2225 int32_t subRunCount = 1; 2226 int32_t subRunLimit = typeLimit; 2227 2228 /* 2229 * If |level| is even, i.e. the direction of the run is left-to-right, we 2230 * render the subruns from left to right and increment the x-coordinate 2231 * |xOffset| by the width of each subrun after rendering. 2232 * 2233 * If |level| is odd, i.e. the direction of the run is right-to-left, we 2234 * render the subruns from right to left. We begin by incrementing |xOffset| 2235 * by the width of the whole run, and then decrement it by the width of each 2236 * subrun before rendering. After rendering all the subruns, we restore the 2237 * x-coordinate of the end of the run for the start of the next run. 2238 */ 2239 2240 if (dir == BidiDirection::RTL) { 2241 aprocessor.SetText(aText + start, subRunLength, BidiDirection::RTL); 2242 width = aprocessor.GetWidth(); 2243 xOffset += width; 2244 xEndRun = xOffset; 2245 } 2246 2247 while (subRunCount > 0) { 2248 // CalculateBidiClass can increment subRunCount if the run 2249 // contains mixed character types 2250 CalculateBidiClass(aText, lineOffset, typeLimit, subRunLimit, 2251 subRunLength, subRunCount, bidiClass, prevClass); 2252 2253 nsAutoString runVisualText(aText + start, subRunLength); 2254 if (aPresContext) { 2255 FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), 2256 subRunLength, bidiClass); 2257 } 2258 2259 aprocessor.SetText(runVisualText.get(), subRunLength, dir); 2260 width = aprocessor.GetWidth(); 2261 totalWidth += width; 2262 if (dir == BidiDirection::RTL) { 2263 xOffset -= width; 2264 } 2265 if (aMode == MODE_DRAW) { 2266 aprocessor.DrawText(xOffset); 2267 } 2268 2269 /* 2270 * The caller may request to calculate the visual position of one 2271 * or more characters. 2272 */ 2273 for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) { 2274 nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve]; 2275 /* 2276 * Did we already resolve this position's visual metric? If so, skip. 2277 */ 2278 if (posResolve->visualLeftTwips != kNotFound) { 2279 continue; 2280 } 2281 2282 /* 2283 * First find out if the logical position is within this run. 2284 */ 2285 if (start <= posResolve->logicalIndex && 2286 start + subRunLength > posResolve->logicalIndex) { 2287 /* 2288 * If this run is only one character long, we have an easy case: 2289 * the visual position is the x-coord of the start of the run 2290 * less the x-coord of the start of the whole text. 2291 */ 2292 if (subRunLength == 1) { 2293 posResolve->visualIndex = visualStart; 2294 posResolve->visualLeftTwips = xOffset; 2295 posResolve->visualWidth = width; 2296 } 2297 /* 2298 * Otherwise, we need to measure the width of the run's part 2299 * which is to the visual left of the index. 2300 * In other words, the run is broken in two, around the logical index, 2301 * and we measure the part which is visually left. 2302 * If the run is right-to-left, this part will span from after the 2303 * index up to the end of the run; if it is left-to-right, this part 2304 * will span from the start of the run up to (and inclduing) the 2305 * character before the index. 2306 */ 2307 else { 2308 /* 2309 * Here is a description of how the width of the current character 2310 * (posResolve->visualWidth) is calculated: 2311 * 2312 * LTR (current char: "P"): 2313 * S A M P L E (logical index: 3, visual index: 3) 2314 * ^ (visualLeftPart) 2315 * ^ (visualRightSide) 2316 * visualLeftLength == 3 2317 * ^^^^^^ (subWidth) 2318 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide) 2319 * ^^ (posResolve->visualWidth) 2320 * 2321 * RTL (current char: "M"): 2322 * E L P M A S (logical index: 2, visual index: 3) 2323 * ^ (visualLeftPart) 2324 * ^ (visualRightSide) 2325 * visualLeftLength == 3 2326 * ^^^^^^ (subWidth) 2327 * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide) 2328 * ^^ (posResolve->visualWidth) 2329 */ 2330 nscoord subWidth; 2331 // The position in the text where this run's "left part" begins. 2332 const char16_t* visualLeftPart; 2333 const char16_t* visualRightSide; 2334 if (dir == BidiDirection::RTL) { 2335 // One day, son, this could all be replaced with 2336 // mPresContext->BidiEngine().GetVisualIndex() ... 2337 posResolve->visualIndex = 2338 visualStart + 2339 (subRunLength - (posResolve->logicalIndex + 1 - start)); 2340 // Skipping to the "left part". 2341 visualLeftPart = aText + posResolve->logicalIndex + 1; 2342 // Skipping to the right side of the current character 2343 visualRightSide = visualLeftPart - 1; 2344 } else { 2345 posResolve->visualIndex = 2346 visualStart + (posResolve->logicalIndex - start); 2347 // Skipping to the "left part". 2348 visualLeftPart = aText + start; 2349 // In LTR mode this is the same as visualLeftPart 2350 visualRightSide = visualLeftPart; 2351 } 2352 // The delta between the start of the run and the left part's end. 2353 int32_t visualLeftLength = posResolve->visualIndex - visualStart; 2354 aprocessor.SetText(visualLeftPart, visualLeftLength, dir); 2355 subWidth = aprocessor.GetWidth(); 2356 aprocessor.SetText(visualRightSide, visualLeftLength + 1, dir); 2357 posResolve->visualLeftTwips = xOffset + subWidth; 2358 posResolve->visualWidth = aprocessor.GetWidth() - subWidth; 2359 } 2360 } 2361 } 2362 2363 if (dir == BidiDirection::LTR) { 2364 xOffset += width; 2365 } 2366 2367 --subRunCount; 2368 start = lineOffset; 2369 subRunLimit = typeLimit; 2370 subRunLength = typeLimit - lineOffset; 2371 } // while 2372 if (dir == BidiDirection::RTL) { 2373 xOffset = xEndRun; 2374 } 2375 2376 visualStart += length; 2377 } // for 2378 2379 if (aWidth) { 2380 *aWidth = totalWidth; 2381 } 2382 return NS_OK; 2383 } 2384 2385 // This is called either for a single character (one code unit, or a surrogate 2386 // pair), or for a run that is known to be purely LTR. 2387 void nsBidiPresUtils::ProcessSimpleRun(const char16_t* aText, size_t aLength, 2388 BidiEmbeddingLevel aBaseLevel, 2389 nsPresContext* aPresContext, 2390 BidiProcessor& aprocessor, Mode aMode, 2391 nsBidiPositionResolve* aPosResolve, 2392 int32_t aPosResolveCount, 2393 nscoord* aWidth) { 2394 if (!aLength) { 2395 if (aWidth) { 2396 *aWidth = 0; 2397 } 2398 return; 2399 } 2400 // Get bidi class from the first (or only) character. 2401 uint32_t ch = aText[0]; 2402 if (aLength > 1 && NS_IS_HIGH_SURROGATE(ch) && 2403 NS_IS_LOW_SURROGATE(aText[1])) { 2404 ch = SURROGATE_TO_UCS4(aText[0], aText[1]); 2405 } 2406 BidiClass bidiClass = intl::UnicodeProperties::GetBidiClass(ch); 2407 2408 nsAutoString runVisualText(aText, aLength); 2409 int32_t length = aLength; 2410 if (aPresContext) { 2411 FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), length, 2412 bidiClass); 2413 } 2414 2415 BidiDirection dir = bidiClass == BidiClass::RightToLeft || 2416 bidiClass == BidiClass::RightToLeftArabic 2417 ? BidiDirection::RTL 2418 : BidiDirection::LTR; 2419 aprocessor.SetText(runVisualText.get(), length, dir); 2420 2421 if (aMode == MODE_DRAW) { 2422 aprocessor.DrawText(0); 2423 } 2424 2425 if (!aWidth && !aPosResolve) { 2426 return; 2427 } 2428 2429 nscoord width = aprocessor.GetWidth(); 2430 2431 for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) { 2432 nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve]; 2433 if (posResolve->visualLeftTwips != kNotFound) { 2434 continue; 2435 } 2436 if (0 <= posResolve->logicalIndex && length > posResolve->logicalIndex) { 2437 posResolve->visualIndex = 0; 2438 posResolve->visualLeftTwips = 0; 2439 posResolve->visualWidth = width; 2440 } 2441 } 2442 2443 if (aWidth) { 2444 *aWidth = width; 2445 } 2446 } 2447 2448 class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final 2449 : public nsBidiPresUtils::BidiProcessor { 2450 public: 2451 typedef gfx::DrawTarget DrawTarget; 2452 2453 nsIRenderingContextBidiProcessor(gfxContext* aCtx, 2454 DrawTarget* aTextRunConstructionDrawTarget, 2455 nsFontMetrics* aFontMetrics, 2456 const nsPoint& aPt) 2457 : mCtx(aCtx), 2458 mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget), 2459 mFontMetrics(aFontMetrics), 2460 mPt(aPt), 2461 mText(nullptr), 2462 mLength(0) {} 2463 2464 ~nsIRenderingContextBidiProcessor() { mFontMetrics->SetTextRunRTL(false); } 2465 2466 virtual void SetText(const char16_t* aText, int32_t aLength, 2467 BidiDirection aDirection) override { 2468 mFontMetrics->SetTextRunRTL(aDirection == BidiDirection::RTL); 2469 mText = aText; 2470 mLength = aLength; 2471 } 2472 2473 virtual nscoord GetWidth() override { 2474 return nsLayoutUtils::AppUnitWidthOfString(mText, mLength, *mFontMetrics, 2475 mTextRunConstructionDrawTarget); 2476 } 2477 2478 virtual void DrawText(nscoord aIOffset) override { 2479 nsPoint pt(mPt); 2480 if (mFontMetrics->GetVertical()) { 2481 pt.y += aIOffset; 2482 } else { 2483 pt.x += aIOffset; 2484 } 2485 mFontMetrics->DrawString(mText, mLength, pt.x, pt.y, mCtx, 2486 mTextRunConstructionDrawTarget); 2487 } 2488 2489 private: 2490 gfxContext* mCtx; 2491 DrawTarget* mTextRunConstructionDrawTarget; 2492 nsFontMetrics* mFontMetrics; 2493 nsPoint mPt; 2494 const char16_t* mText; 2495 int32_t mLength; 2496 }; 2497 2498 nsresult nsBidiPresUtils::ProcessTextForRenderingContext( 2499 const char16_t* aText, int32_t aLength, BidiEmbeddingLevel aBaseLevel, 2500 nsPresContext* aPresContext, gfxContext& aRenderingContext, 2501 DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics& aFontMetrics, 2502 Mode aMode, nscoord aX, nscoord aY, nsBidiPositionResolve* aPosResolve, 2503 int32_t aPosResolveCount, nscoord* aWidth) { 2504 nsIRenderingContextBidiProcessor processor(&aRenderingContext, 2505 aTextRunConstructionDrawTarget, 2506 &aFontMetrics, nsPoint(aX, aY)); 2507 nsDependentSubstring text(aText, aLength); 2508 auto separatorIndex = text.FindCharInSet(kSeparators); 2509 if (separatorIndex == kNotFound) { 2510 return ProcessText(text.BeginReading(), text.Length(), aBaseLevel, 2511 aPresContext, processor, aMode, aPosResolve, 2512 aPosResolveCount, aWidth, aPresContext->BidiEngine()); 2513 } 2514 2515 // We need to replace any block or segment separators with space for bidi 2516 // processing, so make a local copy. 2517 nsAutoString localText(text); 2518 ReplaceSeparators(localText, separatorIndex); 2519 return ProcessText(localText.BeginReading(), localText.Length(), aBaseLevel, 2520 aPresContext, processor, aMode, aPosResolve, 2521 aPosResolveCount, aWidth, aPresContext->BidiEngine()); 2522 } 2523 2524 /* static */ 2525 BidiEmbeddingLevel nsBidiPresUtils::BidiLevelFromStyle( 2526 ComputedStyle* aComputedStyle) { 2527 if (aComputedStyle->StyleTextReset()->mUnicodeBidi == 2528 StyleUnicodeBidi::Plaintext) { 2529 return BidiEmbeddingLevel::DefaultLTR(); 2530 } 2531 2532 if (aComputedStyle->StyleVisibility()->mDirection == StyleDirection::Rtl) { 2533 return BidiEmbeddingLevel::RTL(); 2534 } 2535 2536 return BidiEmbeddingLevel::LTR(); 2537 }