nsTextFrame.cpp (468601B)
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 /* rendering object for textual content of elements */ 8 9 #include "nsTextFrame.h" 10 11 #include <algorithm> 12 #include <limits> 13 14 #include "MathMLTextRunFactory.h" 15 #include "PresShellInlines.h" 16 #include "TextDrawTarget.h" 17 #include "gfx2DGlue.h" 18 #include "gfxContext.h" 19 #include "gfxUtils.h" 20 #include "mozilla/Attributes.h" 21 #include "mozilla/BinarySearch.h" 22 #include "mozilla/CaretAssociationHint.h" 23 #include "mozilla/ComputedStyle.h" 24 #include "mozilla/DebugOnly.h" 25 #include "mozilla/IntegerRange.h" 26 #include "mozilla/Likely.h" 27 #include "mozilla/MathAlgorithms.h" 28 #include "mozilla/PodOperations.h" 29 #include "mozilla/PresShell.h" 30 #include "mozilla/SVGTextFrame.h" 31 #include "mozilla/ServoStyleSet.h" 32 #include "mozilla/StaticPrefs_layout.h" 33 #include "mozilla/StaticPresData.h" 34 #include "mozilla/TextEditor.h" 35 #include "mozilla/TextEvents.h" 36 #include "mozilla/TextUtils.h" 37 #include "mozilla/dom/CharacterDataBuffer.h" 38 #include "mozilla/dom/PerformanceMainThread.h" 39 #include "mozilla/gfx/2D.h" 40 #include "mozilla/intl/Bidi.h" 41 #include "mozilla/intl/Segmenter.h" 42 #include "mozilla/intl/UnicodeProperties.h" 43 #include "mozilla/widget/ThemeDrawing.h" 44 #include "nsBlockFrame.h" 45 #include "nsCOMPtr.h" 46 #include "nsCSSColorUtils.h" 47 #include "nsCSSFrameConstructor.h" 48 #include "nsCSSPseudoElements.h" 49 #include "nsCSSRendering.h" 50 #include "nsCompatibility.h" 51 #include "nsContentUtils.h" 52 #include "nsCoord.h" 53 #include "nsDisplayList.h" 54 #include "nsFirstLetterFrame.h" 55 #include "nsFontMetrics.h" 56 #include "nsFrameSelection.h" 57 #include "nsGkAtoms.h" 58 #include "nsIContent.h" 59 #include "nsIFrame.h" 60 #include "nsIFrameInlines.h" 61 #include "nsIMathMLFrame.h" 62 #include "nsLayoutUtils.h" 63 #include "nsLineBreaker.h" 64 #include "nsLineLayout.h" 65 #include "nsPlaceholderFrame.h" 66 #include "nsPresContext.h" 67 #include "nsRange.h" 68 #include "nsRubyFrame.h" 69 #include "nsSplittableFrame.h" 70 #include "nsString.h" 71 #include "nsStyleConsts.h" 72 #include "nsStyleStruct.h" 73 #include "nsStyleStructInlines.h" 74 #include "nsStyleUtil.h" 75 #include "nsTArray.h" 76 #include "nsTextFrameUtils.h" 77 #include "nsTextPaintStyle.h" 78 #include "nsTextRunTransformations.h" 79 #include "nsUnicharUtils.h" 80 #include "nsUnicodeProperties.h" 81 #ifdef ACCESSIBILITY 82 # include "nsAccessibilityService.h" 83 #endif 84 85 #include "mozilla/LookAndFeel.h" 86 #include "mozilla/ProfilerLabels.h" 87 #include "mozilla/UniquePtr.h" 88 #include "mozilla/dom/Element.h" 89 #include "mozilla/gfx/DrawTargetRecording.h" 90 #include "nsPrintfCString.h" 91 92 #ifdef DEBUG 93 # undef NOISY_REFLOW 94 # undef NOISY_TRIM 95 #else 96 # undef NOISY_REFLOW 97 # undef NOISY_TRIM 98 #endif 99 100 #ifdef DrawText 101 # undef DrawText 102 #endif 103 104 using namespace mozilla; 105 using namespace mozilla::dom; 106 using namespace mozilla::gfx; 107 108 static bool NeedsToMaskPassword(const nsTextFrame* aFrame) { 109 MOZ_ASSERT(aFrame); 110 MOZ_ASSERT(aFrame->GetContent()); 111 if (!aFrame->GetContent()->HasFlag(NS_MAYBE_MASKED)) { 112 return false; 113 } 114 const nsIFrame* frame = 115 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::TextInput); 116 MOZ_ASSERT(frame, "How do we have a masked text node without a text input?"); 117 return !frame || !frame->GetContent()->AsElement()->State().HasState( 118 ElementState::REVEALED); 119 } 120 121 namespace mozilla { 122 123 bool TextAutospace::ShouldSuppressLetterNumeralSpacing(const nsIFrame* aFrame) { 124 const auto wm = aFrame->GetWritingMode(); 125 if (wm.IsVertical() && !wm.IsVerticalSideways() && 126 aFrame->StyleVisibility()->mTextOrientation == 127 StyleTextOrientation::Upright) { 128 // The characters are in vertical writing mode with forced upright glyph 129 // orientation. 130 return true; 131 } 132 if (aFrame->Style()->IsTextCombined()) { 133 // The characters have combined forced upright glyph orientation. 134 return true; 135 } 136 if (aFrame->StyleText()->mTextTransform & StyleTextTransform::FULL_WIDTH) { 137 // The characters are transformed to full-width, so non-ideographic 138 // letters/numerals look like ideograph letter/numerals. 139 return true; 140 } 141 return false; 142 } 143 144 bool TextAutospace::Enabled(const StyleTextAutospace& aStyleTextAutospace, 145 const nsTextFrame* aFrame) { 146 if (aStyleTextAutospace == StyleTextAutospace::NO_AUTOSPACE) { 147 return false; 148 } 149 150 if (aStyleTextAutospace == StyleTextAutospace::AUTO) { 151 // 'text-autospace: auto' is UA-defined. Currently, WebKit parses `auto` but 152 // does not add spacing; Blink does not parse 'auto' (treated as invalid). 153 // To align with other engines, we treat 'auto' the same as a no-op. 154 return false; 155 } 156 157 if (ShouldSuppressLetterNumeralSpacing(aFrame)) { 158 // If we suppress the spacing for aFrame, ideograph-alpha or 159 // ideograph-numeric boundaries cannot occur. 160 return false; 161 } 162 163 if (NeedsToMaskPassword(aFrame)) { 164 // Don't allow autospacing in masked password fields, as it could reveal 165 // hints about the types of characters present. 166 return false; 167 } 168 169 return true; 170 } 171 172 TextAutospace::TextAutospace(const StyleTextAutospace& aStyleTextAutospace, 173 nscoord aInterScriptSpacing) 174 : mBoundarySet(InitBoundarySet(aStyleTextAutospace)), 175 mInterScriptSpacing(aInterScriptSpacing) {} 176 177 bool TextAutospace::ShouldApplySpacing(CharClass aPrevClass, 178 CharClass aCurrClass) const { 179 const EnumSet<CharClass> classes{aPrevClass, aCurrClass}; 180 if (mBoundarySet.contains(Boundary::IdeographAlpha)) { 181 constexpr EnumSet<CharClass> kIdeographAlphaMask{ 182 CharClass::Ideograph, CharClass::NonIdeographicLetter}; 183 if (classes == kIdeographAlphaMask) { 184 return true; 185 } 186 } 187 188 if (mBoundarySet.contains(Boundary::IdeographNumeric)) { 189 constexpr EnumSet<CharClass> kIdeographNumericMask{ 190 CharClass::Ideograph, CharClass::NonIdeographicNumeral}; 191 if (classes == kIdeographNumericMask) { 192 return true; 193 } 194 } 195 196 return false; 197 } 198 199 bool TextAutospace::IsIdeograph(char32_t aChar) { 200 // All characters in the range of U+3041 to U+30FF, except those that belong 201 // to Unicode Punctuation [P*] general category. 202 if (0x3041 <= aChar && aChar <= 0x30FF) { 203 return !intl::UnicodeProperties::IsPunctuation(aChar); 204 } 205 206 // CJK Strokes (U+31C0 to U+31EF) and Katakana Phonetic Extensions (U+31F0 to 207 // U+31FF). 208 if (0x31C0 <= aChar && aChar <= 0x31FF) { 209 return true; 210 } 211 212 // All characters that have the Han script property. 213 if (intl::UnicodeProperties::GetScriptCode(aChar) == intl::Script::HAN) { 214 return true; 215 } 216 217 return false; 218 } 219 220 TextAutospace::CharClass TextAutospace::GetCharClass(char32_t aChar) { 221 if (IsAsciiAlpha(aChar)) { 222 return CharClass::NonIdeographicLetter; 223 } 224 225 if (IsAsciiDigit(aChar)) { 226 return CharClass::NonIdeographicNumeral; 227 } 228 229 if (IsIdeograph(aChar)) { 230 return CharClass::Ideograph; 231 } 232 233 // From now on, aChar is *not* an ideograph. 234 if (intl::UnicodeProperties::IsCombiningMark(aChar)) { 235 // Do not treat combining mark as letter. 236 return CharClass::CombiningMark; 237 } 238 239 if (intl::UnicodeProperties::IsLetter(aChar) && 240 !intl::UnicodeProperties::IsEastAsianFullWidth(aChar)) { 241 return CharClass::NonIdeographicLetter; 242 } 243 244 if (intl::UnicodeProperties::CharType(aChar) == 245 intl::GeneralCategory::Decimal_Number) { 246 if (!intl::UnicodeProperties::IsEastAsianFullWidth(aChar)) { 247 return CharClass::NonIdeographicNumeral; 248 } 249 } 250 251 return CharClass::Other; 252 } 253 254 TextAutospace::BoundarySet TextAutospace::InitBoundarySet( 255 const StyleTextAutospace& aStyleTextAutospace) const { 256 if (aStyleTextAutospace == StyleTextAutospace::NORMAL) { 257 return {Boundary::IdeographAlpha, Boundary::IdeographNumeric}; 258 } 259 260 if (aStyleTextAutospace == StyleTextAutospace::IDEOGRAPH_ALPHA) { 261 return {Boundary::IdeographAlpha}; 262 } 263 264 if (aStyleTextAutospace == StyleTextAutospace::IDEOGRAPH_NUMERIC) { 265 return {Boundary::IdeographNumeric}; 266 } 267 268 return {}; 269 } 270 271 } // namespace mozilla 272 273 struct TabWidth { 274 TabWidth(uint32_t aOffset, uint32_t aWidth) 275 : mOffset(aOffset), mWidth(float(aWidth)) {} 276 277 uint32_t mOffset; // DOM offset relative to the current frame's offset. 278 float mWidth; // extra space to be added at this position (in app units) 279 }; 280 281 struct nsTextFrame::TabWidthStore { 282 explicit TabWidthStore(int32_t aValidForContentOffset) 283 : mLimit(0), mValidForContentOffset(aValidForContentOffset) {} 284 285 // Apply tab widths to the aSpacing array, which corresponds to characters 286 // beginning at aOffset and has length aLength. (Width records outside this 287 // range will be ignored.) 288 void ApplySpacing(gfxTextRun::PropertyProvider::Spacing* aSpacing, 289 uint32_t aOffset, uint32_t aLength); 290 291 // Offset up to which tabs have been measured; positions beyond this have not 292 // been calculated yet but may be appended if needed later. It's a DOM 293 // offset relative to the current frame's offset. 294 uint32_t mLimit; 295 296 // Need to recalc tab offsets if frame content offset differs from this. 297 int32_t mValidForContentOffset; 298 299 // A TabWidth record for each tab character measured so far. 300 nsTArray<TabWidth> mWidths; 301 }; 302 303 namespace { 304 305 struct TabwidthAdaptor { 306 const nsTArray<TabWidth>& mWidths; 307 explicit TabwidthAdaptor(const nsTArray<TabWidth>& aWidths) 308 : mWidths(aWidths) {} 309 uint32_t operator[](size_t aIdx) const { return mWidths[aIdx].mOffset; } 310 }; 311 312 } // namespace 313 314 void nsTextFrame::TabWidthStore::ApplySpacing( 315 gfxTextRun::PropertyProvider::Spacing* aSpacing, uint32_t aOffset, 316 uint32_t aLength) { 317 size_t i = 0; 318 const size_t len = mWidths.Length(); 319 320 // If aOffset is non-zero, do a binary search to find where to start 321 // processing the tab widths, in case the list is really long. (See bug 322 // 953247.) 323 // We need to start from the first entry where mOffset >= aOffset. 324 if (aOffset > 0) { 325 mozilla::BinarySearch(TabwidthAdaptor(mWidths), 0, len, aOffset, &i); 326 } 327 328 uint32_t limit = aOffset + aLength; 329 while (i < len) { 330 const TabWidth& tw = mWidths[i]; 331 if (tw.mOffset >= limit) { 332 break; 333 } 334 aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth; 335 i++; 336 } 337 } 338 339 NS_DECLARE_FRAME_PROPERTY_DELETABLE(TabWidthProperty, 340 nsTextFrame::TabWidthStore) 341 342 NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(OffsetToFrameProperty, nsTextFrame) 343 344 NS_DECLARE_FRAME_PROPERTY_RELEASABLE(UninflatedTextRunProperty, gfxTextRun) 345 346 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float) 347 348 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(HangableWhitespaceProperty, nscoord) 349 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TrimmableWhitespaceProperty, 350 gfxTextRun::TrimmableWS) 351 352 struct nsTextFrame::PaintTextSelectionParams : nsTextFrame::PaintTextParams { 353 Point textBaselinePt; 354 PropertyProvider* provider = nullptr; 355 Range contentRange; 356 nsTextPaintStyle* textPaintStyle = nullptr; 357 Range glyphRange; 358 explicit PaintTextSelectionParams(const PaintTextParams& aParams) 359 : PaintTextParams(aParams) {} 360 }; 361 362 struct nsTextFrame::DrawTextRunParams { 363 gfxContext* context; 364 mozilla::gfx::PaletteCache& paletteCache; 365 PropertyProvider* provider = nullptr; 366 gfxFloat* advanceWidth = nullptr; 367 mozilla::SVGContextPaint* contextPaint = nullptr; 368 DrawPathCallbacks* callbacks = nullptr; 369 nscolor textColor = NS_RGBA(0, 0, 0, 0); 370 nscolor textStrokeColor = NS_RGBA(0, 0, 0, 0); 371 nsAtom* fontPalette = nullptr; 372 float textStrokeWidth = 0.0f; 373 bool drawSoftHyphen = false; 374 bool hasTextShadow = false; 375 bool paintingShadows = false; 376 DrawTextRunParams(gfxContext* aContext, 377 mozilla::gfx::PaletteCache& aPaletteCache) 378 : context(aContext), paletteCache(aPaletteCache) {} 379 }; 380 381 struct nsTextFrame::ClipEdges { 382 ClipEdges(const nsIFrame* aFrame, const nsPoint& aToReferenceFrame, 383 nscoord aVisIStartEdge, nscoord aVisIEndEdge) { 384 nsRect r = aFrame->ScrollableOverflowRect() + aToReferenceFrame; 385 if (aFrame->GetWritingMode().IsVertical()) { 386 mVisIStart = aVisIStartEdge > 0 ? r.y + aVisIStartEdge : nscoord_MIN; 387 mVisIEnd = aVisIEndEdge > 0 388 ? std::max(r.YMost() - aVisIEndEdge, mVisIStart) 389 : nscoord_MAX; 390 } else { 391 mVisIStart = aVisIStartEdge > 0 ? r.x + aVisIStartEdge : nscoord_MIN; 392 mVisIEnd = aVisIEndEdge > 0 393 ? std::max(r.XMost() - aVisIEndEdge, mVisIStart) 394 : nscoord_MAX; 395 } 396 } 397 398 void Intersect(nscoord* aVisIStart, nscoord* aVisISize) const { 399 nscoord end = *aVisIStart + *aVisISize; 400 *aVisIStart = std::max(*aVisIStart, mVisIStart); 401 *aVisISize = std::max(std::min(end, mVisIEnd) - *aVisIStart, 0); 402 } 403 404 nscoord mVisIStart; 405 nscoord mVisIEnd; 406 }; 407 408 struct nsTextFrame::DrawTextParams : nsTextFrame::DrawTextRunParams { 409 Point framePt; 410 LayoutDeviceRect dirtyRect; 411 const nsTextPaintStyle* textStyle = nullptr; 412 const ClipEdges* clipEdges = nullptr; 413 const nscolor* decorationOverrideColor = nullptr; 414 Range glyphRange; 415 DrawTextParams(gfxContext* aContext, 416 mozilla::gfx::PaletteCache& aPaletteCache) 417 : DrawTextRunParams(aContext, aPaletteCache) {} 418 }; 419 420 struct nsTextFrame::PaintShadowParams { 421 gfxTextRun::Range range; 422 LayoutDeviceRect dirtyRect; 423 Point framePt; 424 Point textBaselinePt; 425 gfxContext* context; 426 DrawPathCallbacks* callbacks = nullptr; 427 nscolor foregroundColor = NS_RGBA(0, 0, 0, 0); 428 const ClipEdges* clipEdges = nullptr; 429 PropertyProvider* provider = nullptr; 430 nscoord leftSideOffset = 0; 431 explicit PaintShadowParams(const PaintTextParams& aParams) 432 : dirtyRect(aParams.dirtyRect), 433 framePt(aParams.framePt), 434 context(aParams.context) {} 435 }; 436 437 /** 438 * A glyph observer for the change of a font glyph in a text run. 439 * 440 * This is stored in {Simple, Complex}TextRunUserData. 441 */ 442 class GlyphObserver final : public gfxFont::GlyphChangeObserver { 443 public: 444 GlyphObserver(gfxFont* aFont, gfxTextRun* aTextRun) 445 : gfxFont::GlyphChangeObserver(aFont), mTextRun(aTextRun) { 446 MOZ_ASSERT(aTextRun->GetUserData()); 447 } 448 void NotifyGlyphsChanged() override; 449 450 private: 451 gfxTextRun* mTextRun; 452 }; 453 454 static const nsFrameState TEXT_REFLOW_FLAGS = 455 TEXT_FIRST_LETTER | TEXT_START_OF_LINE | TEXT_END_OF_LINE | 456 TEXT_HYPHEN_BREAK | TEXT_TRIMMED_TRAILING_WHITESPACE | 457 TEXT_JUSTIFICATION_ENABLED | TEXT_HAS_NONCOLLAPSED_CHARACTERS | 458 TEXT_SELECTION_UNDERLINE_OVERFLOWED | TEXT_NO_RENDERED_GLYPHS; 459 460 static const nsFrameState TEXT_WHITESPACE_FLAGS = 461 TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE; 462 463 /* 464 * Some general notes 465 * 466 * Text frames delegate work to gfxTextRun objects. The gfxTextRun object 467 * transforms text to positioned glyphs. It can report the geometry of the 468 * glyphs and paint them. Text frames configure gfxTextRuns by providing text, 469 * spacing, language, and other information. 470 * 471 * A gfxTextRun can cover more than one DOM text node. This is necessary to 472 * get kerning, ligatures and shaping for text that spans multiple text nodes 473 * but is all the same font. 474 * 475 * The userdata for a gfxTextRun object can be: 476 * 477 * - A nsTextFrame* in the case a text run maps to only one flow. In this 478 * case, the textrun's user data pointer is a pointer to mStartFrame for that 479 * flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength is the 480 * length of the text node. 481 * 482 * - A SimpleTextRunUserData in the case a text run maps to one flow, but we 483 * still have to keep a list of glyph observers. 484 * 485 * - A ComplexTextRunUserData in the case a text run maps to multiple flows, 486 * but we need to keep a list of glyph observers. 487 * 488 * - A TextRunUserData in the case a text run maps multiple flows, but it 489 * doesn't have any glyph observer for changes in SVG fonts. 490 * 491 * You can differentiate between the four different cases with the 492 * IsSimpleFlow and MightHaveGlyphChanges flags. 493 * 494 * We go to considerable effort to make sure things work even if in-flow 495 * siblings have different ComputedStyles (i.e., first-letter and first-line). 496 * 497 * Our convention is that unsigned integer character offsets are offsets into 498 * the transformed string. Signed integer character offsets are offsets into 499 * the DOM string. 500 * 501 * XXX currently we don't handle hyphenated breaks between text frames where the 502 * hyphen occurs at the end of the first text frame, e.g. 503 * <b>Kit­</b>ty 504 */ 505 506 /** 507 * This is our user data for the textrun, when textRun->GetFlags2() has 508 * IsSimpleFlow set, and also MightHaveGlyphChanges. 509 * 510 * This allows having an array of observers if there are fonts whose glyphs 511 * might change, but also avoid allocation in the simple case that there aren't. 512 */ 513 struct SimpleTextRunUserData { 514 nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers; 515 nsTextFrame* mFrame; 516 explicit SimpleTextRunUserData(nsTextFrame* aFrame) : mFrame(aFrame) {} 517 }; 518 519 /** 520 * We use an array of these objects to record which text frames 521 * are associated with the textrun. mStartFrame is the start of a list of 522 * text frames. Some sequence of its continuations are covered by the textrun. 523 * A content textnode can have at most one TextRunMappedFlow associated with it 524 * for a given textrun. 525 * 526 * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to 527 * obtain the offset into the before-transformation text of the textrun. It can 528 * be positive (when a text node starts in the middle of a text run) or negative 529 * (when a text run starts in the middle of a text node). Of course it can also 530 * be zero. 531 */ 532 struct TextRunMappedFlow { 533 nsTextFrame* mStartFrame; 534 int32_t mDOMOffsetToBeforeTransformOffset; 535 // The text mapped starts at mStartFrame->GetContentOffset() and is this long 536 uint32_t mContentLength; 537 }; 538 539 /** 540 * This is the type in the gfxTextRun's userdata field in the common case that 541 * the text run maps to multiple flows, but no fonts have been found with 542 * animatable glyphs. 543 * 544 * This way, we avoid allocating and constructing the extra nsTArray. 545 */ 546 struct TextRunUserData { 547 #ifdef DEBUG 548 TextRunMappedFlow* mMappedFlows; 549 #endif 550 uint32_t mMappedFlowCount; 551 uint32_t mLastFlowIndex; 552 }; 553 554 /** 555 * This is our user data for the textrun, when textRun->GetFlags2() does not 556 * have IsSimpleFlow set and has the MightHaveGlyphChanges flag. 557 */ 558 struct ComplexTextRunUserData : public TextRunUserData { 559 nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers; 560 }; 561 562 static TextRunUserData* CreateUserData(uint32_t aMappedFlowCount) { 563 TextRunUserData* data = static_cast<TextRunUserData*>(moz_xmalloc( 564 sizeof(TextRunUserData) + aMappedFlowCount * sizeof(TextRunMappedFlow))); 565 #ifdef DEBUG 566 data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1); 567 #endif 568 data->mMappedFlowCount = aMappedFlowCount; 569 data->mLastFlowIndex = 0; 570 return data; 571 } 572 573 static void DestroyUserData(TextRunUserData* aUserData) { 574 if (aUserData) { 575 free(aUserData); 576 } 577 } 578 579 static ComplexTextRunUserData* CreateComplexUserData( 580 uint32_t aMappedFlowCount) { 581 ComplexTextRunUserData* data = static_cast<ComplexTextRunUserData*>( 582 moz_xmalloc(sizeof(ComplexTextRunUserData) + 583 aMappedFlowCount * sizeof(TextRunMappedFlow))); 584 new (data) ComplexTextRunUserData(); 585 #ifdef DEBUG 586 data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1); 587 #endif 588 data->mMappedFlowCount = aMappedFlowCount; 589 data->mLastFlowIndex = 0; 590 return data; 591 } 592 593 static void DestroyComplexUserData(ComplexTextRunUserData* aUserData) { 594 if (aUserData) { 595 aUserData->~ComplexTextRunUserData(); 596 free(aUserData); 597 } 598 } 599 600 static void DestroyTextRunUserData(gfxTextRun* aTextRun) { 601 MOZ_ASSERT(aTextRun->GetUserData()); 602 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { 603 if (aTextRun->GetFlags2() & 604 nsTextFrameUtils::Flags::MightHaveGlyphChanges) { 605 delete static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData()); 606 } 607 } else { 608 if (aTextRun->GetFlags2() & 609 nsTextFrameUtils::Flags::MightHaveGlyphChanges) { 610 DestroyComplexUserData( 611 static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData())); 612 } else { 613 DestroyUserData(static_cast<TextRunUserData*>(aTextRun->GetUserData())); 614 } 615 } 616 aTextRun->ClearFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges); 617 aTextRun->SetUserData(nullptr); 618 } 619 620 static TextRunMappedFlow* GetMappedFlows(const gfxTextRun* aTextRun) { 621 MOZ_ASSERT(aTextRun->GetUserData(), "UserData must exist."); 622 MOZ_ASSERT(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow), 623 "The method should not be called for simple flows."); 624 TextRunMappedFlow* flows; 625 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) { 626 flows = reinterpret_cast<TextRunMappedFlow*>( 627 static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) + 1); 628 } else { 629 flows = reinterpret_cast<TextRunMappedFlow*>( 630 static_cast<TextRunUserData*>(aTextRun->GetUserData()) + 1); 631 } 632 MOZ_ASSERT( 633 static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == 634 flows, 635 "GetMappedFlows should return the same pointer as mMappedFlows."); 636 return flows; 637 } 638 639 /** 640 * These are utility functions just for helping with the complexity related with 641 * the text runs user data. 642 */ 643 static nsTextFrame* GetFrameForSimpleFlow(const gfxTextRun* aTextRun) { 644 MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow, 645 "Not so simple flow?"); 646 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) { 647 return static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())->mFrame; 648 } 649 650 return static_cast<nsTextFrame*>(aTextRun->GetUserData()); 651 } 652 653 /** 654 * Remove |aTextRun| from the frame continuation chain starting at 655 * |aStartContinuation| if non-null, otherwise starting at |aFrame|. 656 * Unmark |aFrame| as a text run owner if it's the frame we start at. 657 * Return true if |aStartContinuation| is non-null and was found 658 * in the next-continuation chain of |aFrame|. 659 */ 660 static bool ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun, 661 nsTextFrame* aStartContinuation, 662 nsFrameState aWhichTextRunState) { 663 MOZ_ASSERT(aFrame, "null frame"); 664 MOZ_ASSERT(!aStartContinuation || 665 (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || 666 aStartContinuation->GetTextRun(nsTextFrame::eInflated) == 667 aTextRun) || 668 (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || 669 aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == 670 aTextRun), 671 "wrong aStartContinuation for this text run"); 672 673 if (!aStartContinuation || aStartContinuation == aFrame) { 674 aFrame->RemoveStateBits(aWhichTextRunState); 675 } else { 676 do { 677 NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame"); 678 aFrame = aFrame->GetNextContinuation(); 679 } while (aFrame && aFrame != aStartContinuation); 680 } 681 bool found = aStartContinuation == aFrame; 682 while (aFrame) { 683 NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame"); 684 if (!aFrame->RemoveTextRun(aTextRun)) { 685 break; 686 } 687 aFrame = aFrame->GetNextContinuation(); 688 } 689 690 MOZ_ASSERT(!found || aStartContinuation, "how did we find null?"); 691 return found; 692 } 693 694 /** 695 * Kill all references to |aTextRun| starting at |aStartContinuation|. 696 * It could be referenced by any of its owners, and all their in-flows. 697 * If |aStartContinuation| is null then process all userdata frames 698 * and their continuations. 699 * @note the caller is expected to take care of possibly destroying the 700 * text run if all userdata frames were reset (userdata is deallocated 701 * by this function though). The caller can detect this has occured by 702 * checking |aTextRun->GetUserData() == nullptr|. 703 */ 704 static void UnhookTextRunFromFrames(gfxTextRun* aTextRun, 705 nsTextFrame* aStartContinuation) { 706 if (!aTextRun->GetUserData()) { 707 return; 708 } 709 710 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { 711 nsTextFrame* userDataFrame = GetFrameForSimpleFlow(aTextRun); 712 nsFrameState whichTextRunState = 713 userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun 714 ? TEXT_IN_TEXTRUN_USER_DATA 715 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA; 716 DebugOnly<bool> found = ClearAllTextRunReferences( 717 userDataFrame, aTextRun, aStartContinuation, whichTextRunState); 718 NS_ASSERTION(!aStartContinuation || found, 719 "aStartContinuation wasn't found in simple flow text run"); 720 if (!userDataFrame->HasAnyStateBits(whichTextRunState)) { 721 DestroyTextRunUserData(aTextRun); 722 } 723 } else { 724 auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData()); 725 TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun); 726 int32_t destroyFromIndex = aStartContinuation ? -1 : 0; 727 for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) { 728 nsTextFrame* userDataFrame = userMappedFlows[i].mStartFrame; 729 nsFrameState whichTextRunState = 730 userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun 731 ? TEXT_IN_TEXTRUN_USER_DATA 732 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA; 733 bool found = ClearAllTextRunReferences( 734 userDataFrame, aTextRun, aStartContinuation, whichTextRunState); 735 if (found) { 736 if (userDataFrame->HasAnyStateBits(whichTextRunState)) { 737 destroyFromIndex = i + 1; 738 } else { 739 destroyFromIndex = i; 740 } 741 aStartContinuation = nullptr; 742 } 743 } 744 NS_ASSERTION(destroyFromIndex >= 0, 745 "aStartContinuation wasn't found in multi flow text run"); 746 if (destroyFromIndex == 0) { 747 DestroyTextRunUserData(aTextRun); 748 } else { 749 userData->mMappedFlowCount = uint32_t(destroyFromIndex); 750 if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) { 751 userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1; 752 } 753 } 754 } 755 } 756 757 static void InvalidateFrameDueToGlyphsChanged(nsIFrame* aFrame) { 758 MOZ_ASSERT(aFrame); 759 760 PresShell* presShell = aFrame->PresShell(); 761 for (nsIFrame* f = aFrame; f; 762 f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { 763 f->InvalidateFrame(); 764 765 // If this is a non-display text frame within SVG <text>, we need 766 // to reflow the SVGTextFrame. (This is similar to reflowing the 767 // SVGTextFrame in response to style changes, in 768 // SVGTextFrame::DidSetComputedStyle.) 769 if (f->IsInSVGTextSubtree() && f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 770 auto* svgTextFrame = static_cast<SVGTextFrame*>( 771 nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::SVGText)); 772 svgTextFrame->ScheduleReflowSVGNonDisplayText(IntrinsicDirty::None); 773 } else { 774 // Theoretically we could just update overflow areas, perhaps using 775 // OverflowChangedTracker, but that would do a bunch of work eagerly that 776 // we should probably do lazily here since there could be a lot 777 // of text frames affected and we'd like to coalesce the work. So that's 778 // not easy to do well. 779 presShell->FrameNeedsReflow(f, IntrinsicDirty::None, NS_FRAME_IS_DIRTY); 780 } 781 } 782 } 783 784 void GlyphObserver::NotifyGlyphsChanged() { 785 if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { 786 InvalidateFrameDueToGlyphsChanged(GetFrameForSimpleFlow(mTextRun)); 787 return; 788 } 789 790 auto data = static_cast<TextRunUserData*>(mTextRun->GetUserData()); 791 TextRunMappedFlow* userMappedFlows = GetMappedFlows(mTextRun); 792 for (uint32_t i = 0; i < data->mMappedFlowCount; ++i) { 793 InvalidateFrameDueToGlyphsChanged(userMappedFlows[i].mStartFrame); 794 } 795 } 796 797 int32_t nsTextFrame::GetContentEnd() const { 798 nsTextFrame* next = GetNextContinuation(); 799 // In case of allocation failure when setting/modifying the text buffer, 800 // it's possible our text might be missing. So we check the buffer length, 801 // in addition to the offset of the next continuation (if any). 802 int32_t bufferLen = CharacterDataBuffer().GetLength(); 803 return next ? std::min(bufferLen, next->GetContentOffset()) : bufferLen; 804 } 805 806 struct FlowLengthProperty { 807 int32_t mStartOffset; 808 // The offset of the next fixed continuation after mStartOffset, or 809 // of the end of the text if there is none 810 int32_t mEndFlowOffset; 811 }; 812 813 int32_t nsTextFrame::GetInFlowContentLength() { 814 if (!HasAnyStateBits(NS_FRAME_IS_BIDI)) { 815 return mContent->TextLength() - mContentOffset; 816 } 817 818 FlowLengthProperty* flowLength = 819 mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) 820 ? static_cast<FlowLengthProperty*>( 821 mContent->GetProperty(nsGkAtoms::flowlength)) 822 : nullptr; 823 MOZ_ASSERT(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength, 824 "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag"); 825 /** 826 * This frame must start inside the cached flow. If the flow starts at 827 * mContentOffset but this frame is empty, logically it might be before the 828 * start of the cached flow. 829 */ 830 if (flowLength && 831 (flowLength->mStartOffset < mContentOffset || 832 (flowLength->mStartOffset == mContentOffset && 833 GetContentEnd() > mContentOffset)) && 834 flowLength->mEndFlowOffset > mContentOffset) { 835 #ifdef DEBUG 836 NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(), 837 "frame crosses fixed continuation boundary"); 838 #endif 839 return flowLength->mEndFlowOffset - mContentOffset; 840 } 841 842 nsTextFrame* nextBidi = LastInFlow()->GetNextContinuation(); 843 int32_t endFlow = 844 nextBidi ? nextBidi->GetContentOffset() : GetContent()->TextLength(); 845 846 if (!flowLength) { 847 flowLength = new FlowLengthProperty; 848 if (NS_FAILED(mContent->SetProperty( 849 nsGkAtoms::flowlength, flowLength, 850 nsINode::DeleteProperty<FlowLengthProperty>))) { 851 delete flowLength; 852 flowLength = nullptr; 853 } else { 854 mContent->SetFlags(NS_HAS_FLOWLENGTH_PROPERTY); 855 } 856 } 857 if (flowLength) { 858 flowLength->mStartOffset = mContentOffset; 859 flowLength->mEndFlowOffset = endFlow; 860 } 861 862 return endFlow - mContentOffset; 863 } 864 865 // Smarter versions of dom::IsSpaceCharacter. 866 // Unicode is really annoying; sometimes a space character isn't whitespace --- 867 // when it combines with another character 868 // So we have several versions of IsSpace for use in different contexts. 869 870 static bool IsSpaceCombiningSequenceTail(const CharacterDataBuffer& aBuffer, 871 uint32_t aPos) { 872 NS_ASSERTION(aPos <= aBuffer.GetLength(), "Bad offset"); 873 if (!aBuffer.Is2b()) { 874 return false; 875 } 876 return nsTextFrameUtils::IsSpaceCombiningSequenceTail( 877 aBuffer.Get2b() + aPos, aBuffer.GetLength() - aPos); 878 } 879 880 // Check whether aPos is a space for CSS 'word-spacing' purposes 881 static bool IsCSSWordSpacingSpace(const CharacterDataBuffer& aBuffer, 882 uint32_t aPos, const nsTextFrame* aFrame, 883 const nsStyleText* aStyleText) { 884 NS_ASSERTION(aPos < aBuffer.GetLength(), "No text for IsSpace!"); 885 886 char16_t ch = aBuffer.CharAt(aPos); 887 switch (ch) { 888 case ' ': 889 case CH_NBSP: 890 return !IsSpaceCombiningSequenceTail(aBuffer, aPos + 1); 891 case '\r': 892 case '\t': 893 return !aStyleText->WhiteSpaceIsSignificant(); 894 case '\n': 895 return !aStyleText->NewlineIsSignificant(aFrame); 896 default: 897 return false; 898 } 899 } 900 901 constexpr char16_t kOghamSpaceMark = 0x1680; 902 903 // Check whether the string aChars/aLength starts with space that's 904 // trimmable according to CSS 'white-space:normal/nowrap'. 905 static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength) { 906 NS_ASSERTION(aLength > 0, "No text for IsSpace!"); 907 908 char16_t ch = *aChars; 909 if (ch == ' ' || ch == kOghamSpaceMark) { 910 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, 911 aLength - 1); 912 } 913 return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r'; 914 } 915 916 // Check whether the character aCh is trimmable according to CSS 917 // 'white-space:normal/nowrap' 918 static bool IsTrimmableSpace(char aCh) { 919 return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r'; 920 } 921 922 static bool IsTrimmableSpace(const CharacterDataBuffer& aBuffer, uint32_t aPos, 923 const nsStyleText* aStyleText, 924 bool aAllowHangingWS = false) { 925 NS_ASSERTION(aPos < aBuffer.GetLength(), "No text for IsSpace!"); 926 927 switch (aBuffer.CharAt(aPos)) { 928 case ' ': 929 case kOghamSpaceMark: 930 return (!aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS) && 931 !IsSpaceCombiningSequenceTail(aBuffer, aPos + 1); 932 case '\n': 933 return !aStyleText->NewlineIsSignificantStyle() && 934 aStyleText->mWhiteSpaceCollapse != 935 StyleWhiteSpaceCollapse::PreserveSpaces; 936 case '\t': 937 case '\r': 938 case '\f': 939 return !aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS; 940 default: 941 return false; 942 } 943 } 944 945 static bool IsSelectionInlineWhitespace(const CharacterDataBuffer& aBuffer, 946 uint32_t aPos) { 947 NS_ASSERTION(aPos < aBuffer.GetLength(), 948 "No text for IsSelectionInlineWhitespace!"); 949 char16_t ch = aBuffer.CharAt(aPos); 950 if (ch == ' ' || ch == CH_NBSP) { 951 return !IsSpaceCombiningSequenceTail(aBuffer, aPos + 1); 952 } 953 return ch == '\t' || ch == '\f'; 954 } 955 956 static bool IsSelectionNewline(const CharacterDataBuffer& aBuffer, 957 uint32_t aPos) { 958 NS_ASSERTION(aPos < aBuffer.GetLength(), "No text for IsSelectionNewline!"); 959 char16_t ch = aBuffer.CharAt(aPos); 960 return ch == '\n' || ch == '\r'; 961 } 962 963 // Count the amount of trimmable whitespace (as per CSS 964 // 'white-space:normal/nowrap') in a character buffer. The first 965 // character is at offset aStartOffset; the maximum number of characters 966 // to check is aLength. aDirection is -1 or 1 depending on whether we should 967 // progress backwards or forwards. 968 static uint32_t GetTrimmableWhitespaceCount(const CharacterDataBuffer& aBuffer, 969 int32_t aStartOffset, 970 int32_t aLength, 971 int32_t aDirection) { 972 if (!aLength) { 973 return 0; 974 } 975 976 int32_t count = 0; 977 if (aBuffer.Is2b()) { 978 const char16_t* str = aBuffer.Get2b() + aStartOffset; 979 int32_t bufferLen = aBuffer.GetLength() - aStartOffset; 980 for (; count < aLength; ++count) { 981 if (!IsTrimmableSpace(str, bufferLen)) { 982 break; 983 } 984 str += aDirection; 985 bufferLen -= aDirection; 986 } 987 } else { 988 const char* str = aBuffer.Get1b() + aStartOffset; 989 for (; count < aLength; ++count) { 990 if (!IsTrimmableSpace(*str)) { 991 break; 992 } 993 str += aDirection; 994 } 995 } 996 return count; 997 } 998 999 static bool IsAllWhitespace(const CharacterDataBuffer& aBuffer, 1000 bool aAllowNewline) { 1001 if (aBuffer.Is2b()) { 1002 return false; 1003 } 1004 int32_t len = aBuffer.GetLength(); 1005 const char* str = aBuffer.Get1b(); 1006 for (int32_t i = 0; i < len; ++i) { 1007 char ch = str[i]; 1008 if (ch == ' ' || ch == '\t' || ch == '\r' || 1009 (ch == '\n' && aAllowNewline)) { 1010 continue; 1011 } 1012 return false; 1013 } 1014 return true; 1015 } 1016 1017 static void ClearObserversFromTextRun(gfxTextRun* aTextRun) { 1018 if (!(aTextRun->GetFlags2() & 1019 nsTextFrameUtils::Flags::MightHaveGlyphChanges)) { 1020 return; 1021 } 1022 1023 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { 1024 static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData()) 1025 ->mGlyphObservers.Clear(); 1026 } else { 1027 static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) 1028 ->mGlyphObservers.Clear(); 1029 } 1030 } 1031 1032 static void CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun) { 1033 if (!aTextRun->GetUserData()) { 1034 return; 1035 } 1036 1037 ClearObserversFromTextRun(aTextRun); 1038 1039 nsTArray<gfxFont*> fontsWithAnimatedGlyphs; 1040 uint32_t numGlyphRuns; 1041 const gfxTextRun::GlyphRun* glyphRuns = aTextRun->GetGlyphRuns(&numGlyphRuns); 1042 for (uint32_t i = 0; i < numGlyphRuns; ++i) { 1043 gfxFont* font = glyphRuns[i].mFont; 1044 if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) { 1045 fontsWithAnimatedGlyphs.AppendElement(font); 1046 } 1047 } 1048 if (fontsWithAnimatedGlyphs.IsEmpty()) { 1049 // NB: Theoretically, we should clear the MightHaveGlyphChanges 1050 // here. That would involve de-allocating the simple user data struct if 1051 // present too, and resetting the pointer to the frame. In practice, I 1052 // don't think worth doing that work here, given the flag's only purpose is 1053 // to distinguish what kind of user data is there. 1054 return; 1055 } 1056 1057 nsTArray<UniquePtr<GlyphObserver>>* observers; 1058 1059 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { 1060 // Swap the frame pointer for a just-allocated SimpleTextRunUserData if 1061 // appropriate. 1062 if (!(aTextRun->GetFlags2() & 1063 nsTextFrameUtils::Flags::MightHaveGlyphChanges)) { 1064 auto frame = static_cast<nsTextFrame*>(aTextRun->GetUserData()); 1065 aTextRun->SetUserData(new SimpleTextRunUserData(frame)); 1066 } 1067 1068 auto data = static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData()); 1069 observers = &data->mGlyphObservers; 1070 } else { 1071 if (!(aTextRun->GetFlags2() & 1072 nsTextFrameUtils::Flags::MightHaveGlyphChanges)) { 1073 auto oldData = static_cast<TextRunUserData*>(aTextRun->GetUserData()); 1074 TextRunMappedFlow* oldMappedFlows = GetMappedFlows(aTextRun); 1075 ComplexTextRunUserData* data = 1076 CreateComplexUserData(oldData->mMappedFlowCount); 1077 TextRunMappedFlow* dataMappedFlows = 1078 reinterpret_cast<TextRunMappedFlow*>(data + 1); 1079 data->mLastFlowIndex = oldData->mLastFlowIndex; 1080 for (uint32_t i = 0; i < oldData->mMappedFlowCount; ++i) { 1081 dataMappedFlows[i] = oldMappedFlows[i]; 1082 } 1083 DestroyUserData(oldData); 1084 aTextRun->SetUserData(data); 1085 } 1086 auto data = static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()); 1087 observers = &data->mGlyphObservers; 1088 } 1089 1090 aTextRun->SetFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges); 1091 1092 observers->SetCapacity(observers->Length() + 1093 fontsWithAnimatedGlyphs.Length()); 1094 for (auto font : fontsWithAnimatedGlyphs) { 1095 observers->AppendElement(MakeUnique<GlyphObserver>(font, aTextRun)); 1096 } 1097 } 1098 1099 /** 1100 * This class accumulates state as we scan a paragraph of text. It detects 1101 * textrun boundaries (changes from text to non-text, hard 1102 * line breaks, and font changes) and builds a gfxTextRun at each boundary. 1103 * It also detects linebreaker run boundaries (changes from text to non-text, 1104 * and hard line breaks) and at each boundary runs the linebreaker to compute 1105 * potential line breaks. It also records actual line breaks to store them in 1106 * the textruns. 1107 */ 1108 class BuildTextRunsScanner { 1109 public: 1110 BuildTextRunsScanner(nsPresContext* aPresContext, DrawTarget* aDrawTarget, 1111 nsIFrame* aLineContainer, 1112 nsTextFrame::TextRunType aWhichTextRun, 1113 bool aDoLineBreaking) 1114 : mDrawTarget(aDrawTarget), 1115 mLineContainer(aLineContainer), 1116 mCommonAncestorWithLastFrame(nullptr), 1117 mMissingFonts(aPresContext->MissingFontRecorder()), 1118 mBidiEnabled(aPresContext->BidiEnabled()), 1119 mStartOfLine(true), 1120 mSkipIncompleteTextRuns(false), 1121 mCanStopOnThisLine(false), 1122 mDoLineBreaking(aDoLineBreaking), 1123 mWhichTextRun(aWhichTextRun), 1124 mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE), 1125 mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) { 1126 ResetRunInfo(); 1127 } 1128 ~BuildTextRunsScanner() { 1129 NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared"); 1130 NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared"); 1131 NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared"); 1132 } 1133 1134 void SetAtStartOfLine() { 1135 mStartOfLine = true; 1136 mCanStopOnThisLine = false; 1137 } 1138 void SetSkipIncompleteTextRuns(bool aSkip) { 1139 mSkipIncompleteTextRuns = aSkip; 1140 } 1141 void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) { 1142 mCommonAncestorWithLastFrame = aFrame; 1143 } 1144 bool CanStopOnThisLine() { return mCanStopOnThisLine; } 1145 nsIFrame* GetCommonAncestorWithLastFrame() { 1146 return mCommonAncestorWithLastFrame; 1147 } 1148 void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) { 1149 if (mCommonAncestorWithLastFrame && 1150 mCommonAncestorWithLastFrame->GetParent() == aFrame) { 1151 mCommonAncestorWithLastFrame = aFrame; 1152 } 1153 } 1154 void ScanFrame(nsIFrame* aFrame); 1155 bool IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun); 1156 void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak); 1157 void FlushLineBreaks(gfxTextRun* aTrailingTextRun); 1158 void ResetRunInfo() { 1159 mLastFrame = nullptr; 1160 mMappedFlows.Clear(); 1161 mLineBreakBeforeFrames.Clear(); 1162 mMaxTextLength = 0; 1163 mDoubleByteText = false; 1164 } 1165 void AccumulateRunInfo(nsTextFrame* aFrame); 1166 /** 1167 * @return null to indicate either textrun construction failed or 1168 * we constructed just a partial textrun to set up linebreaker and other 1169 * state for following textruns. 1170 */ 1171 already_AddRefed<gfxTextRun> BuildTextRunForFrames(void* aTextBuffer); 1172 bool SetupLineBreakerContext(gfxTextRun* aTextRun); 1173 void AssignTextRun(gfxTextRun* aTextRun, float aInflation); 1174 nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex); 1175 void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aTextPtr); 1176 void SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, const void* aTextPtr); 1177 struct FindBoundaryState { 1178 nsIFrame* mStopAtFrame; 1179 nsTextFrame* mFirstTextFrame; 1180 nsTextFrame* mLastTextFrame; 1181 bool mSeenTextRunBoundaryOnLaterLine; 1182 bool mSeenTextRunBoundaryOnThisLine; 1183 bool mSeenSpaceForLineBreakingOnThisLine; 1184 nsTArray<char16_t>& mBuffer; 1185 }; 1186 enum FindBoundaryResult { 1187 FB_CONTINUE, 1188 FB_STOPPED_AT_STOP_FRAME, 1189 FB_FOUND_VALID_TEXTRUN_BOUNDARY 1190 }; 1191 FindBoundaryResult FindBoundaries(nsIFrame* aFrame, 1192 FindBoundaryState* aState); 1193 1194 bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2); 1195 1196 // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame 1197 // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then 1198 // continuations starting from mStartFrame are a sequence of in-flow frames). 1199 struct MappedFlow { 1200 nsTextFrame* mStartFrame; 1201 nsTextFrame* mEndFrame; 1202 // When we consider breaking between elements, the nearest common 1203 // ancestor of the elements containing the characters is the one whose 1204 // CSS 'white-space' property governs. So this records the nearest common 1205 // ancestor of mStartFrame and the previous text frame, or null if there 1206 // was no previous text frame on this line. 1207 nsIFrame* mAncestorControllingInitialBreak; 1208 1209 int32_t GetContentEnd() const { 1210 int32_t bufferLen = mStartFrame->CharacterDataBuffer().GetLength(); 1211 return mEndFrame ? std::min(bufferLen, mEndFrame->GetContentOffset()) 1212 : bufferLen; 1213 } 1214 }; 1215 1216 class BreakSink final : public nsILineBreakSink { 1217 public: 1218 BreakSink(gfxTextRun* aTextRun, DrawTarget* aDrawTarget, 1219 uint32_t aOffsetIntoTextRun) 1220 : mTextRun(aTextRun), 1221 mDrawTarget(aDrawTarget), 1222 mOffsetIntoTextRun(aOffsetIntoTextRun) {} 1223 1224 void SetBreaks(uint32_t aOffset, uint32_t aLength, 1225 uint8_t* aBreakBefore) final { 1226 gfxTextRun::Range range(aOffset + mOffsetIntoTextRun, 1227 aOffset + mOffsetIntoTextRun + aLength); 1228 if (mTextRun->SetPotentialLineBreaks(range, aBreakBefore)) { 1229 // Be conservative and assume that some breaks have been set 1230 mTextRun->ClearFlagBits(nsTextFrameUtils::Flags::NoBreaks); 1231 } 1232 } 1233 1234 void SetCapitalization(uint32_t aOffset, uint32_t aLength, 1235 bool* aCapitalize) final { 1236 MOZ_ASSERT(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed, 1237 "Text run should be transformed!"); 1238 if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) { 1239 nsTransformedTextRun* transformedTextRun = 1240 static_cast<nsTransformedTextRun*>(mTextRun.get()); 1241 transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, 1242 aLength, aCapitalize); 1243 } 1244 } 1245 1246 void Finish(gfxMissingFontRecorder* aMFR) { 1247 if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) { 1248 nsTransformedTextRun* transformedTextRun = 1249 static_cast<nsTransformedTextRun*>(mTextRun.get()); 1250 transformedTextRun->FinishSettingProperties(mDrawTarget, aMFR); 1251 } 1252 // The way nsTransformedTextRun is implemented, its glyph runs aren't 1253 // available until after nsTransformedTextRun::FinishSettingProperties() 1254 // is called. So that's why we defer checking for animated glyphs to here. 1255 CreateObserversForAnimatedGlyphs(mTextRun); 1256 } 1257 1258 RefPtr<gfxTextRun> mTextRun; 1259 DrawTarget* mDrawTarget; 1260 uint32_t mOffsetIntoTextRun; 1261 }; 1262 1263 private: 1264 AutoTArray<MappedFlow, 10> mMappedFlows; 1265 AutoTArray<nsTextFrame*, 50> mLineBreakBeforeFrames; 1266 AutoTArray<UniquePtr<BreakSink>, 10> mBreakSinks; 1267 nsLineBreaker mLineBreaker; 1268 RefPtr<gfxTextRun> mCurrentFramesAllSameTextRun; 1269 DrawTarget* mDrawTarget; 1270 nsIFrame* mLineContainer; 1271 nsTextFrame* mLastFrame; 1272 // The common ancestor of the current frame and the previous leaf frame 1273 // on the line, or null if there was no previous leaf frame. 1274 nsIFrame* mCommonAncestorWithLastFrame; 1275 gfxMissingFontRecorder* mMissingFonts; 1276 // mMaxTextLength is an upper bound on the size of the text in all mapped 1277 // frames The value UINT32_MAX represents overflow; text will be discarded 1278 uint32_t mMaxTextLength; 1279 bool mDoubleByteText; 1280 bool mBidiEnabled; 1281 bool mStartOfLine; 1282 bool mSkipIncompleteTextRuns; 1283 bool mCanStopOnThisLine; 1284 bool mDoLineBreaking; 1285 nsTextFrame::TextRunType mWhichTextRun; 1286 uint8_t mNextRunContextInfo; 1287 uint8_t mCurrentRunContextInfo; 1288 }; 1289 1290 static const nsIFrame* FindLineContainer(const nsIFrame* aFrame) { 1291 while (aFrame && 1292 (aFrame->IsLineParticipant() || aFrame->CanContinueTextRun())) { 1293 aFrame = aFrame->GetParent(); 1294 } 1295 return aFrame; 1296 } 1297 1298 static nsIFrame* FindLineContainer(nsIFrame* aFrame) { 1299 return const_cast<nsIFrame*>( 1300 FindLineContainer(const_cast<const nsIFrame*>(aFrame))); 1301 } 1302 1303 static bool IsLineBreakingWhiteSpace(char16_t aChar) { 1304 // 0x0A (\n) is not handled as white-space by the line breaker, since 1305 // we break before it, if it isn't transformed to a normal space. 1306 // (If we treat it as normal white-space then we'd only break after it.) 1307 // However, it does induce a line break or is converted to a regular 1308 // space, and either way it can be used to bound the region of text 1309 // that needs to be analyzed for line breaking. 1310 return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A; 1311 } 1312 1313 static bool TextContainsLineBreakerWhiteSpace(const void* aText, 1314 uint32_t aLength, 1315 bool aIsDoubleByte) { 1316 if (aIsDoubleByte) { 1317 const char16_t* chars = static_cast<const char16_t*>(aText); 1318 for (uint32_t i = 0; i < aLength; ++i) { 1319 if (IsLineBreakingWhiteSpace(chars[i])) { 1320 return true; 1321 } 1322 } 1323 return false; 1324 } else { 1325 const uint8_t* chars = static_cast<const uint8_t*>(aText); 1326 for (uint32_t i = 0; i < aLength; ++i) { 1327 if (IsLineBreakingWhiteSpace(chars[i])) { 1328 return true; 1329 } 1330 } 1331 return false; 1332 } 1333 } 1334 1335 static nsTextFrameUtils::CompressionMode GetCSSWhitespaceToCompressionMode( 1336 nsTextFrame* aFrame, const nsStyleText* aStyleText) { 1337 switch (aStyleText->mWhiteSpaceCollapse) { 1338 case StyleWhiteSpaceCollapse::Collapse: 1339 return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE; 1340 case StyleWhiteSpaceCollapse::PreserveBreaks: 1341 return nsTextFrameUtils::COMPRESS_WHITESPACE; 1342 case StyleWhiteSpaceCollapse::Preserve: 1343 case StyleWhiteSpaceCollapse::PreserveSpaces: 1344 case StyleWhiteSpaceCollapse::BreakSpaces: 1345 if (!aStyleText->NewlineIsSignificant(aFrame)) { 1346 // If newline is set to be preserved, but then suppressed, 1347 // transform newline to space. 1348 return nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE; 1349 } 1350 return nsTextFrameUtils::COMPRESS_NONE; 1351 } 1352 MOZ_ASSERT_UNREACHABLE("Unknown white-space-collapse value"); 1353 return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE; 1354 } 1355 1356 struct FrameTextTraversal { 1357 FrameTextTraversal() 1358 : mFrameToScan(nullptr), 1359 mOverflowFrameToScan(nullptr), 1360 mScanSiblings(false), 1361 mLineBreakerCanCrossFrameBoundary(false), 1362 mTextRunCanCrossFrameBoundary(false) {} 1363 1364 // These fields identify which frames should be recursively scanned 1365 // The first normal frame to scan (or null, if no such frame should be 1366 // scanned) 1367 nsIFrame* mFrameToScan; 1368 // The first overflow frame to scan (or null, if no such frame should be 1369 // scanned) 1370 nsIFrame* mOverflowFrameToScan; 1371 // Whether to scan the siblings of 1372 // mFrameToDescendInto/mOverflowFrameToDescendInto 1373 bool mScanSiblings; 1374 1375 // These identify the boundaries of the context required for 1376 // line breaking or textrun construction 1377 bool mLineBreakerCanCrossFrameBoundary; 1378 bool mTextRunCanCrossFrameBoundary; 1379 1380 nsIFrame* NextFrameToScan() { 1381 nsIFrame* f; 1382 if (mFrameToScan) { 1383 f = mFrameToScan; 1384 mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr; 1385 } else if (mOverflowFrameToScan) { 1386 f = mOverflowFrameToScan; 1387 mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr; 1388 } else { 1389 f = nullptr; 1390 } 1391 return f; 1392 } 1393 }; 1394 1395 static FrameTextTraversal CanTextCrossFrameBoundary(nsIFrame* aFrame) { 1396 FrameTextTraversal result; 1397 1398 bool continuesTextRun = aFrame->CanContinueTextRun(); 1399 if (aFrame->IsPlaceholderFrame()) { 1400 // placeholders are "invisible", so a text run should be able to span 1401 // across one. But don't descend into the out-of-flow. 1402 result.mLineBreakerCanCrossFrameBoundary = true; 1403 if (continuesTextRun) { 1404 // ... Except for first-letter floats, which are really in-flow 1405 // from the point of view of capitalization etc, so we'd better 1406 // descend into them. But we actually need to break the textrun for 1407 // first-letter floats since things look bad if, say, we try to make a 1408 // ligature across the float boundary. 1409 result.mFrameToScan = 1410 (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame(); 1411 } else { 1412 result.mTextRunCanCrossFrameBoundary = true; 1413 } 1414 } else { 1415 if (continuesTextRun) { 1416 result.mFrameToScan = aFrame->PrincipalChildList().FirstChild(); 1417 result.mOverflowFrameToScan = 1418 aFrame->GetChildList(FrameChildListID::Overflow).FirstChild(); 1419 NS_WARNING_ASSERTION( 1420 !result.mOverflowFrameToScan, 1421 "Scanning overflow inline frames is something we should avoid"); 1422 result.mScanSiblings = true; 1423 result.mTextRunCanCrossFrameBoundary = true; 1424 result.mLineBreakerCanCrossFrameBoundary = true; 1425 } else { 1426 MOZ_ASSERT(!aFrame->IsRubyTextContainerFrame(), 1427 "Shouldn't call this method for ruby text container"); 1428 } 1429 } 1430 return result; 1431 } 1432 1433 BuildTextRunsScanner::FindBoundaryResult BuildTextRunsScanner::FindBoundaries( 1434 nsIFrame* aFrame, FindBoundaryState* aState) { 1435 LayoutFrameType frameType = aFrame->Type(); 1436 if (frameType == LayoutFrameType::RubyTextContainer) { 1437 // Don't stop a text run for ruby text container. We want ruby text 1438 // containers to be skipped, but continue the text run across them. 1439 return FB_CONTINUE; 1440 } 1441 1442 nsTextFrame* textFrame = frameType == LayoutFrameType::Text 1443 ? static_cast<nsTextFrame*>(aFrame) 1444 : nullptr; 1445 if (textFrame) { 1446 if (aState->mLastTextFrame && 1447 textFrame != aState->mLastTextFrame->GetNextInFlow() && 1448 !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) { 1449 aState->mSeenTextRunBoundaryOnThisLine = true; 1450 if (aState->mSeenSpaceForLineBreakingOnThisLine) { 1451 return FB_FOUND_VALID_TEXTRUN_BOUNDARY; 1452 } 1453 } 1454 if (!aState->mFirstTextFrame) { 1455 aState->mFirstTextFrame = textFrame; 1456 } 1457 aState->mLastTextFrame = textFrame; 1458 } 1459 1460 if (aFrame == aState->mStopAtFrame) { 1461 return FB_STOPPED_AT_STOP_FRAME; 1462 } 1463 1464 if (textFrame) { 1465 if (aState->mSeenSpaceForLineBreakingOnThisLine) { 1466 return FB_CONTINUE; 1467 } 1468 const CharacterDataBuffer& characterDataBuffer = 1469 textFrame->CharacterDataBuffer(); 1470 uint32_t start = textFrame->GetContentOffset(); 1471 uint32_t length = textFrame->GetContentLength(); 1472 const void* text; 1473 const nsAtom* language = textFrame->StyleFont()->mLanguage; 1474 if (characterDataBuffer.Is2b()) { 1475 // It is possible that we may end up removing all whitespace in 1476 // a piece of text because of The White Space Processing Rules, 1477 // so we need to transform it before we can check existence of 1478 // such whitespaces. 1479 aState->mBuffer.EnsureLengthAtLeast(length); 1480 nsTextFrameUtils::CompressionMode compression = 1481 GetCSSWhitespaceToCompressionMode(textFrame, textFrame->StyleText()); 1482 uint8_t incomingFlags = 0; 1483 gfxSkipChars skipChars; 1484 nsTextFrameUtils::Flags analysisFlags; 1485 char16_t* bufStart = aState->mBuffer.Elements(); 1486 char16_t* bufEnd = nsTextFrameUtils::TransformText( 1487 characterDataBuffer.Get2b() + start, length, bufStart, compression, 1488 &incomingFlags, &skipChars, &analysisFlags, language); 1489 text = bufStart; 1490 length = bufEnd - bufStart; 1491 } else { 1492 // If the text only contains ASCII characters, it is currently 1493 // impossible that TransformText would remove all whitespaces, 1494 // and thus the check below should return the same result for 1495 // transformed text and original text. So we don't need to try 1496 // transforming it here. 1497 text = static_cast<const void*>(characterDataBuffer.Get1b() + start); 1498 } 1499 if (TextContainsLineBreakerWhiteSpace(text, length, 1500 characterDataBuffer.Is2b())) { 1501 aState->mSeenSpaceForLineBreakingOnThisLine = true; 1502 if (aState->mSeenTextRunBoundaryOnLaterLine) { 1503 return FB_FOUND_VALID_TEXTRUN_BOUNDARY; 1504 } 1505 } 1506 return FB_CONTINUE; 1507 } 1508 1509 FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame); 1510 if (!traversal.mTextRunCanCrossFrameBoundary) { 1511 aState->mSeenTextRunBoundaryOnThisLine = true; 1512 if (aState->mSeenSpaceForLineBreakingOnThisLine) { 1513 return FB_FOUND_VALID_TEXTRUN_BOUNDARY; 1514 } 1515 } 1516 1517 for (nsIFrame* f = traversal.NextFrameToScan(); f; 1518 f = traversal.NextFrameToScan()) { 1519 FindBoundaryResult result = FindBoundaries(f, aState); 1520 if (result != FB_CONTINUE) { 1521 return result; 1522 } 1523 } 1524 1525 if (!traversal.mTextRunCanCrossFrameBoundary) { 1526 aState->mSeenTextRunBoundaryOnThisLine = true; 1527 if (aState->mSeenSpaceForLineBreakingOnThisLine) { 1528 return FB_FOUND_VALID_TEXTRUN_BOUNDARY; 1529 } 1530 } 1531 1532 return FB_CONTINUE; 1533 } 1534 1535 // build text runs for the 200 lines following aForFrame, and stop after that 1536 // when we get a chance. 1537 #define NUM_LINES_TO_BUILD_TEXT_RUNS 200 1538 1539 /** 1540 * General routine for building text runs. This is hairy because of the need 1541 * to build text runs that span content nodes. 1542 * 1543 * @param aContext The gfxContext we're using to construct this text run. 1544 * @param aForFrame The nsTextFrame for which we're building this text run. 1545 * @param aLineContainer the line container containing aForFrame; if null, 1546 * we'll walk the ancestors to find it. It's required to be non-null 1547 * when aForFrameLine is non-null. 1548 * @param aForFrameLine the line containing aForFrame; if null, we'll figure 1549 * out the line (slowly) 1550 * @param aWhichTextRun The type of text run we want to build. If font inflation 1551 * is enabled, this will be eInflated, otherwise it's eNotInflated. 1552 */ 1553 static void BuildTextRuns(DrawTarget* aDrawTarget, nsTextFrame* aForFrame, 1554 nsIFrame* aLineContainer, 1555 const nsLineList::iterator* aForFrameLine, 1556 nsTextFrame::TextRunType aWhichTextRun) { 1557 MOZ_ASSERT(aForFrame, "for no frame?"); 1558 NS_ASSERTION(!aForFrameLine || aLineContainer, "line but no line container"); 1559 1560 nsIFrame* lineContainerChild = aForFrame; 1561 if (!aLineContainer) { 1562 if (aForFrame->IsFloatingFirstLetterChild()) { 1563 lineContainerChild = aForFrame->GetParent()->GetPlaceholderFrame(); 1564 } 1565 aLineContainer = FindLineContainer(lineContainerChild); 1566 } else { 1567 NS_ASSERTION( 1568 (aLineContainer == FindLineContainer(aForFrame) || 1569 (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating())), 1570 "Wrong line container hint"); 1571 } 1572 1573 if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) { 1574 aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML); 1575 if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) { 1576 aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI); 1577 } 1578 } 1579 if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) { 1580 aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT); 1581 } 1582 1583 nsPresContext* presContext = aLineContainer->PresContext(); 1584 bool doLineBreaking = !aForFrame->IsInSVGTextSubtree(); 1585 BuildTextRunsScanner scanner(presContext, aDrawTarget, aLineContainer, 1586 aWhichTextRun, doLineBreaking); 1587 1588 nsBlockFrame* block = do_QueryFrame(aLineContainer); 1589 1590 if (!block) { 1591 nsIFrame* textRunContainer = aLineContainer; 1592 if (aLineContainer->IsRubyTextContainerFrame()) { 1593 textRunContainer = aForFrame; 1594 while (textRunContainer && !textRunContainer->IsRubyTextFrame()) { 1595 textRunContainer = textRunContainer->GetParent(); 1596 } 1597 MOZ_ASSERT(textRunContainer && 1598 textRunContainer->GetParent() == aLineContainer); 1599 } else { 1600 NS_ASSERTION( 1601 !aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(), 1602 "Breakable non-block line containers other than " 1603 "ruby text container is not supported"); 1604 } 1605 // Just loop through all the children of the linecontainer ... it's really 1606 // just one line 1607 scanner.SetAtStartOfLine(); 1608 scanner.SetCommonAncestorWithLastFrame(nullptr); 1609 for (nsIFrame* child : textRunContainer->PrincipalChildList()) { 1610 scanner.ScanFrame(child); 1611 } 1612 // Set mStartOfLine so FlushFrames knows its textrun ends a line 1613 scanner.SetAtStartOfLine(); 1614 scanner.FlushFrames(true, false); 1615 return; 1616 } 1617 1618 // Find the line containing 'lineContainerChild'. 1619 1620 bool isValid = true; 1621 nsBlockInFlowLineIterator backIterator(block, &isValid); 1622 if (aForFrameLine) { 1623 backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine); 1624 } else { 1625 backIterator = 1626 nsBlockInFlowLineIterator(block, lineContainerChild, &isValid); 1627 NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us"); 1628 NS_ASSERTION(backIterator.GetContainer() == block, 1629 "Someone lied to us about the block"); 1630 } 1631 nsBlockFrame::LineIterator startLine = backIterator.GetLine(); 1632 1633 // Find a line where we can start building text runs. We choose the last line 1634 // where: 1635 // -- there is a textrun boundary between the start of the line and the 1636 // start of aForFrame 1637 // -- there is a space between the start of the line and the textrun boundary 1638 // (this is so we can be sure the line breaks will be set properly 1639 // on the textruns we construct). 1640 // The possibly-partial text runs up to and including the first space 1641 // are not reconstructed. We construct partial text runs for that text --- 1642 // for the sake of simplifying the code and feeding the linebreaker --- 1643 // but we discard them instead of assigning them to frames. 1644 // This is a little awkward because we traverse lines in the reverse direction 1645 // but we traverse the frames in each line in the forward direction. 1646 nsBlockInFlowLineIterator forwardIterator = backIterator; 1647 nsIFrame* stopAtFrame = lineContainerChild; 1648 nsTextFrame* nextLineFirstTextFrame = nullptr; 1649 AutoTArray<char16_t, BIG_TEXT_NODE_SIZE> buffer; 1650 bool seenTextRunBoundaryOnLaterLine = false; 1651 bool mayBeginInTextRun = true; 1652 while (true) { 1653 forwardIterator = backIterator; 1654 nsBlockFrame::LineIterator line = backIterator.GetLine(); 1655 if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) { 1656 mayBeginInTextRun = false; 1657 break; 1658 } 1659 1660 BuildTextRunsScanner::FindBoundaryState state = { 1661 stopAtFrame, nullptr, nullptr, bool(seenTextRunBoundaryOnLaterLine), 1662 false, false, buffer}; 1663 nsIFrame* child = line->mFirstChild; 1664 bool foundBoundary = false; 1665 for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) { 1666 BuildTextRunsScanner::FindBoundaryResult result = 1667 scanner.FindBoundaries(child, &state); 1668 if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) { 1669 foundBoundary = true; 1670 break; 1671 } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) { 1672 break; 1673 } 1674 child = child->GetNextSibling(); 1675 } 1676 if (foundBoundary) { 1677 break; 1678 } 1679 if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame && 1680 !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame, 1681 nextLineFirstTextFrame)) { 1682 // Found a usable textrun boundary at the end of the line 1683 if (state.mSeenSpaceForLineBreakingOnThisLine) { 1684 break; 1685 } 1686 seenTextRunBoundaryOnLaterLine = true; 1687 } else if (state.mSeenTextRunBoundaryOnThisLine) { 1688 seenTextRunBoundaryOnLaterLine = true; 1689 } 1690 stopAtFrame = nullptr; 1691 if (state.mFirstTextFrame) { 1692 nextLineFirstTextFrame = state.mFirstTextFrame; 1693 } 1694 } 1695 scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun); 1696 1697 // Now iterate over all text frames starting from the current line. 1698 // First-in-flow text frames will be accumulated into textRunFrames as we go. 1699 // When a text run boundary is required we flush textRunFrames ((re)building 1700 // their gfxTextRuns as necessary). 1701 bool seenStartLine = false; 1702 uint32_t linesAfterStartLine = 0; 1703 do { 1704 nsBlockFrame::LineIterator line = forwardIterator.GetLine(); 1705 if (line->IsBlock()) { 1706 break; 1707 } 1708 line->SetInvalidateTextRuns(false); 1709 scanner.SetAtStartOfLine(); 1710 scanner.SetCommonAncestorWithLastFrame(nullptr); 1711 nsIFrame* child = line->mFirstChild; 1712 for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) { 1713 scanner.ScanFrame(child); 1714 child = child->GetNextSibling(); 1715 } 1716 if (line.get() == startLine.get()) { 1717 seenStartLine = true; 1718 } 1719 if (seenStartLine) { 1720 ++linesAfterStartLine; 1721 if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS && 1722 scanner.CanStopOnThisLine()) { 1723 // Don't flush frames; we may be in the middle of a textrun 1724 // that we can't end here. That's OK, we just won't build it. 1725 // Note that we must already have finished the textrun for aForFrame, 1726 // because we've seen the end of a textrun in a line after the line 1727 // containing aForFrame. 1728 scanner.FlushLineBreaks(nullptr); 1729 // This flushes out mMappedFlows and mLineBreakBeforeFrames, which 1730 // silences assertions in the scanner destructor. 1731 scanner.ResetRunInfo(); 1732 return; 1733 } 1734 } 1735 } while (forwardIterator.Next()); 1736 1737 // Set mStartOfLine so FlushFrames knows its textrun ends a line 1738 scanner.SetAtStartOfLine(); 1739 scanner.FlushFrames(true, false); 1740 } 1741 1742 static char16_t* ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount) { 1743 while (aCount) { 1744 *aDest = *aSrc; 1745 ++aDest; 1746 ++aSrc; 1747 --aCount; 1748 } 1749 return aDest; 1750 } 1751 1752 bool BuildTextRunsScanner::IsTextRunValidForMappedFlows( 1753 const gfxTextRun* aTextRun) { 1754 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { 1755 return mMappedFlows.Length() == 1 && 1756 mMappedFlows[0].mStartFrame == GetFrameForSimpleFlow(aTextRun) && 1757 mMappedFlows[0].mEndFrame == nullptr; 1758 } 1759 1760 auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData()); 1761 TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun); 1762 if (userData->mMappedFlowCount != mMappedFlows.Length()) { 1763 return false; 1764 } 1765 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { 1766 if (userMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame || 1767 int32_t(userMappedFlows[i].mContentLength) != 1768 mMappedFlows[i].GetContentEnd() - 1769 mMappedFlows[i].mStartFrame->GetContentOffset()) { 1770 return false; 1771 } 1772 } 1773 return true; 1774 } 1775 1776 /** 1777 * This gets called when we need to make a text run for the current list of 1778 * frames. 1779 */ 1780 void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks, 1781 bool aSuppressTrailingBreak) { 1782 RefPtr<gfxTextRun> textRun; 1783 if (!mMappedFlows.IsEmpty()) { 1784 if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun && 1785 !!(mCurrentFramesAllSameTextRun->GetFlags2() & 1786 nsTextFrameUtils::Flags::IncomingWhitespace) == 1787 !!(mCurrentRunContextInfo & 1788 nsTextFrameUtils::INCOMING_WHITESPACE) && 1789 !!(mCurrentFramesAllSameTextRun->GetFlags() & 1790 gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR) == 1791 !!(mCurrentRunContextInfo & 1792 nsTextFrameUtils::INCOMING_ARABICCHAR) && 1793 IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) { 1794 // Optimization: We do not need to (re)build the textrun. 1795 textRun = mCurrentFramesAllSameTextRun; 1796 1797 if (mDoLineBreaking) { 1798 // Feed this run's text into the linebreaker to provide context. 1799 if (!SetupLineBreakerContext(textRun)) { 1800 return; 1801 } 1802 } 1803 1804 // Update mNextRunContextInfo appropriately 1805 mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE; 1806 if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TrailingWhitespace) { 1807 mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE; 1808 } 1809 if (textRun->GetFlags() & 1810 gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR) { 1811 mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR; 1812 } 1813 } else { 1814 AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> buffer; 1815 uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1); 1816 if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX || 1817 !buffer.AppendElements(bufferSize, fallible)) { 1818 return; 1819 } 1820 textRun = BuildTextRunForFrames(buffer.Elements()); 1821 } 1822 } 1823 1824 if (aFlushLineBreaks) { 1825 FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun.get()); 1826 if (!mDoLineBreaking && textRun) { 1827 CreateObserversForAnimatedGlyphs(textRun.get()); 1828 } 1829 } 1830 1831 mCanStopOnThisLine = true; 1832 ResetRunInfo(); 1833 } 1834 1835 void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun) { 1836 // If the line-breaker is buffering a potentially-unfinished word, 1837 // preserve the state of being in-word so that we don't spuriously 1838 // capitalize the next letter. 1839 bool inWord = mLineBreaker.InWord(); 1840 bool trailingLineBreak; 1841 nsresult rv = mLineBreaker.Reset(&trailingLineBreak); 1842 mLineBreaker.SetWordContinuation(inWord); 1843 // textRun may be null for various reasons, including because we constructed 1844 // a partial textrun just to get the linebreaker and other state set up 1845 // to build the next textrun. 1846 if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) { 1847 aTrailingTextRun->SetFlagBits(nsTextFrameUtils::Flags::HasTrailingBreak); 1848 } 1849 1850 for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) { 1851 // TODO cause frames associated with the textrun to be reflowed, if they 1852 // aren't being reflowed already! 1853 mBreakSinks[i]->Finish(mMissingFonts); 1854 } 1855 mBreakSinks.Clear(); 1856 } 1857 1858 void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame) { 1859 if (mMaxTextLength != UINT32_MAX) { 1860 NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(), 1861 "integer overflow"); 1862 if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) { 1863 mMaxTextLength = UINT32_MAX; 1864 } else { 1865 mMaxTextLength += aFrame->GetContentLength(); 1866 } 1867 } 1868 mDoubleByteText |= aFrame->CharacterDataBuffer().Is2b(); 1869 mLastFrame = aFrame; 1870 mCommonAncestorWithLastFrame = aFrame->GetParent(); 1871 1872 MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; 1873 NS_ASSERTION(mappedFlow->mStartFrame == aFrame || 1874 mappedFlow->GetContentEnd() == aFrame->GetContentOffset(), 1875 "Overlapping or discontiguous frames => BAD"); 1876 mappedFlow->mEndFrame = aFrame->GetNextContinuation(); 1877 if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) { 1878 mCurrentFramesAllSameTextRun = nullptr; 1879 } 1880 1881 if (mStartOfLine) { 1882 mLineBreakBeforeFrames.AppendElement(aFrame); 1883 mStartOfLine = false; 1884 } 1885 1886 // Default limits used by `hyphenate-limit-chars` for `auto` components, as 1887 // suggested by the CSS Text spec. 1888 // TODO: consider making these sensitive to the context, e.g. increasing the 1889 // values for long line lengths to reduce the tendency to hyphenate too much. 1890 const uint32_t kDefaultHyphenateTotalWordLength = 5; 1891 const uint32_t kDefaultHyphenatePreBreakLength = 2; 1892 const uint32_t kDefaultHyphenatePostBreakLength = 2; 1893 1894 const auto& hyphenateLimitChars = aFrame->StyleText()->mHyphenateLimitChars; 1895 uint32_t pre = 1896 hyphenateLimitChars.pre_hyphen_length.IsAuto() 1897 ? kDefaultHyphenatePreBreakLength 1898 : std::max(0, hyphenateLimitChars.pre_hyphen_length.AsNumber()); 1899 uint32_t post = 1900 hyphenateLimitChars.post_hyphen_length.IsAuto() 1901 ? kDefaultHyphenatePostBreakLength 1902 : std::max(0, hyphenateLimitChars.post_hyphen_length.AsNumber()); 1903 uint32_t total = 1904 hyphenateLimitChars.total_word_length.IsAuto() 1905 ? kDefaultHyphenateTotalWordLength 1906 : std::max(0, hyphenateLimitChars.total_word_length.AsNumber()); 1907 total = std::max(total, pre + post); 1908 mLineBreaker.SetHyphenateLimitChars(total, pre, post); 1909 } 1910 1911 static bool HasTerminalNewline(const nsTextFrame* aFrame) { 1912 if (aFrame->GetContentLength() == 0) { 1913 return false; 1914 } 1915 const CharacterDataBuffer& characterDataBuffer = 1916 aFrame->CharacterDataBuffer(); 1917 return characterDataBuffer.CharAt( 1918 AssertedCast<uint32_t>(aFrame->GetContentEnd()) - 1) == '\n'; 1919 } 1920 1921 static gfxFont::Metrics GetFirstFontMetrics(gfxFontGroup* aFontGroup, 1922 bool aVerticalMetrics) { 1923 if (!aFontGroup) { 1924 return gfxFont::Metrics(); 1925 } 1926 RefPtr<gfxFont> font = aFontGroup->GetFirstValidFont(); 1927 return font->GetMetrics(aVerticalMetrics ? nsFontMetrics::eVertical 1928 : nsFontMetrics::eHorizontal); 1929 } 1930 1931 static gfxFloat GetMinTabAdvanceAppUnits(const gfxTextRun* aTextRun) { 1932 gfxFloat chWidthAppUnits = NS_round( 1933 GetFirstFontMetrics(aTextRun->GetFontGroup(), aTextRun->IsVertical()) 1934 .ZeroOrAveCharWidth() * 1935 aTextRun->GetAppUnitsPerDevUnit()); 1936 return 0.5 * chWidthAppUnits; 1937 } 1938 1939 static float GetSVGFontSizeScaleFactor(nsIFrame* aFrame) { 1940 if (!aFrame->IsInSVGTextSubtree()) { 1941 return 1.0f; 1942 } 1943 auto* container = 1944 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText); 1945 MOZ_ASSERT(container); 1946 return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor(); 1947 } 1948 1949 static nscoord LetterSpacing(nsIFrame* aFrame, const nsStyleText& aStyleText) { 1950 if (aFrame->IsInSVGTextSubtree()) { 1951 // SVG text can have a scaling factor applied so that very small or very 1952 // large font-sizes don't suffer from poor glyph placement due to app unit 1953 // rounding. The used letter-spacing value must be scaled by the same 1954 // factor. 1955 return GetSVGFontSizeScaleFactor(aFrame) * 1956 aStyleText.mLetterSpacing.Resolve( 1957 [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); }); 1958 } 1959 1960 return aStyleText.mLetterSpacing.Resolve( 1961 [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); }); 1962 } 1963 1964 // This function converts non-coord values (e.g. percentages) to nscoord. 1965 static nscoord WordSpacing(nsIFrame* aFrame, const nsStyleText& aStyleText) { 1966 if (aFrame->IsInSVGTextSubtree()) { 1967 // SVG text can have a scaling factor applied so that very small or very 1968 // large font-sizes don't suffer from poor glyph placement due to app unit 1969 // rounding. The used word-spacing value must be scaled by the same 1970 // factor. 1971 return GetSVGFontSizeScaleFactor(aFrame) * 1972 aStyleText.mWordSpacing.Resolve( 1973 [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); }); 1974 } 1975 1976 return aStyleText.mWordSpacing.Resolve( 1977 [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); }); 1978 } 1979 1980 gfx::ShapedTextFlags nsTextFrame::GetSpacingFlags() const { 1981 const nsStyleText* styleText = StyleText(); 1982 const auto& ls = styleText->mLetterSpacing; 1983 const auto& ws = styleText->mWordSpacing; 1984 1985 // It's possible to have a calc() value that computes to zero but for which 1986 // IsDefinitelyZero() is false, in which case we'll return 1987 // TEXT_ENABLE_SPACING unnecessarily. That's ok because such cases are likely 1988 // to be rare, and avoiding TEXT_ENABLE_SPACING is just an optimization. 1989 bool nonStandardSpacing = 1990 !ls.IsDefinitelyZero() || !ws.IsDefinitelyZero() || 1991 TextAutospace::Enabled(styleText->EffectiveTextAutospace(), this); 1992 return nonStandardSpacing ? gfx::ShapedTextFlags::TEXT_ENABLE_SPACING 1993 : gfx::ShapedTextFlags(); 1994 } 1995 1996 bool BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, 1997 nsTextFrame* aFrame2) { 1998 // We don't need to check font size inflation, since 1999 // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|) 2000 // ensures that text runs never cross block boundaries. This means 2001 // that the font size inflation on all text frames in the text run is 2002 // already guaranteed to be the same as each other (and for the line 2003 // container). 2004 if (mBidiEnabled) { 2005 FrameBidiData data1 = aFrame1->GetBidiData(); 2006 FrameBidiData data2 = aFrame2->GetBidiData(); 2007 if (data1.embeddingLevel != data2.embeddingLevel || 2008 data2.precedingControl != kBidiLevelNone) { 2009 return false; 2010 } 2011 } 2012 2013 ComputedStyle* sc1 = aFrame1->Style(); 2014 ComputedStyle* sc2 = aFrame2->Style(); 2015 2016 // Any difference in writing-mode/directionality inhibits shaping across 2017 // the boundary. 2018 WritingMode wm(sc1); 2019 if (wm != WritingMode(sc2)) { 2020 return false; 2021 } 2022 2023 const nsStyleText* textStyle1 = sc1->StyleText(); 2024 // If the first frame ends in a preformatted newline, then we end the textrun 2025 // here. This avoids creating giant textruns for an entire plain text file. 2026 // Note that we create a single text frame for a preformatted text node, 2027 // even if it has newlines in it, so typically we won't see trailing newlines 2028 // until after reflow has broken up the frame into one (or more) frames per 2029 // line. That's OK though. 2030 if (textStyle1->NewlineIsSignificant(aFrame1) && 2031 HasTerminalNewline(aFrame1)) { 2032 return false; 2033 } 2034 2035 if (aFrame1->GetParent()->GetContent() != 2036 aFrame2->GetParent()->GetContent()) { 2037 // Does aFrame, or any ancestor between it and aAncestor, have a property 2038 // that should inhibit cross-element-boundary shaping on aSide? 2039 auto PreventCrossBoundaryShaping = [](const nsIFrame* aFrame, 2040 const nsIFrame* aAncestor, 2041 Side aSide) { 2042 while (aFrame != aAncestor) { 2043 ComputedStyle* ctx = aFrame->Style(); 2044 const auto anchorResolutionParams = 2045 AnchorPosResolutionParams::From(aFrame); 2046 // According to https://drafts.csswg.org/css-text/#boundary-shaping: 2047 // 2048 // Text shaping must be broken at inline box boundaries when any of 2049 // the following are true for any box whose boundary separates the 2050 // two typographic character units: 2051 // 2052 // 1. Any of margin/border/padding separating the two typographic 2053 // character units in the inline axis is non-zero. 2054 const auto margin = 2055 ctx->StyleMargin()->GetMargin(aSide, anchorResolutionParams); 2056 if (!margin->ConvertsToLength() || 2057 margin->AsLengthPercentage().ToLength() != 0) { 2058 return true; 2059 } 2060 const auto& padding = ctx->StylePadding()->mPadding.Get(aSide); 2061 if (!padding.ConvertsToLength() || padding.ToLength() != 0) { 2062 return true; 2063 } 2064 if (ctx->StyleBorder()->GetComputedBorderWidth(aSide) != 0) { 2065 return true; 2066 } 2067 2068 // 2. vertical-align is not baseline. 2069 // 2070 // FIXME: Should this use VerticalAlignEnum()? 2071 const auto& verticalAlign = ctx->StyleDisplay()->mVerticalAlign; 2072 if (!verticalAlign.IsKeyword() || 2073 verticalAlign.AsKeyword() != StyleVerticalAlignKeyword::Baseline) { 2074 return true; 2075 } 2076 2077 // 3. The boundary is a bidi isolation boundary. 2078 const auto unicodeBidi = ctx->StyleTextReset()->mUnicodeBidi; 2079 if (unicodeBidi == StyleUnicodeBidi::Isolate || 2080 unicodeBidi == StyleUnicodeBidi::IsolateOverride) { 2081 return true; 2082 } 2083 2084 aFrame = aFrame->GetParent(); 2085 } 2086 return false; 2087 }; 2088 2089 const nsIFrame* ancestor = 2090 nsLayoutUtils::FindNearestCommonAncestorFrameWithinBlock(aFrame1, 2091 aFrame2); 2092 2093 if (!ancestor) { 2094 // The two frames are within different blocks, e.g. due to block 2095 // fragmentation. In theory we shouldn't prevent cross-frame shaping 2096 // here, but it's an edge case where we should rarely decide to allow 2097 // cross-frame shaping, so we don't try harder here. 2098 return false; 2099 } 2100 2101 // We inhibit cross-element-boundary shaping if we're in SVG content, 2102 // as there are too many things SVG might be doing (like applying per- 2103 // element positioning) that wouldn't make sense with shaping across 2104 // the boundary. 2105 if (ancestor->IsInSVGTextSubtree()) { 2106 return false; 2107 } 2108 2109 // Map inline-end and inline-start to physical sides for checking presence 2110 // of non-zero margin/border/padding. 2111 Side side1 = wm.PhysicalSide(LogicalSide::IEnd); 2112 Side side2 = wm.PhysicalSide(LogicalSide::IStart); 2113 // If the frames have an embedding level that is opposite to the writing 2114 // mode, we need to swap which sides we're checking. 2115 if (aFrame1->GetEmbeddingLevel().IsRTL() == wm.IsBidiLTR()) { 2116 std::swap(side1, side2); 2117 } 2118 2119 if (PreventCrossBoundaryShaping(aFrame1, ancestor, side1) || 2120 PreventCrossBoundaryShaping(aFrame2, ancestor, side2)) { 2121 return false; 2122 } 2123 } 2124 2125 if (aFrame1->GetContent() == aFrame2->GetContent() && 2126 aFrame1->GetNextInFlow() != aFrame2) { 2127 // aFrame2 must be a non-fluid continuation of aFrame1. This can happen 2128 // sometimes when the unicode-bidi property is used; the bidi resolver 2129 // breaks text into different frames even though the text has the same 2130 // direction. We can't allow these two frames to share the same textrun 2131 // because that would violate our invariant that two flows in the same 2132 // textrun have different content elements. 2133 return false; 2134 } 2135 2136 if (sc1 == sc2) { 2137 return true; 2138 } 2139 2140 const nsStyleText* textStyle2 = sc2->StyleText(); 2141 if (textStyle1->mTextTransform != textStyle2->mTextTransform || 2142 textStyle1->EffectiveWordBreak() != textStyle2->EffectiveWordBreak() || 2143 textStyle1->mLineBreak != textStyle2->mLineBreak) { 2144 return false; 2145 } 2146 2147 nsPresContext* pc = aFrame1->PresContext(); 2148 MOZ_ASSERT(pc == aFrame2->PresContext()); 2149 2150 const nsStyleFont* fontStyle1 = sc1->StyleFont(); 2151 const nsStyleFont* fontStyle2 = sc2->StyleFont(); 2152 nscoord letterSpacing1 = LetterSpacing(aFrame1, *textStyle1); 2153 nscoord letterSpacing2 = LetterSpacing(aFrame2, *textStyle2); 2154 return fontStyle1->mFont == fontStyle2->mFont && 2155 fontStyle1->mLanguage == fontStyle2->mLanguage && 2156 nsLayoutUtils::GetTextRunFlagsForStyle(sc1, pc, fontStyle1, textStyle1, 2157 letterSpacing1) == 2158 nsLayoutUtils::GetTextRunFlagsForStyle(sc2, pc, fontStyle2, 2159 textStyle2, letterSpacing2); 2160 } 2161 2162 void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame) { 2163 LayoutFrameType frameType = aFrame->Type(); 2164 if (frameType == LayoutFrameType::RubyTextContainer) { 2165 // Don't include any ruby text container into the text run. 2166 return; 2167 } 2168 2169 // First check if we can extend the current mapped frame block. This is 2170 // common. 2171 if (mMappedFlows.Length() > 0) { 2172 MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; 2173 if (mappedFlow->mEndFrame == aFrame && 2174 aFrame->HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION)) { 2175 NS_ASSERTION(frameType == LayoutFrameType::Text, 2176 "Flow-sibling of a text frame is not a text frame?"); 2177 2178 // Don't do this optimization if mLastFrame has a terminal newline... 2179 // it's quite likely preformatted and we might want to end the textrun 2180 // here. This is almost always true: 2181 if (mLastFrame->Style() == aFrame->Style() && 2182 !HasTerminalNewline(mLastFrame)) { 2183 AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame)); 2184 return; 2185 } 2186 } 2187 } 2188 2189 // Now see if we can add a new set of frames to the current textrun 2190 if (frameType == LayoutFrameType::Text) { 2191 nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame); 2192 2193 if (mLastFrame) { 2194 if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) { 2195 FlushFrames(false, false); 2196 } else { 2197 if (mLastFrame->GetContent() == frame->GetContent()) { 2198 AccumulateRunInfo(frame); 2199 return; 2200 } 2201 } 2202 } 2203 2204 MappedFlow* mappedFlow = mMappedFlows.AppendElement(); 2205 mappedFlow->mStartFrame = frame; 2206 mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame; 2207 2208 AccumulateRunInfo(frame); 2209 if (mMappedFlows.Length() == 1) { 2210 mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun); 2211 mCurrentRunContextInfo = mNextRunContextInfo; 2212 } 2213 return; 2214 } 2215 2216 if (frameType == LayoutFrameType::Placeholder && 2217 aFrame->HasAnyStateBits(PLACEHOLDER_FOR_ABSPOS | 2218 PLACEHOLDER_FOR_FIXEDPOS)) { 2219 // Somewhat hacky fix for bug 1418472: 2220 // If this is a placeholder for an absolute-positioned frame, we need to 2221 // flush the line-breaker to prevent the placeholder becoming separated 2222 // from the immediately-following content. 2223 // XXX This will interrupt text shaping (ligatures, etc) if an abs-pos 2224 // element occurs within a word where shaping should be in effect, but 2225 // that's an edge case, unlikely to occur in real content. A more precise 2226 // fix might require better separation of line-breaking from textrun setup, 2227 // but that's a big invasive change (and potentially expensive for perf, as 2228 // it might introduce an additional pass over all the frames). 2229 FlushFrames(true, false); 2230 } 2231 2232 FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame); 2233 bool isBR = frameType == LayoutFrameType::Br; 2234 if (!traversal.mLineBreakerCanCrossFrameBoundary) { 2235 // BR frames are special. We do not need or want to record a break 2236 // opportunity before a BR frame. 2237 FlushFrames(true, isBR); 2238 mCommonAncestorWithLastFrame = aFrame; 2239 mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE; 2240 mStartOfLine = false; 2241 } else if (!traversal.mTextRunCanCrossFrameBoundary) { 2242 FlushFrames(false, false); 2243 } 2244 2245 for (nsIFrame* f = traversal.NextFrameToScan(); f; 2246 f = traversal.NextFrameToScan()) { 2247 ScanFrame(f); 2248 } 2249 2250 if (!traversal.mLineBreakerCanCrossFrameBoundary) { 2251 // Really if we're a BR frame this is unnecessary since descendInto will be 2252 // false. In fact this whole "if" statement should move into the 2253 // descendInto. 2254 FlushFrames(true, isBR); 2255 mCommonAncestorWithLastFrame = aFrame; 2256 mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE; 2257 } else if (!traversal.mTextRunCanCrossFrameBoundary) { 2258 FlushFrames(false, false); 2259 } 2260 2261 LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent()); 2262 } 2263 2264 nsTextFrame* BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex) { 2265 uint32_t index = *aIndex; 2266 if (index >= mLineBreakBeforeFrames.Length()) { 2267 return nullptr; 2268 } 2269 *aIndex = index + 1; 2270 return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index)); 2271 } 2272 2273 static gfxFontGroup* GetFontGroupForFrame( 2274 const nsIFrame* aFrame, float aFontSizeInflation, 2275 nsFontMetrics** aOutFontMetrics = nullptr) { 2276 RefPtr<nsFontMetrics> metrics = 2277 nsLayoutUtils::GetFontMetricsForFrame(aFrame, aFontSizeInflation); 2278 gfxFontGroup* fontGroup = metrics->GetThebesFontGroup(); 2279 2280 // Populate outparam before we return: 2281 if (aOutFontMetrics) { 2282 metrics.forget(aOutFontMetrics); 2283 } 2284 // XXX this is a bit bogus, we're releasing 'metrics' so the 2285 // returned font-group might actually be torn down, although because 2286 // of the way the device context caches font metrics, this seems to 2287 // not actually happen. But we should fix this. 2288 return fontGroup; 2289 } 2290 2291 nsFontMetrics* nsTextFrame::InflatedFontMetrics() const { 2292 if (!mFontMetrics) { 2293 float inflation = nsLayoutUtils::FontSizeInflationFor(this); 2294 mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(this, inflation); 2295 } 2296 return mFontMetrics; 2297 } 2298 2299 static gfxFontGroup* GetInflatedFontGroupForFrame(nsTextFrame* aFrame) { 2300 gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated); 2301 if (textRun) { 2302 return textRun->GetFontGroup(); 2303 } 2304 return aFrame->InflatedFontMetrics()->GetThebesFontGroup(); 2305 } 2306 2307 static already_AddRefed<DrawTarget> CreateReferenceDrawTarget( 2308 const nsTextFrame* aTextFrame) { 2309 UniquePtr<gfxContext> ctx = 2310 aTextFrame->PresShell()->CreateReferenceRenderingContext(); 2311 RefPtr<DrawTarget> dt = ctx->GetDrawTarget(); 2312 return dt.forget(); 2313 } 2314 2315 static already_AddRefed<gfxTextRun> GetHyphenTextRun(nsTextFrame* aTextFrame, 2316 DrawTarget* aDrawTarget) { 2317 RefPtr<DrawTarget> dt = aDrawTarget; 2318 if (!dt) { 2319 dt = CreateReferenceDrawTarget(aTextFrame); 2320 if (!dt) { 2321 return nullptr; 2322 } 2323 } 2324 2325 RefPtr<nsFontMetrics> fm = 2326 nsLayoutUtils::GetInflatedFontMetricsForFrame(aTextFrame); 2327 auto* fontGroup = fm->GetThebesFontGroup(); 2328 auto appPerDev = aTextFrame->PresContext()->AppUnitsPerDevPixel(); 2329 const auto& hyphenateChar = aTextFrame->StyleText()->mHyphenateCharacter; 2330 gfx::ShapedTextFlags flags = 2331 nsLayoutUtils::GetTextRunOrientFlagsForStyle(aTextFrame->Style()); 2332 // Make the directionality of the hyphen run (in case it is multi-char) match 2333 // the text frame. 2334 if (aTextFrame->GetWritingMode().IsBidiRTL()) { 2335 flags |= gfx::ShapedTextFlags::TEXT_IS_RTL; 2336 } 2337 if (hyphenateChar.IsAuto()) { 2338 return fontGroup->MakeHyphenTextRun(dt, flags, appPerDev); 2339 } 2340 auto* missingFonts = aTextFrame->PresContext()->MissingFontRecorder(); 2341 const NS_ConvertUTF8toUTF16 hyphenStr(hyphenateChar.AsString().AsString()); 2342 return fontGroup->MakeTextRun(hyphenStr.BeginReading(), hyphenStr.Length(), 2343 dt, appPerDev, flags, nsTextFrameUtils::Flags(), 2344 missingFonts); 2345 } 2346 2347 already_AddRefed<gfxTextRun> BuildTextRunsScanner::BuildTextRunForFrames( 2348 void* aTextBuffer) { 2349 gfxSkipChars skipChars; 2350 2351 const void* textPtr = aTextBuffer; 2352 bool anyTextTransformStyle = false; 2353 bool anyMathMLStyling = false; 2354 bool anyTextEmphasis = false; 2355 uint8_t sstyScriptLevel = 0; 2356 uint32_t mathFlags = 0; 2357 gfx::ShapedTextFlags flags = gfx::ShapedTextFlags(); 2358 nsTextFrameUtils::Flags flags2 = nsTextFrameUtils::Flags::NoBreaks; 2359 2360 if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { 2361 flags2 |= nsTextFrameUtils::Flags::IncomingWhitespace; 2362 } 2363 if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { 2364 flags |= gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR; 2365 } 2366 2367 AutoTArray<int32_t, 50> textBreakPoints; 2368 TextRunUserData dummyData; 2369 TextRunMappedFlow dummyMappedFlow; 2370 TextRunMappedFlow* userMappedFlows; 2371 TextRunUserData* userData; 2372 TextRunUserData* userDataToDestroy; 2373 // If the situation is particularly simple (and common) we don't need to 2374 // allocate userData. 2375 if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame && 2376 mMappedFlows[0].mStartFrame->GetContentOffset() == 0) { 2377 userData = &dummyData; 2378 userMappedFlows = &dummyMappedFlow; 2379 userDataToDestroy = nullptr; 2380 dummyData.mMappedFlowCount = mMappedFlows.Length(); 2381 dummyData.mLastFlowIndex = 0; 2382 } else { 2383 userData = CreateUserData(mMappedFlows.Length()); 2384 userMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1); 2385 userDataToDestroy = userData; 2386 } 2387 2388 uint32_t currentTransformedTextOffset = 0; 2389 2390 uint32_t nextBreakIndex = 0; 2391 nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); 2392 bool isSVG = mLineContainer->IsInSVGTextSubtree(); 2393 bool enabledJustification = 2394 (mLineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify || 2395 mLineContainer->StyleText()->mTextAlignLast == 2396 StyleTextAlignLast::Justify); 2397 2398 const nsStyleText* textStyle = nullptr; 2399 const nsStyleFont* fontStyle = nullptr; 2400 ComputedStyle* lastComputedStyle = nullptr; 2401 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { 2402 MappedFlow* mappedFlow = &mMappedFlows[i]; 2403 nsTextFrame* f = mappedFlow->mStartFrame; 2404 2405 lastComputedStyle = f->Style(); 2406 // Detect use of text-transform or font-variant anywhere in the run 2407 textStyle = f->StyleText(); 2408 if (!textStyle->mTextTransform.IsNone() || 2409 textStyle->mWebkitTextSecurity != StyleTextSecurity::None || 2410 // text-combine-upright requires converting from full-width 2411 // characters to non-full-width correspendent in some cases. 2412 lastComputedStyle->IsTextCombined()) { 2413 anyTextTransformStyle = true; 2414 } 2415 if (textStyle->HasEffectiveTextEmphasis()) { 2416 anyTextEmphasis = true; 2417 } 2418 flags |= f->GetSpacingFlags(); 2419 nsTextFrameUtils::CompressionMode compression = 2420 GetCSSWhitespaceToCompressionMode(f, textStyle); 2421 if ((enabledJustification || f->ShouldSuppressLineBreak()) && !isSVG) { 2422 flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING; 2423 } 2424 fontStyle = f->StyleFont(); 2425 nsIFrame* parent = mLineContainer->GetParent(); 2426 if (StyleMathVariant::None != fontStyle->mMathVariant) { 2427 if (StyleMathVariant::Normal != fontStyle->mMathVariant) { 2428 anyMathMLStyling = true; 2429 } 2430 } else if (mLineContainer->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) { 2431 flags2 |= nsTextFrameUtils::Flags::IsSingleCharMi; 2432 anyMathMLStyling = true; 2433 } 2434 if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) { 2435 // All MathML tokens except <mtext> use 'math' script. 2436 if (!(parent && parent->GetContent() && 2437 parent->GetContent()->IsMathMLElement(nsGkAtoms::mtext))) { 2438 flags |= gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT; 2439 } 2440 nsIMathMLFrame* mathFrame = do_QueryFrame(parent); 2441 if (mathFrame) { 2442 nsPresentationData presData; 2443 mathFrame->GetPresentationData(presData); 2444 if (presData.flags.contains(MathMLPresentationFlag::Dtls)) { 2445 mathFlags |= MathMLTextRunFactory::MATH_FONT_FEATURE_DTLS; 2446 anyMathMLStyling = true; 2447 } 2448 } 2449 } 2450 nsIFrame* child = mLineContainer; 2451 uint8_t oldScriptLevel = 0; 2452 while (parent && 2453 child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) { 2454 // Reconstruct the script level ignoring any user overrides. It is 2455 // calculated this way instead of using scriptlevel to ensure the 2456 // correct ssty font feature setting is used even if the user sets a 2457 // different (especially negative) scriptlevel. 2458 nsIMathMLFrame* mathFrame = do_QueryFrame(parent); 2459 if (mathFrame) { 2460 sstyScriptLevel += mathFrame->ScriptIncrement(child); 2461 } 2462 if (sstyScriptLevel < oldScriptLevel) { 2463 // overflow 2464 sstyScriptLevel = UINT8_MAX; 2465 break; 2466 } 2467 child = parent; 2468 parent = parent->GetParent(); 2469 oldScriptLevel = sstyScriptLevel; 2470 } 2471 if (sstyScriptLevel) { 2472 anyMathMLStyling = true; 2473 } 2474 2475 // Figure out what content is included in this flow. 2476 nsIContent* content = f->GetContent(); 2477 const CharacterDataBuffer& characterDataBuffer = f->CharacterDataBuffer(); 2478 int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset(); 2479 int32_t contentEnd = mappedFlow->GetContentEnd(); 2480 int32_t contentLength = contentEnd - contentStart; 2481 const nsAtom* language = f->StyleFont()->mLanguage; 2482 2483 TextRunMappedFlow* newFlow = &userMappedFlows[i]; 2484 newFlow->mStartFrame = mappedFlow->mStartFrame; 2485 newFlow->mDOMOffsetToBeforeTransformOffset = 2486 skipChars.GetOriginalCharCount() - 2487 mappedFlow->mStartFrame->GetContentOffset(); 2488 newFlow->mContentLength = contentLength; 2489 2490 while (nextBreakBeforeFrame && 2491 nextBreakBeforeFrame->GetContent() == content) { 2492 textBreakPoints.AppendElement(nextBreakBeforeFrame->GetContentOffset() + 2493 newFlow->mDOMOffsetToBeforeTransformOffset); 2494 nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); 2495 } 2496 2497 nsTextFrameUtils::Flags analysisFlags; 2498 if (characterDataBuffer.Is2b()) { 2499 NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!"); 2500 char16_t* bufStart = static_cast<char16_t*>(aTextBuffer); 2501 char16_t* bufEnd = nsTextFrameUtils::TransformText( 2502 characterDataBuffer.Get2b() + contentStart, contentLength, bufStart, 2503 compression, &mNextRunContextInfo, &skipChars, &analysisFlags, 2504 language); 2505 aTextBuffer = bufEnd; 2506 currentTransformedTextOffset = 2507 bufEnd - static_cast<const char16_t*>(textPtr); 2508 } else { 2509 if (mDoubleByteText) { 2510 // Need to expand the text. First transform it into a temporary buffer, 2511 // then expand. 2512 AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> tempBuf; 2513 uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible); 2514 if (!bufStart) { 2515 DestroyUserData(userDataToDestroy); 2516 return nullptr; 2517 } 2518 uint8_t* end = nsTextFrameUtils::TransformText( 2519 reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) + 2520 contentStart, 2521 contentLength, bufStart, compression, &mNextRunContextInfo, 2522 &skipChars, &analysisFlags, language); 2523 aTextBuffer = 2524 ExpandBuffer(static_cast<char16_t*>(aTextBuffer), 2525 tempBuf.Elements(), end - tempBuf.Elements()); 2526 currentTransformedTextOffset = static_cast<char16_t*>(aTextBuffer) - 2527 static_cast<const char16_t*>(textPtr); 2528 } else { 2529 uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer); 2530 uint8_t* end = nsTextFrameUtils::TransformText( 2531 reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) + 2532 contentStart, 2533 contentLength, bufStart, compression, &mNextRunContextInfo, 2534 &skipChars, &analysisFlags, language); 2535 aTextBuffer = end; 2536 currentTransformedTextOffset = 2537 end - static_cast<const uint8_t*>(textPtr); 2538 } 2539 } 2540 flags2 |= analysisFlags; 2541 } 2542 2543 void* finalUserData; 2544 if (userData == &dummyData) { 2545 flags2 |= nsTextFrameUtils::Flags::IsSimpleFlow; 2546 userData = nullptr; 2547 finalUserData = mMappedFlows[0].mStartFrame; 2548 } else { 2549 finalUserData = userData; 2550 } 2551 2552 uint32_t transformedLength = currentTransformedTextOffset; 2553 2554 // Now build the textrun 2555 nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame; 2556 float fontInflation; 2557 gfxFontGroup* fontGroup; 2558 if (mWhichTextRun == nsTextFrame::eNotInflated) { 2559 fontInflation = 1.0f; 2560 fontGroup = GetFontGroupForFrame(firstFrame, fontInflation); 2561 } else { 2562 fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame); 2563 fontGroup = GetInflatedFontGroupForFrame(firstFrame); 2564 } 2565 MOZ_ASSERT(fontGroup); 2566 2567 if (flags2 & nsTextFrameUtils::Flags::HasTab) { 2568 flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING; 2569 } 2570 if (flags2 & nsTextFrameUtils::Flags::HasShy) { 2571 flags |= gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS; 2572 } 2573 if (mBidiEnabled && (firstFrame->GetEmbeddingLevel().IsRTL())) { 2574 flags |= gfx::ShapedTextFlags::TEXT_IS_RTL; 2575 } 2576 if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { 2577 flags2 |= nsTextFrameUtils::Flags::TrailingWhitespace; 2578 } 2579 if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { 2580 flags |= gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR; 2581 } 2582 // ContinueTextRunAcrossFrames guarantees that it doesn't matter which 2583 // frame's style is used, so we use a mixture of the first frame and 2584 // last frame's style 2585 flags |= nsLayoutUtils::GetTextRunFlagsForStyle( 2586 lastComputedStyle, firstFrame->PresContext(), fontStyle, textStyle, 2587 LetterSpacing(firstFrame, *textStyle)); 2588 // XXX this is a bit of a hack. For performance reasons, if we're favouring 2589 // performance over quality, don't try to get accurate glyph extents. 2590 if (!(flags & gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED)) { 2591 flags |= gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX; 2592 } 2593 2594 // Convert linebreak coordinates to transformed string offsets 2595 NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(), 2596 "Didn't find all the frames to break-before..."); 2597 gfxSkipCharsIterator iter(skipChars); 2598 AutoTArray<uint32_t, 50> textBreakPointsAfterTransform; 2599 for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) { 2600 nsTextFrameUtils::AppendLineBreakOffset( 2601 &textBreakPointsAfterTransform, 2602 iter.ConvertOriginalToSkipped(textBreakPoints[i])); 2603 } 2604 if (mStartOfLine) { 2605 nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform, 2606 transformedLength); 2607 } 2608 2609 // Setup factory chain 2610 bool needsToMaskPassword = NeedsToMaskPassword(firstFrame); 2611 UniquePtr<nsTransformingTextRunFactory> transformingFactory; 2612 if (anyTextTransformStyle || needsToMaskPassword) { 2613 char16_t maskChar = 2614 needsToMaskPassword ? 0 : textStyle->TextSecurityMaskChar(); 2615 transformingFactory = MakeUnique<nsCaseTransformTextRunFactory>( 2616 std::move(transformingFactory), false, maskChar); 2617 } 2618 if (anyMathMLStyling) { 2619 transformingFactory = MakeUnique<MathMLTextRunFactory>( 2620 std::move(transformingFactory), mathFlags, sstyScriptLevel, 2621 fontInflation); 2622 } 2623 nsTArray<RefPtr<nsTransformedCharStyle>> styles; 2624 if (transformingFactory) { 2625 uint32_t unmaskStart = 0, unmaskEnd = UINT32_MAX; 2626 if (needsToMaskPassword) { 2627 unmaskStart = unmaskEnd = UINT32_MAX; 2628 const TextEditor* const passwordEditor = 2629 nsContentUtils::GetExtantTextEditorFromAnonymousNode( 2630 firstFrame->GetContent()); 2631 if (passwordEditor && !passwordEditor->IsAllMasked()) { 2632 unmaskStart = passwordEditor->UnmaskedStart(); 2633 unmaskEnd = passwordEditor->UnmaskedEnd(); 2634 } 2635 } 2636 2637 iter.SetOriginalOffset(0); 2638 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { 2639 MappedFlow* mappedFlow = &mMappedFlows[i]; 2640 nsTextFrame* f; 2641 ComputedStyle* sc = nullptr; 2642 RefPtr<nsTransformedCharStyle> defaultStyle; 2643 RefPtr<nsTransformedCharStyle> unmaskStyle; 2644 for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame; 2645 f = f->GetNextContinuation()) { 2646 uint32_t skippedOffset = iter.GetSkippedOffset(); 2647 // Text-combined frames have content-dependent transform, so we 2648 // want to create new nsTransformedCharStyle for them anyway. 2649 if (sc != f->Style() || sc->IsTextCombined()) { 2650 sc = f->Style(); 2651 defaultStyle = new nsTransformedCharStyle(sc, f->PresContext()); 2652 if (sc->IsTextCombined() && f->CountGraphemeClusters() > 1) { 2653 defaultStyle->mForceNonFullWidth = true; 2654 } 2655 if (needsToMaskPassword) { 2656 defaultStyle->mMaskPassword = true; 2657 if (unmaskStart != unmaskEnd) { 2658 unmaskStyle = new nsTransformedCharStyle(sc, f->PresContext()); 2659 unmaskStyle->mForceNonFullWidth = 2660 defaultStyle->mForceNonFullWidth; 2661 } 2662 } 2663 } 2664 iter.AdvanceOriginal(f->GetContentLength()); 2665 uint32_t skippedEnd = iter.GetSkippedOffset(); 2666 if (unmaskStyle) { 2667 uint32_t skippedUnmaskStart = 2668 iter.ConvertOriginalToSkipped(unmaskStart); 2669 uint32_t skippedUnmaskEnd = iter.ConvertOriginalToSkipped(unmaskEnd); 2670 iter.SetSkippedOffset(skippedEnd); 2671 for (; skippedOffset < std::min(skippedEnd, skippedUnmaskStart); 2672 ++skippedOffset) { 2673 styles.AppendElement(defaultStyle); 2674 } 2675 for (; skippedOffset < std::min(skippedEnd, skippedUnmaskEnd); 2676 ++skippedOffset) { 2677 styles.AppendElement(unmaskStyle); 2678 } 2679 for (; skippedOffset < skippedEnd; ++skippedOffset) { 2680 styles.AppendElement(defaultStyle); 2681 } 2682 } else { 2683 for (; skippedOffset < skippedEnd; ++skippedOffset) { 2684 styles.AppendElement(defaultStyle); 2685 } 2686 } 2687 } 2688 } 2689 flags2 |= nsTextFrameUtils::Flags::IsTransformed; 2690 NS_ASSERTION(iter.GetSkippedOffset() == transformedLength, 2691 "We didn't cover all the characters in the text run!"); 2692 } 2693 2694 RefPtr<gfxTextRun> textRun; 2695 gfxTextRunFactory::Parameters params = { 2696 mDrawTarget, 2697 finalUserData, 2698 &skipChars, 2699 textBreakPointsAfterTransform.Elements(), 2700 uint32_t(textBreakPointsAfterTransform.Length()), 2701 int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())}; 2702 2703 if (mDoubleByteText) { 2704 const char16_t* text = static_cast<const char16_t*>(textPtr); 2705 if (transformingFactory) { 2706 textRun = transformingFactory->MakeTextRun( 2707 text, transformedLength, ¶ms, fontGroup, flags, flags2, 2708 std::move(styles), true); 2709 } else { 2710 textRun = fontGroup->MakeTextRun(text, transformedLength, ¶ms, flags, 2711 flags2, mMissingFonts); 2712 } 2713 } else { 2714 const uint8_t* text = static_cast<const uint8_t*>(textPtr); 2715 flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT; 2716 if (transformingFactory) { 2717 textRun = transformingFactory->MakeTextRun( 2718 text, transformedLength, ¶ms, fontGroup, flags, flags2, 2719 std::move(styles), true); 2720 } else { 2721 textRun = fontGroup->MakeTextRun(text, transformedLength, ¶ms, flags, 2722 flags2, mMissingFonts); 2723 } 2724 } 2725 if (!textRun) { 2726 DestroyUserData(userDataToDestroy); 2727 return nullptr; 2728 } 2729 2730 // We have to set these up after we've created the textrun, because 2731 // the breaks may be stored in the textrun during this very call. 2732 // This is a bit annoying because it requires another loop over the frames 2733 // making up the textrun, but I don't see a way to avoid this. 2734 // We have to do this if line-breaking is required OR if a text-transform 2735 // is in effect, because we depend on the line-breaker's scanner (via 2736 // BreakSink::Finish) to finish building transformed textruns. 2737 if (mDoLineBreaking || transformingFactory) { 2738 SetupBreakSinksForTextRun(textRun.get(), textPtr); 2739 } 2740 2741 // Ownership of the factory has passed to the textrun 2742 // TODO: bug 1285316: clean up ownership transfer from the factory to 2743 // the textrun 2744 (void)transformingFactory.release(); 2745 2746 if (anyTextEmphasis) { 2747 SetupTextEmphasisForTextRun(textRun.get(), textPtr); 2748 } 2749 2750 if (mSkipIncompleteTextRuns) { 2751 mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace( 2752 textPtr, transformedLength, mDoubleByteText); 2753 // Since we're doing to destroy the user data now, avoid a dangling 2754 // pointer. Strictly speaking we don't need to do this since it should 2755 // not be used (since this textrun will not be used and will be 2756 // itself deleted soon), but it's always better to not have dangling 2757 // pointers around. 2758 textRun->SetUserData(nullptr); 2759 DestroyUserData(userDataToDestroy); 2760 return nullptr; 2761 } 2762 2763 // Actually wipe out the textruns associated with the mapped frames and 2764 // associate those frames with this text run. 2765 AssignTextRun(textRun.get(), fontInflation); 2766 return textRun.forget(); 2767 } 2768 2769 // This is a cut-down version of BuildTextRunForFrames used to set up 2770 // context for the line-breaker, when the textrun has already been created. 2771 // So it does the same walk over the mMappedFlows, but doesn't actually 2772 // build a new textrun. 2773 bool BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun* aTextRun) { 2774 AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> buffer; 2775 uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1); 2776 if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) { 2777 return false; 2778 } 2779 void* textPtr = buffer.AppendElements(bufferSize, fallible); 2780 if (!textPtr) { 2781 return false; 2782 } 2783 2784 gfxSkipChars skipChars; 2785 const nsAtom* language = mMappedFlows[0].mStartFrame->StyleFont()->mLanguage; 2786 2787 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { 2788 MappedFlow* mappedFlow = &mMappedFlows[i]; 2789 nsTextFrame* f = mappedFlow->mStartFrame; 2790 2791 const nsStyleText* textStyle = f->StyleText(); 2792 nsTextFrameUtils::CompressionMode compression = 2793 GetCSSWhitespaceToCompressionMode(f, textStyle); 2794 2795 // Figure out what content is included in this flow. 2796 const CharacterDataBuffer& characterDataBuffer = f->CharacterDataBuffer(); 2797 int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset(); 2798 int32_t contentEnd = mappedFlow->GetContentEnd(); 2799 int32_t contentLength = contentEnd - contentStart; 2800 2801 nsTextFrameUtils::Flags analysisFlags; 2802 if (characterDataBuffer.Is2b()) { 2803 NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!"); 2804 char16_t* bufStart = static_cast<char16_t*>(textPtr); 2805 char16_t* bufEnd = nsTextFrameUtils::TransformText( 2806 characterDataBuffer.Get2b() + contentStart, contentLength, bufStart, 2807 compression, &mNextRunContextInfo, &skipChars, &analysisFlags, 2808 language); 2809 textPtr = bufEnd; 2810 } else { 2811 if (mDoubleByteText) { 2812 // Need to expand the text. First transform it into a temporary buffer, 2813 // then expand. 2814 AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> tempBuf; 2815 uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible); 2816 if (!bufStart) { 2817 return false; 2818 } 2819 uint8_t* end = nsTextFrameUtils::TransformText( 2820 reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) + 2821 contentStart, 2822 contentLength, bufStart, compression, &mNextRunContextInfo, 2823 &skipChars, &analysisFlags, language); 2824 textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr), 2825 tempBuf.Elements(), end - tempBuf.Elements()); 2826 } else { 2827 uint8_t* bufStart = static_cast<uint8_t*>(textPtr); 2828 uint8_t* end = nsTextFrameUtils::TransformText( 2829 reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) + 2830 contentStart, 2831 contentLength, bufStart, compression, &mNextRunContextInfo, 2832 &skipChars, &analysisFlags, language); 2833 textPtr = end; 2834 } 2835 } 2836 } 2837 2838 // We have to set these up after we've created the textrun, because 2839 // the breaks may be stored in the textrun during this very call. 2840 // This is a bit annoying because it requires another loop over the frames 2841 // making up the textrun, but I don't see a way to avoid this. 2842 SetupBreakSinksForTextRun(aTextRun, buffer.Elements()); 2843 2844 return true; 2845 } 2846 2847 static bool HasCompressedLeadingWhitespace( 2848 nsTextFrame* aFrame, const nsStyleText* aStyleText, 2849 int32_t aContentEndOffset, const gfxSkipCharsIterator& aIterator) { 2850 if (!aIterator.IsOriginalCharSkipped()) { 2851 return false; 2852 } 2853 2854 gfxSkipCharsIterator iter = aIterator; 2855 int32_t frameContentOffset = aFrame->GetContentOffset(); 2856 const CharacterDataBuffer& characterDataBuffer = 2857 aFrame->CharacterDataBuffer(); 2858 while (frameContentOffset < aContentEndOffset && 2859 iter.IsOriginalCharSkipped()) { 2860 if (IsTrimmableSpace(characterDataBuffer, frameContentOffset, aStyleText)) { 2861 return true; 2862 } 2863 ++frameContentOffset; 2864 iter.AdvanceOriginal(1); 2865 } 2866 return false; 2867 } 2868 2869 void BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun, 2870 const void* aTextPtr) { 2871 using mozilla::intl::LineBreakRule; 2872 using mozilla::intl::WordBreakRule; 2873 2874 // textruns have uniform language 2875 const nsStyleFont* styleFont = mMappedFlows[0].mStartFrame->StyleFont(); 2876 // We should only use a language for hyphenation if it was specified 2877 // explicitly. 2878 nsAtom* hyphenationLanguage = 2879 styleFont->mExplicitLanguage ? styleFont->mLanguage.get() : nullptr; 2880 // We keep this pointed at the skip-chars data for the current mappedFlow. 2881 // This lets us cheaply check whether the flow has compressed initial 2882 // whitespace... 2883 gfxSkipCharsIterator iter(aTextRun->GetSkipChars()); 2884 2885 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { 2886 MappedFlow* mappedFlow = &mMappedFlows[i]; 2887 // The CSS word-break value may change within a word, so we reset it for 2888 // each MappedFlow. The line-breaker will flush its text if the property 2889 // actually changes. 2890 const auto* styleText = mappedFlow->mStartFrame->StyleText(); 2891 auto wordBreak = styleText->EffectiveWordBreak(); 2892 switch (wordBreak) { 2893 case StyleWordBreak::BreakAll: 2894 mLineBreaker.SetWordBreak(WordBreakRule::BreakAll); 2895 break; 2896 case StyleWordBreak::KeepAll: 2897 mLineBreaker.SetWordBreak(WordBreakRule::KeepAll); 2898 break; 2899 case StyleWordBreak::Normal: 2900 default: 2901 MOZ_ASSERT(wordBreak == StyleWordBreak::Normal); 2902 mLineBreaker.SetWordBreak(WordBreakRule::Normal); 2903 break; 2904 } 2905 switch (styleText->mLineBreak) { 2906 case StyleLineBreak::Auto: 2907 mLineBreaker.SetStrictness(LineBreakRule::Auto); 2908 break; 2909 case StyleLineBreak::Normal: 2910 mLineBreaker.SetStrictness(LineBreakRule::Normal); 2911 break; 2912 case StyleLineBreak::Loose: 2913 mLineBreaker.SetStrictness(LineBreakRule::Loose); 2914 break; 2915 case StyleLineBreak::Strict: 2916 mLineBreaker.SetStrictness(LineBreakRule::Strict); 2917 break; 2918 case StyleLineBreak::Anywhere: 2919 mLineBreaker.SetStrictness(LineBreakRule::Anywhere); 2920 break; 2921 } 2922 2923 uint32_t offset = iter.GetSkippedOffset(); 2924 gfxSkipCharsIterator iterNext = iter; 2925 iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() - 2926 mappedFlow->mStartFrame->GetContentOffset()); 2927 2928 UniquePtr<BreakSink>* breakSink = mBreakSinks.AppendElement( 2929 MakeUnique<BreakSink>(aTextRun, mDrawTarget, offset)); 2930 2931 uint32_t length = iterNext.GetSkippedOffset() - offset; 2932 uint32_t flags = 0; 2933 nsIFrame* initialBreakController = 2934 mappedFlow->mAncestorControllingInitialBreak; 2935 if (!initialBreakController) { 2936 initialBreakController = mLineContainer; 2937 } 2938 if (!initialBreakController->StyleText()->WhiteSpaceCanWrap( 2939 initialBreakController)) { 2940 flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL; 2941 } 2942 nsTextFrame* startFrame = mappedFlow->mStartFrame; 2943 const nsStyleText* textStyle = startFrame->StyleText(); 2944 if (!textStyle->WhiteSpaceCanWrap(startFrame)) { 2945 flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE; 2946 } 2947 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::NoBreaks) { 2948 flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS; 2949 } 2950 if (textStyle->mTextTransform & StyleTextTransform::CAPITALIZE) { 2951 flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION; 2952 } 2953 if (textStyle->mHyphens == StyleHyphens::Auto && 2954 textStyle->mLineBreak != StyleLineBreak::Anywhere) { 2955 flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION; 2956 } 2957 2958 if (HasCompressedLeadingWhitespace(startFrame, textStyle, 2959 mappedFlow->GetContentEnd(), iter)) { 2960 mLineBreaker.AppendInvisibleWhitespace(flags); 2961 } 2962 2963 if (length > 0) { 2964 BreakSink* sink = mSkipIncompleteTextRuns ? nullptr : (*breakSink).get(); 2965 if (mDoubleByteText) { 2966 const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr); 2967 mLineBreaker.AppendText(hyphenationLanguage, text + offset, length, 2968 flags, sink); 2969 } else { 2970 const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr); 2971 mLineBreaker.AppendText(hyphenationLanguage, text + offset, length, 2972 flags, sink); 2973 } 2974 } 2975 2976 iter = iterNext; 2977 } 2978 } 2979 2980 static bool MayCharacterHaveEmphasisMark(uint32_t aCh) { 2981 // Punctuation characters that *can* take emphasis marks (exceptions to the 2982 // rule that characters with GeneralCategory=P* do not take emphasis), as per 2983 // https://drafts.csswg.org/css-text-decor/#text-emphasis-style-property 2984 // There are no non-BMP codepoints in the punctuation exceptions, so we can 2985 // just use a 16-bit string to list & check them. 2986 constexpr nsLiteralString kPunctuationAcceptsEmphasis = 2987 u"\x0023" // # NUMBER SIGN 2988 u"\x0025" // % PERCENT SIGN 2989 u"\x0026" // & AMPERSAND 2990 u"\x0040" // @ COMMERCIAL AT 2991 u"\x00A7" // § SECTION SIGN 2992 u"\x00B6" // ¶ PILCROW SIGN 2993 u"\x0609" // ؉ ARABIC-INDIC PER MILLE SIGN 2994 u"\x060A" // ؊ ARABIC-INDIC PER TEN THOUSAND SIGN 2995 u"\x066A" // ٪ ARABIC PERCENT SIGN 2996 u"\x2030" // ‰ PER MILLE SIGN 2997 u"\x2031" // ‱ PER TEN THOUSAND SIGN 2998 u"\x204A" // ⁊ TIRONIAN SIGN ET 2999 u"\x204B" // ⁋ REVERSED PILCROW SIGN 3000 u"\x2053" // ⁓ SWUNG DASH 3001 u"\x303D" // 〽️ PART ALTERNATION MARK 3002 // Characters that are NFKD-equivalent to the above, extracted from 3003 // UnicodeData.txt. 3004 u"\xFE5F" // SMALL NUMBER SIGN;Po;0;ET;<small> 0023;;;;N;;;;; 3005 u"\xFE60" // SMALL AMPERSAND;Po;0;ON;<small> 0026;;;;N;;;;; 3006 u"\xFE6A" // SMALL PERCENT SIGN;Po;0;ET;<small> 0025;;;;N;;;;; 3007 u"\xFE6B" // SMALL COMMERCIAL AT;Po;0;ON;<small> 0040;;;;N;;;;; 3008 u"\xFF03" // FULLWIDTH NUMBER SIGN;Po;0;ET;<wide> 0023;;;;N;;;;; 3009 u"\xFF05" // FULLWIDTH PERCENT SIGN;Po;0;ET;<wide> 0025;;;;N;;;;; 3010 u"\xFF06" // FULLWIDTH AMPERSAND;Po;0;ON;<wide> 0026;;;;N;;;;; 3011 u"\xFF20"_ns; // FULLWIDTH COMMERCIAL AT;Po;0;ON;<wide> 0040;;;;N;;;;; 3012 3013 switch (unicode::GetGenCategory(aCh)) { 3014 case nsUGenCategory::kSeparator: // whitespace, line- & para-separators 3015 return false; 3016 case nsUGenCategory::kOther: // control categories 3017 return false; 3018 case nsUGenCategory::kPunctuation: 3019 return aCh <= 0xffff && 3020 kPunctuationAcceptsEmphasis.Contains(char16_t(aCh)); 3021 default: 3022 return true; 3023 } 3024 } 3025 3026 void BuildTextRunsScanner::SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, 3027 const void* aTextPtr) { 3028 if (!mDoubleByteText) { 3029 auto text = reinterpret_cast<const uint8_t*>(aTextPtr); 3030 for (auto i : IntegerRange(aTextRun->GetLength())) { 3031 if (!MayCharacterHaveEmphasisMark(text[i])) { 3032 aTextRun->SetNoEmphasisMark(i); 3033 } 3034 } 3035 } else { 3036 auto text = reinterpret_cast<const char16_t*>(aTextPtr); 3037 auto length = aTextRun->GetLength(); 3038 for (size_t i = 0; i < length; ++i) { 3039 if (i + 1 < length && NS_IS_SURROGATE_PAIR(text[i], text[i + 1])) { 3040 uint32_t ch = SURROGATE_TO_UCS4(text[i], text[i + 1]); 3041 if (!MayCharacterHaveEmphasisMark(ch)) { 3042 aTextRun->SetNoEmphasisMark(i); 3043 aTextRun->SetNoEmphasisMark(i + 1); 3044 } 3045 ++i; 3046 } else { 3047 if (!MayCharacterHaveEmphasisMark(uint32_t(text[i]))) { 3048 aTextRun->SetNoEmphasisMark(i); 3049 } 3050 } 3051 } 3052 } 3053 } 3054 3055 // Find the flow corresponding to aContent in aUserData 3056 static inline TextRunMappedFlow* FindFlowForContent( 3057 TextRunUserData* aUserData, nsIContent* aContent, 3058 TextRunMappedFlow* userMappedFlows) { 3059 // Find the flow that contains us 3060 int32_t i = aUserData->mLastFlowIndex; 3061 int32_t delta = 1; 3062 int32_t sign = 1; 3063 // Search starting at the current position and examine close-by 3064 // positions first, moving further and further away as we go. 3065 while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) { 3066 TextRunMappedFlow* flow = &userMappedFlows[i]; 3067 if (flow->mStartFrame->GetContent() == aContent) { 3068 return flow; 3069 } 3070 3071 i += delta; 3072 sign = -sign; 3073 delta = -delta + sign; 3074 } 3075 3076 // We ran into an array edge. Add |delta| to |i| once more to get 3077 // back to the side where we still need to search, then step in 3078 // the |sign| direction. 3079 i += delta; 3080 if (sign > 0) { 3081 for (; i < int32_t(aUserData->mMappedFlowCount); ++i) { 3082 TextRunMappedFlow* flow = &userMappedFlows[i]; 3083 if (flow->mStartFrame->GetContent() == aContent) { 3084 return flow; 3085 } 3086 } 3087 } else { 3088 for (; i >= 0; --i) { 3089 TextRunMappedFlow* flow = &userMappedFlows[i]; 3090 if (flow->mStartFrame->GetContent() == aContent) { 3091 return flow; 3092 } 3093 } 3094 } 3095 3096 return nullptr; 3097 } 3098 3099 void BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun, 3100 float aInflation) { 3101 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { 3102 MappedFlow* mappedFlow = &mMappedFlows[i]; 3103 nsTextFrame* startFrame = mappedFlow->mStartFrame; 3104 nsTextFrame* endFrame = mappedFlow->mEndFrame; 3105 nsTextFrame* f; 3106 for (f = startFrame; f != endFrame; f = f->GetNextContinuation()) { 3107 #ifdef DEBUG_roc 3108 if (f->GetTextRun(mWhichTextRun)) { 3109 gfxTextRun* textRun = f->GetTextRun(mWhichTextRun); 3110 if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { 3111 if (mMappedFlows[0].mStartFrame != GetFrameForSimpleFlow(textRun)) { 3112 NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!"); 3113 } 3114 } else { 3115 auto userData = 3116 static_cast<TextRunUserData*>(aTextRun->GetUserData()); 3117 TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun); 3118 if (userData->mMappedFlowCount >= mMappedFlows.Length() || 3119 userMappedFlows[userData->mMappedFlowCount - 1].mStartFrame != 3120 mMappedFlows[userdata->mMappedFlowCount - 1].mStartFrame) { 3121 NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!"); 3122 } 3123 } 3124 } 3125 #endif 3126 3127 gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun); 3128 if (oldTextRun) { 3129 nsTextFrame* firstFrame = nullptr; 3130 uint32_t startOffset = 0; 3131 if (oldTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { 3132 firstFrame = GetFrameForSimpleFlow(oldTextRun); 3133 } else { 3134 auto userData = 3135 static_cast<TextRunUserData*>(oldTextRun->GetUserData()); 3136 TextRunMappedFlow* userMappedFlows = GetMappedFlows(oldTextRun); 3137 firstFrame = userMappedFlows[0].mStartFrame; 3138 if (MOZ_UNLIKELY(f != firstFrame)) { 3139 TextRunMappedFlow* flow = 3140 FindFlowForContent(userData, f->GetContent(), userMappedFlows); 3141 if (flow) { 3142 startOffset = flow->mDOMOffsetToBeforeTransformOffset; 3143 } else { 3144 NS_ERROR("Can't find flow containing frame 'f'"); 3145 } 3146 } 3147 } 3148 3149 // Optimization: if |f| is the first frame in the flow then there are no 3150 // prev-continuations that use |oldTextRun|. 3151 nsTextFrame* clearFrom = nullptr; 3152 if (MOZ_UNLIKELY(f != firstFrame)) { 3153 // If all the frames in the mapped flow starting at |f| (inclusive) 3154 // are empty then we let the prev-continuations keep the old text run. 3155 gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset, 3156 f->GetContentOffset()); 3157 uint32_t textRunOffset = 3158 iter.ConvertOriginalToSkipped(f->GetContentOffset()); 3159 clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr; 3160 } 3161 f->ClearTextRun(clearFrom, mWhichTextRun); 3162 3163 #ifdef DEBUG 3164 if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) { 3165 // oldTextRun was destroyed - assert that we don't reference it. 3166 for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) { 3167 NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun, 3168 "destroyed text run is still in use"); 3169 } 3170 } 3171 #endif 3172 } 3173 f->SetTextRun(aTextRun, mWhichTextRun, aInflation); 3174 } 3175 // Set this bit now; we can't set it any earlier because 3176 // f->ClearTextRun() might clear it out. 3177 nsFrameState whichTextRunState = 3178 startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun 3179 ? TEXT_IN_TEXTRUN_USER_DATA 3180 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA; 3181 startFrame->AddStateBits(whichTextRunState); 3182 } 3183 } 3184 3185 NS_QUERYFRAME_HEAD(nsTextFrame) 3186 NS_QUERYFRAME_ENTRY(nsTextFrame) 3187 NS_QUERYFRAME_TAIL_INHERITING(nsIFrame) 3188 3189 gfxSkipCharsIterator nsTextFrame::EnsureTextRun( 3190 TextRunType aWhichTextRun, DrawTarget* aRefDrawTarget, 3191 nsIFrame* aLineContainer, const nsLineList::iterator* aLine, 3192 uint32_t* aFlowEndInTextRun) { 3193 gfxTextRun* textRun = GetTextRun(aWhichTextRun); 3194 if (!textRun || (aLine && (*aLine)->GetInvalidateTextRuns())) { 3195 RefPtr<DrawTarget> refDT = aRefDrawTarget; 3196 if (!refDT) { 3197 refDT = CreateReferenceDrawTarget(this); 3198 } 3199 if (refDT) { 3200 BuildTextRuns(refDT, this, aLineContainer, aLine, aWhichTextRun); 3201 } 3202 textRun = GetTextRun(aWhichTextRun); 3203 if (!textRun) { 3204 // A text run was not constructed for this frame. This is bad. The caller 3205 // will check mTextRun. 3206 return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(), 3207 0); 3208 } 3209 TabWidthStore* tabWidths = GetProperty(TabWidthProperty()); 3210 if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) { 3211 RemoveProperty(TabWidthProperty()); 3212 } 3213 } 3214 3215 if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { 3216 if (aFlowEndInTextRun) { 3217 *aFlowEndInTextRun = textRun->GetLength(); 3218 } 3219 return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset); 3220 } 3221 3222 auto userData = static_cast<TextRunUserData*>(textRun->GetUserData()); 3223 TextRunMappedFlow* userMappedFlows = GetMappedFlows(textRun); 3224 TextRunMappedFlow* flow = 3225 FindFlowForContent(userData, mContent, userMappedFlows); 3226 if (flow) { 3227 // Since textruns can only contain one flow for a given content element, 3228 // this must be our flow. 3229 uint32_t flowIndex = flow - userMappedFlows; 3230 userData->mLastFlowIndex = flowIndex; 3231 gfxSkipCharsIterator iter(textRun->GetSkipChars(), 3232 flow->mDOMOffsetToBeforeTransformOffset, 3233 mContentOffset); 3234 if (aFlowEndInTextRun) { 3235 if (flowIndex + 1 < userData->mMappedFlowCount) { 3236 gfxSkipCharsIterator end(textRun->GetSkipChars()); 3237 *aFlowEndInTextRun = end.ConvertOriginalToSkipped( 3238 flow[1].mStartFrame->GetContentOffset() + 3239 flow[1].mDOMOffsetToBeforeTransformOffset); 3240 } else { 3241 *aFlowEndInTextRun = textRun->GetLength(); 3242 } 3243 } 3244 return iter; 3245 } 3246 3247 NS_ERROR("Can't find flow containing this frame???"); 3248 return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(), 0); 3249 } 3250 3251 static uint32_t GetEndOfTrimmedText(const CharacterDataBuffer& aBuffer, 3252 const nsStyleText* aStyleText, 3253 uint32_t aStart, uint32_t aEnd, 3254 gfxSkipCharsIterator* aIterator, 3255 bool aAllowHangingWS = false) { 3256 aIterator->SetSkippedOffset(aEnd); 3257 while (aIterator->GetSkippedOffset() > aStart) { 3258 aIterator->AdvanceSkipped(-1); 3259 if (!IsTrimmableSpace(aBuffer, aIterator->GetOriginalOffset(), aStyleText, 3260 aAllowHangingWS)) { 3261 return aIterator->GetSkippedOffset() + 1; 3262 } 3263 } 3264 return aStart; 3265 } 3266 3267 nsTextFrame::TrimmedOffsets nsTextFrame::GetTrimmedOffsets( 3268 const class CharacterDataBuffer& aBuffer, TrimmedOffsetFlags aFlags) const { 3269 NS_ASSERTION(mTextRun, "Need textrun here"); 3270 if (!(aFlags & TrimmedOffsetFlags::NotPostReflow)) { 3271 // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS 3272 // to be set correctly. If our parent wasn't reflowed due to the frame 3273 // tree being too deep then the return value doesn't matter. 3274 NS_ASSERTION( 3275 !HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || 3276 GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE), 3277 "Can only call this on frames that have been reflowed"); 3278 NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IN_REFLOW), 3279 "Can only call this on frames that are not being reflowed"); 3280 } 3281 3282 TrimmedOffsets offsets = {GetContentOffset(), GetContentLength()}; 3283 const nsStyleText* textStyle = StyleText(); 3284 // Note that pre-line newlines should still allow us to trim spaces 3285 // for display 3286 if (textStyle->WhiteSpaceIsSignificant()) { 3287 return offsets; 3288 } 3289 3290 if (!(aFlags & TrimmedOffsetFlags::NoTrimBefore) && 3291 ((aFlags & TrimmedOffsetFlags::NotPostReflow) || 3292 HasAnyStateBits(TEXT_START_OF_LINE))) { 3293 int32_t whitespaceCount = GetTrimmableWhitespaceCount( 3294 aBuffer, offsets.mStart, offsets.mLength, 1); 3295 offsets.mStart += whitespaceCount; 3296 offsets.mLength -= whitespaceCount; 3297 } 3298 3299 if (!(aFlags & TrimmedOffsetFlags::NoTrimAfter) && 3300 ((aFlags & TrimmedOffsetFlags::NotPostReflow) || 3301 HasAnyStateBits(TEXT_END_OF_LINE))) { 3302 // This treats a trailing 'pre-line' newline as trimmable. That's fine, 3303 // it's actually what we want since we want whitespace before it to 3304 // be trimmed. 3305 int32_t whitespaceCount = GetTrimmableWhitespaceCount( 3306 aBuffer, offsets.GetEnd() - 1, offsets.mLength, -1); 3307 offsets.mLength -= whitespaceCount; 3308 } 3309 return offsets; 3310 } 3311 3312 static bool IsJustifiableCharacter(const nsStyleText* aTextStyle, 3313 const CharacterDataBuffer& aBuffer, 3314 int32_t aPos, bool aLangIsCJ) { 3315 NS_ASSERTION(aPos >= 0, "negative position?!"); 3316 3317 StyleTextJustify justifyStyle = aTextStyle->mTextJustify; 3318 if (justifyStyle == StyleTextJustify::None) { 3319 return false; 3320 } 3321 3322 const char16_t ch = aBuffer.CharAt(AssertedCast<uint32_t>(aPos)); 3323 if (ch == '\n' || ch == '\t' || ch == '\r') { 3324 return !aTextStyle->WhiteSpaceIsSignificant(); 3325 } 3326 if (ch == ' ' || ch == CH_NBSP) { 3327 // Don't justify spaces that are combined with diacriticals 3328 if (!aBuffer.Is2b()) { 3329 return true; 3330 } 3331 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail( 3332 aBuffer.Get2b() + aPos + 1, aBuffer.GetLength() - (aPos + 1)); 3333 } 3334 3335 if (justifyStyle == StyleTextJustify::InterCharacter) { 3336 char32_t u = aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aPos)); 3337 if (intl::UnicodeProperties::IsCursiveScript(u)) { 3338 return false; 3339 } 3340 return true; 3341 } else if (justifyStyle == StyleTextJustify::InterWord) { 3342 return false; 3343 } 3344 3345 // text-justify: auto 3346 if (ch < 0x2150u) { 3347 return false; 3348 } 3349 if (aLangIsCJ) { 3350 if ( // Number Forms, Arrows, Mathematical Operators 3351 (0x2150u <= ch && ch <= 0x22ffu) || 3352 // Enclosed Alphanumerics 3353 (0x2460u <= ch && ch <= 0x24ffu) || 3354 // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats 3355 (0x2580u <= ch && ch <= 0x27bfu) || 3356 // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B, 3357 // Miscellaneous Mathematical Symbols-B, 3358 // Supplemental Mathematical Operators, Miscellaneous Symbols and Arrows 3359 (0x27f0u <= ch && ch <= 0x2bffu) || 3360 // CJK Radicals Supplement, CJK Radicals Supplement, Ideographic 3361 // Description Characters, CJK Symbols and Punctuation, Hiragana, 3362 // Katakana, Bopomofo 3363 (0x2e80u <= ch && ch <= 0x312fu) || 3364 // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions, 3365 // Enclosed CJK Letters and Months, CJK Compatibility, 3366 // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols, 3367 // CJK Unified Ideographs, Yi Syllables, Yi Radicals 3368 (0x3190u <= ch && ch <= 0xabffu) || 3369 // CJK Compatibility Ideographs 3370 (0xf900u <= ch && ch <= 0xfaffu) || 3371 // Halfwidth and Fullwidth Forms (a part) 3372 (0xff5eu <= ch && ch <= 0xff9fu)) { 3373 return true; 3374 } 3375 if (NS_IS_HIGH_SURROGATE(ch)) { 3376 if (char32_t u = aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aPos))) { 3377 // CJK Unified Ideographs Extension B, 3378 // CJK Unified Ideographs Extension C, 3379 // CJK Unified Ideographs Extension D, 3380 // CJK Compatibility Ideographs Supplement 3381 if (0x20000u <= u && u <= 0x2ffffu) { 3382 return true; 3383 } 3384 } 3385 } 3386 } 3387 return false; 3388 } 3389 3390 void nsTextFrame::ClearMetrics(ReflowOutput& aMetrics) { 3391 aMetrics.ClearSize(); 3392 aMetrics.SetBlockStartAscent(0); 3393 mAscent = 0; 3394 3395 AddStateBits(TEXT_NO_RENDERED_GLYPHS); 3396 } 3397 3398 static int32_t FindChar(const CharacterDataBuffer& characterDataBuffer, 3399 int32_t aOffset, int32_t aLength, char16_t ch) { 3400 int32_t i = 0; 3401 if (characterDataBuffer.Is2b()) { 3402 const char16_t* str = characterDataBuffer.Get2b() + aOffset; 3403 for (; i < aLength; ++i) { 3404 if (*str == ch) { 3405 return i + aOffset; 3406 } 3407 ++str; 3408 } 3409 } else { 3410 if (uint16_t(ch) <= 0xFF) { 3411 const char* str = characterDataBuffer.Get1b() + aOffset; 3412 const void* p = memchr(str, ch, aLength); 3413 if (p) { 3414 return (static_cast<const char*>(p) - str) + aOffset; 3415 } 3416 } 3417 } 3418 return -1; 3419 } 3420 3421 static bool IsChineseOrJapanese(const nsTextFrame* aFrame) { 3422 if (aFrame->ShouldSuppressLineBreak()) { 3423 // Always treat ruby as CJ language so that those characters can 3424 // be expanded properly even when surrounded by other language. 3425 return true; 3426 } 3427 3428 nsAtom* language = aFrame->StyleFont()->mLanguage; 3429 if (!language) { 3430 return false; 3431 } 3432 return nsStyleUtil::MatchesLanguagePrefix(language, u"ja") || 3433 nsStyleUtil::MatchesLanguagePrefix(language, u"zh"); 3434 } 3435 3436 #ifdef DEBUG 3437 static bool IsInBounds(const gfxSkipCharsIterator& aStart, 3438 int32_t aContentLength, gfxTextRun::Range aRange) { 3439 if (aStart.GetSkippedOffset() > aRange.start) { 3440 return false; 3441 } 3442 if (aContentLength == INT32_MAX) { 3443 return true; 3444 } 3445 gfxSkipCharsIterator iter(aStart); 3446 iter.AdvanceOriginal(aContentLength); 3447 return iter.GetSkippedOffset() >= aRange.end; 3448 } 3449 #endif 3450 3451 nsTextFrame::PropertyProvider::PropertyProvider( 3452 gfxTextRun* aTextRun, const nsStyleText* aTextStyle, 3453 const class CharacterDataBuffer& aBuffer, nsTextFrame* aFrame, 3454 const gfxSkipCharsIterator& aStart, int32_t aLength, 3455 nsIFrame* aLineContainer, nscoord aOffsetFromBlockOriginForTabs, 3456 nsTextFrame::TextRunType aWhichTextRun, bool aAtStartOfLine) 3457 : mTextRun(aTextRun), 3458 mFontGroup(nullptr), 3459 mTextStyle(aTextStyle), 3460 mCharacterDataBuffer(aBuffer), 3461 mLineContainer(aLineContainer), 3462 mFrame(aFrame), 3463 mStart(aStart), 3464 mTempIterator(aStart), 3465 mTabWidths(nullptr), 3466 mTabWidthsAnalyzedLimit(0), 3467 mLength(aLength), 3468 mWordSpacing(WordSpacing(aFrame, *aTextStyle)), 3469 mLetterSpacing(LetterSpacing(aFrame, *aTextStyle)), 3470 mMinTabAdvance(-1.0), 3471 mHyphenWidth(-1), 3472 mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs), 3473 mJustificationArrayStart(0), 3474 mReflowing(true), 3475 mWhichTextRun(aWhichTextRun) { 3476 NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?"); 3477 if (aAtStartOfLine) { 3478 mStartOfLineOffset = mStart.GetSkippedOffset(); 3479 } 3480 InitTextAutospace(); 3481 } 3482 3483 nsTextFrame::PropertyProvider::PropertyProvider( 3484 nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart, 3485 nsTextFrame::TextRunType aWhichTextRun, nsFontMetrics* aFontMetrics) 3486 : mTextRun(aFrame->GetTextRun(aWhichTextRun)), 3487 mFontGroup(nullptr), 3488 mFontMetrics(aFontMetrics), 3489 mTextStyle(aFrame->StyleText()), 3490 mCharacterDataBuffer(aFrame->CharacterDataBuffer()), 3491 mLineContainer(nullptr), 3492 mFrame(aFrame), 3493 mStart(aStart), 3494 mTempIterator(aStart), 3495 mTabWidths(nullptr), 3496 mTabWidthsAnalyzedLimit(0), 3497 mLength(aFrame->GetContentLength()), 3498 mWordSpacing(WordSpacing(aFrame, *mTextStyle)), 3499 mLetterSpacing(LetterSpacing(aFrame, *mTextStyle)), 3500 mMinTabAdvance(-1.0), 3501 mHyphenWidth(-1), 3502 mOffsetFromBlockOriginForTabs(0), 3503 mJustificationArrayStart(0), 3504 mReflowing(false), 3505 mWhichTextRun(aWhichTextRun) { 3506 NS_ASSERTION(mTextRun, "Textrun not initialized!"); 3507 InitTextAutospace(); 3508 } 3509 3510 gfx::ShapedTextFlags nsTextFrame::PropertyProvider::GetShapedTextFlags() const { 3511 return nsLayoutUtils::GetTextRunOrientFlagsForStyle(mFrame->Style()); 3512 } 3513 3514 already_AddRefed<DrawTarget> nsTextFrame::PropertyProvider::GetDrawTarget() 3515 const { 3516 return CreateReferenceDrawTarget(GetFrame()); 3517 } 3518 3519 gfxFloat nsTextFrame::PropertyProvider::MinTabAdvance() const { 3520 if (mMinTabAdvance < 0.0) { 3521 mMinTabAdvance = GetMinTabAdvanceAppUnits(mTextRun); 3522 } 3523 return mMinTabAdvance; 3524 } 3525 3526 /** 3527 * Finds the offset of the first character of the cluster containing aPos 3528 */ 3529 static void FindClusterStart(const gfxTextRun* aTextRun, int32_t aOriginalStart, 3530 gfxSkipCharsIterator* aPos) { 3531 while (aPos->GetOriginalOffset() > aOriginalStart) { 3532 if (aPos->IsOriginalCharSkipped() || 3533 aTextRun->IsClusterStart(aPos->GetSkippedOffset())) { 3534 break; 3535 } 3536 aPos->AdvanceOriginal(-1); 3537 } 3538 } 3539 3540 /** 3541 * Finds the offset of the last character of the cluster containing aPos. 3542 * If aAllowSplitLigature is false, we also check for a ligature-group 3543 * start. 3544 */ 3545 static void FindClusterEnd(const gfxTextRun* aTextRun, int32_t aOriginalEnd, 3546 gfxSkipCharsIterator* aPos, 3547 bool aAllowSplitLigature = true) { 3548 MOZ_ASSERT(aPos->GetOriginalOffset() < aOriginalEnd, 3549 "character outside string"); 3550 3551 aPos->AdvanceOriginal(1); 3552 while (aPos->GetOriginalOffset() < aOriginalEnd) { 3553 if (aPos->IsOriginalCharSkipped() || 3554 (aTextRun->IsClusterStart(aPos->GetSkippedOffset()) && 3555 (aAllowSplitLigature || 3556 aTextRun->IsLigatureGroupStart(aPos->GetSkippedOffset())))) { 3557 break; 3558 } 3559 aPos->AdvanceOriginal(1); 3560 } 3561 aPos->AdvanceOriginal(-1); 3562 } 3563 3564 // Get the line number of aFrame in the lines referenced by aLineIter, if 3565 // known (returning -1 if we don't find it). 3566 static int32_t GetFrameLineNum(nsIFrame* aFrame, nsILineIterator* aLineIter) { 3567 if (!aLineIter) { 3568 return -1; 3569 } 3570 // If we don't find the frame directly, but its parent is an inline or other 3571 // "line participant" (e.g. nsFirstLineFrame), we want the line that the 3572 // inline ancestor is on. 3573 do { 3574 int32_t n = aLineIter->FindLineContaining(aFrame); 3575 if (n >= 0) { 3576 return n; 3577 } 3578 aFrame = aFrame->GetParent(); 3579 } while (aFrame && aFrame->IsLineParticipant()); 3580 return -1; 3581 } 3582 3583 // Get the position of the first preserved newline in aFrame, if any, 3584 // returning -1 if none. 3585 static int32_t FindFirstNewlinePosition(const nsTextFrame* aFrame) { 3586 MOZ_ASSERT(aFrame->StyleText()->NewlineIsSignificantStyle(), 3587 "how did the HasNewline flag get set?"); 3588 const auto& characterDataBuffer = aFrame->CharacterDataBuffer(); 3589 for (auto i = aFrame->GetContentOffset(); i < aFrame->GetContentEnd(); ++i) { 3590 if (characterDataBuffer.CharAt(i) == '\n') { 3591 return i; 3592 } 3593 } 3594 return -1; 3595 } 3596 3597 // Get the position of the last preserved tab in aFrame that is before the 3598 // preserved newline at aNewlinePos. 3599 // Passing -1 for aNewlinePos means there is no preserved newline, so we look 3600 // for the last preserved tab in the whole content. 3601 // Returns -1 if no such preserved tab is present. 3602 static int32_t FindLastTabPositionBeforeNewline(const nsTextFrame* aFrame, 3603 int32_t aNewlinePos) { 3604 // We only call this if white-space is not being collapsed. 3605 MOZ_ASSERT(aFrame->StyleText()->WhiteSpaceIsSignificant(), 3606 "how did the HasTab flag get set?"); 3607 const auto& characterDataBuffer = aFrame->CharacterDataBuffer(); 3608 // If a non-negative newline position was given, we only need to search the 3609 // text before that offset. 3610 for (auto i = aNewlinePos < 0 ? aFrame->GetContentEnd() : aNewlinePos; 3611 i > aFrame->GetContentOffset(); --i) { 3612 if (characterDataBuffer.CharAt(i - 1) == '\t') { 3613 return i; 3614 } 3615 } 3616 return -1; 3617 } 3618 3619 // Look for preserved tab or newline in the given frame or its following 3620 // siblings on the same line, to determine whether justification should be 3621 // suppressed in order to avoid disrupting tab-stop positions. 3622 // Returns the first such preserved whitespace char, or 0 if none found. 3623 static char NextPreservedWhiteSpaceOnLine(nsIFrame* aSibling, 3624 nsILineIterator* aLineIter, 3625 int32_t aLineNum) { 3626 while (aSibling) { 3627 // If we find a <br>, treat it like a newline. 3628 if (aSibling->IsBrFrame()) { 3629 return '\n'; 3630 } 3631 // If we've moved on to a later line, stop searching. 3632 if (GetFrameLineNum(aSibling, aLineIter) > aLineNum) { 3633 return 0; 3634 } 3635 // If we encounter an inline frame, recurse into it. 3636 if (aSibling->IsInlineFrame()) { 3637 auto* child = aSibling->PrincipalChildList().FirstChild(); 3638 char result = NextPreservedWhiteSpaceOnLine(child, aLineIter, aLineNum); 3639 if (result) { 3640 return result; 3641 } 3642 } 3643 // If we have a text frame, and whitespace is not collapsed, we need to 3644 // check its contents. 3645 if (aSibling->IsTextFrame()) { 3646 const auto* textStyle = aSibling->StyleText(); 3647 if (textStyle->WhiteSpaceOrNewlineIsSignificant()) { 3648 const auto* textFrame = static_cast<nsTextFrame*>(aSibling); 3649 const auto& characterDataBuffer = textFrame->CharacterDataBuffer(); 3650 for (auto i = textFrame->GetContentOffset(); 3651 i < textFrame->GetContentEnd(); ++i) { 3652 const char16_t ch = characterDataBuffer.CharAt(i); 3653 if (ch == '\n' && textStyle->NewlineIsSignificantStyle()) { 3654 return '\n'; 3655 } 3656 if (ch == '\t' && textStyle->WhiteSpaceIsSignificant()) { 3657 return '\t'; 3658 } 3659 } 3660 } 3661 } 3662 aSibling = aSibling->GetNextSibling(); 3663 } 3664 return 0; 3665 } 3666 3667 static bool HasPreservedTabInFollowingSiblingOnLine(nsTextFrame* aFrame) { 3668 bool foundTab = false; 3669 3670 nsIFrame* lineContainer = FindLineContainer(aFrame); 3671 nsILineIterator* iter = lineContainer->GetLineIterator(); 3672 int32_t line = GetFrameLineNum(aFrame, iter); 3673 char ws = NextPreservedWhiteSpaceOnLine(aFrame->GetNextSibling(), iter, line); 3674 if (ws == '\t') { 3675 foundTab = true; 3676 } else if (!ws) { 3677 // Didn't find a preserved tab or newline in our siblings; if our parent 3678 // (and its parent, etc) is an inline, we need to look at their following 3679 // siblings, too, as long as they're on the same line. 3680 const nsIFrame* maybeInline = aFrame->GetParent(); 3681 while (maybeInline && maybeInline->IsInlineFrame()) { 3682 ws = NextPreservedWhiteSpaceOnLine(maybeInline->GetNextSibling(), iter, 3683 line); 3684 if (ws == '\t') { 3685 foundTab = true; 3686 break; 3687 } 3688 if (ws == '\n') { 3689 break; 3690 } 3691 maybeInline = maybeInline->GetParent(); 3692 } 3693 } 3694 3695 // We called lineContainer->GetLineIterator() above, but we mustn't 3696 // allow a block frame to retain this iterator if we're currently in 3697 // reflow, as it will become invalid as the line list is reflowed. 3698 if (lineContainer->HasAnyStateBits(NS_FRAME_IN_REFLOW) && 3699 lineContainer->IsBlockFrameOrSubclass()) { 3700 static_cast<nsBlockFrame*>(lineContainer)->ClearLineIterator(); 3701 } 3702 3703 return foundTab; 3704 } 3705 3706 JustificationInfo nsTextFrame::PropertyProvider::ComputeJustification( 3707 Range aRange, nsTArray<JustificationAssignment>* aAssignments) { 3708 JustificationInfo info; 3709 3710 // Horizontal-in-vertical frame is orthogonal to the line, so it 3711 // doesn't actually include any justification opportunity inside. 3712 // The spec says such frame should be treated as a U+FFFC. Since we 3713 // do not insert justification opportunities on the sides of that 3714 // character, the sides of this frame are not justifiable either. 3715 if (mFrame->Style()->IsTextCombined()) { 3716 return info; 3717 } 3718 3719 int32_t lastTab = -1; 3720 if (StaticPrefs::layout_css_text_align_justify_only_after_last_tab()) { 3721 // If there is a preserved tab on the line, we don't apply justification 3722 // until we're past its position. 3723 if (mTextStyle->WhiteSpaceIsSignificant()) { 3724 // If there is a preserved newline within the text, we don't need to look 3725 // beyond this frame, as following frames will not be on the same line. 3726 int32_t newlinePos = 3727 (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasNewline) 3728 ? FindFirstNewlinePosition(mFrame) 3729 : -1; 3730 if (newlinePos < 0) { 3731 // There's no preserved newline within this frame; if there's a tab 3732 // in a later sibling frame on the same line, we won't apply any 3733 // justification to this one. 3734 if (HasPreservedTabInFollowingSiblingOnLine(mFrame)) { 3735 return info; 3736 } 3737 } 3738 3739 if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab) { 3740 // Find last tab character in the content; we won't justify anything 3741 // before that position, so that tab alignment remains correct. 3742 lastTab = FindLastTabPositionBeforeNewline(mFrame, newlinePos); 3743 } 3744 } 3745 } 3746 3747 bool isCJ = IsChineseOrJapanese(mFrame); 3748 nsSkipCharsRunIterator run( 3749 mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aRange.Length()); 3750 run.SetOriginalOffset(aRange.start); 3751 mJustificationArrayStart = run.GetSkippedOffset(); 3752 3753 nsTArray<JustificationAssignment> assignments; 3754 assignments.SetCapacity(aRange.Length()); 3755 while (run.NextRun()) { 3756 uint32_t originalOffset = run.GetOriginalOffset(); 3757 uint32_t skippedOffset = run.GetSkippedOffset(); 3758 uint32_t length = run.GetRunLength(); 3759 assignments.SetLength(skippedOffset + length - mJustificationArrayStart); 3760 3761 gfxSkipCharsIterator iter = run.GetPos(); 3762 for (uint32_t i = 0; i < length; ++i) { 3763 uint32_t offset = originalOffset + i; 3764 if (!IsJustifiableCharacter(mTextStyle, mCharacterDataBuffer, offset, 3765 isCJ) || 3766 (lastTab >= 0 && offset <= uint32_t(lastTab))) { 3767 continue; 3768 } 3769 3770 iter.SetOriginalOffset(offset); 3771 3772 FindClusterStart(mTextRun, originalOffset, &iter); 3773 uint32_t firstCharOffset = iter.GetSkippedOffset(); 3774 uint32_t firstChar = firstCharOffset > mJustificationArrayStart 3775 ? firstCharOffset - mJustificationArrayStart 3776 : 0; 3777 if (!firstChar) { 3778 info.mIsStartJustifiable = true; 3779 } else { 3780 auto& assign = assignments[firstChar]; 3781 auto& prevAssign = assignments[firstChar - 1]; 3782 if (prevAssign.mGapsAtEnd) { 3783 prevAssign.mGapsAtEnd = 1; 3784 assign.mGapsAtStart = 1; 3785 } else { 3786 assign.mGapsAtStart = 2; 3787 info.mInnerOpportunities++; 3788 } 3789 } 3790 3791 FindClusterEnd(mTextRun, originalOffset + length, &iter); 3792 uint32_t lastChar = iter.GetSkippedOffset() - mJustificationArrayStart; 3793 // Assign the two gaps temporary to the last char. If the next cluster is 3794 // justifiable as well, one of the gaps will be removed by code above. 3795 assignments[lastChar].mGapsAtEnd = 2; 3796 info.mInnerOpportunities++; 3797 3798 // Skip the whole cluster 3799 i = iter.GetOriginalOffset() - originalOffset; 3800 } 3801 } 3802 3803 if (!assignments.IsEmpty() && assignments.LastElement().mGapsAtEnd) { 3804 // We counted the expansion opportunity after the last character, 3805 // but it is not an inner opportunity. 3806 MOZ_ASSERT(info.mInnerOpportunities > 0); 3807 info.mInnerOpportunities--; 3808 info.mIsEndJustifiable = true; 3809 } 3810 3811 if (aAssignments) { 3812 *aAssignments = std::move(assignments); 3813 } 3814 return info; 3815 } 3816 3817 // aStart, aLength in transformed string offsets. 3818 // Returns false if no non-standard spacing was required. 3819 bool nsTextFrame::PropertyProvider::GetSpacing(Range aRange, 3820 Spacing* aSpacing) const { 3821 return GetSpacingInternal( 3822 aRange, aSpacing, 3823 !(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab)); 3824 } 3825 3826 static bool CanAddSpacingBefore(const gfxTextRun* aTextRun, uint32_t aOffset, 3827 bool aNewlineIsSignificant) { 3828 const auto* g = aTextRun->GetCharacterGlyphs(); 3829 MOZ_ASSERT(aOffset < aTextRun->GetLength()); 3830 if (aNewlineIsSignificant && g[aOffset].CharIsNewline()) { 3831 return false; 3832 } 3833 if (!aOffset) { 3834 return true; 3835 } 3836 return g[aOffset].IsClusterStart() && g[aOffset].IsLigatureGroupStart() && 3837 !g[aOffset - 1].CharIsFormattingControl() && !g[aOffset].CharIsTab(); 3838 } 3839 3840 static bool CanAddSpacingAfter(const gfxTextRun* aTextRun, uint32_t aOffset, 3841 bool aNewlineIsSignificant) { 3842 const auto* g = aTextRun->GetCharacterGlyphs(); 3843 MOZ_ASSERT(aOffset < aTextRun->GetLength()); 3844 if (aNewlineIsSignificant && g[aOffset].CharIsNewline()) { 3845 return false; 3846 } 3847 if (aOffset + 1 >= aTextRun->GetLength()) { 3848 return true; 3849 } 3850 return g[aOffset + 1].IsClusterStart() && 3851 g[aOffset + 1].IsLigatureGroupStart() && 3852 !g[aOffset].CharIsFormattingControl() && !g[aOffset].CharIsTab(); 3853 } 3854 3855 static gfxFloat ComputeTabWidthAppUnits(const nsIFrame* aFrame) { 3856 const auto& tabSize = aFrame->StyleText()->mTabSize; 3857 if (tabSize.IsLength()) { 3858 nscoord w = tabSize.length._0.ToAppUnits(); 3859 MOZ_ASSERT(w >= 0); 3860 return w; 3861 } 3862 3863 MOZ_ASSERT(tabSize.IsNumber()); 3864 gfxFloat spaces = tabSize.number._0; 3865 MOZ_ASSERT(spaces >= 0); 3866 3867 const nsIFrame* cb = aFrame->GetContainingBlock(0, aFrame->StyleDisplay()); 3868 const auto* styleText = cb->StyleText(); 3869 3870 // Round the space width when converting to appunits the same way textruns do. 3871 // We don't use GetFirstFontMetrics here because that may return a font that 3872 // does not actually have the <space> character, yet is considered the "first 3873 // available font" per CSS Fonts. Here, we want the font that would be used 3874 // to render <space>, even if that means looking further down the font-family 3875 // list. 3876 RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(cb, 1.0f); 3877 bool vertical = cb->GetWritingMode().IsCentralBaseline(); 3878 RefPtr font = fm->GetThebesFontGroup()->GetFirstValidFont(' '); 3879 auto metrics = font->GetMetrics(vertical ? nsFontMetrics::eVertical 3880 : nsFontMetrics::eHorizontal); 3881 nscoord spaceWidth = NSToCoordRound(metrics.spaceWidth * 3882 cb->PresContext()->AppUnitsPerDevPixel()); 3883 return spaces * 3884 (spaceWidth + styleText->mLetterSpacing.Resolve(fm->EmHeight()) + 3885 styleText->mWordSpacing.Resolve(spaceWidth)); 3886 } 3887 3888 // Walk backward from aIter to prior cluster starts (within the same textframe's 3889 // content) and return the first non-mark autospace class. 3890 // 3891 // @param aContentOffsetAtFrameStart the original content offset at the start of 3892 // the textframe. 3893 static Maybe<TextAutospace::CharClass> LastNonMarkCharClass( 3894 gfxSkipCharsIterator& aIter, int32_t aContentOffsetAtFrameStart, 3895 const gfxTextRun* aTextRun, const CharacterDataBuffer& aBuffer) { 3896 while (aIter.GetOriginalOffset() > aContentOffsetAtFrameStart) { 3897 aIter.AdvanceOriginal(-1); 3898 FindClusterStart(aTextRun, aContentOffsetAtFrameStart, &aIter); 3899 const char32_t ch = aBuffer.ScalarValueAt(aIter.GetOriginalOffset()); 3900 auto cls = TextAutospace::GetCharClass(ch); 3901 if (cls != TextAutospace::CharClass::CombiningMark) { 3902 return Some(cls); 3903 } 3904 } 3905 return Nothing(); 3906 } 3907 3908 // Walk backward through the frame's content and return the first non-mark 3909 // autospace class. (Unlike the function above, this is usable when the frame 3910 // does not currently have a textrun.) 3911 static Maybe<TextAutospace::CharClass> LastNonMarkCharClass( 3912 const nsTextFrame* aFrame) { 3913 using CharClass = TextAutospace::CharClass; 3914 bool trimSpace = aFrame->HasAnyStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE); 3915 const auto& buffer = aFrame->CharacterDataBuffer(); 3916 const uint32_t startOffset = aFrame->GetContentOffset(); 3917 uint32_t i = aFrame->GetContentEnd(); 3918 while (i > startOffset) { 3919 // Get trailing character, decoding surrogate pair if necessary. 3920 char32_t ch = buffer.CharAt(--i); 3921 if (NS_IS_LOW_SURROGATE(ch) && i > startOffset) { 3922 // Get potential high surrogate, and decode. 3923 char32 hi = buffer.CharAt(i - 1); 3924 if (NS_IS_HIGH_SURROGATE(hi)) { 3925 ch = SURROGATE_TO_UCS4(hi, ch); 3926 --i; 3927 } 3928 } 3929 // Skip over trailing whitespace if the frame was trimmed. 3930 if (trimSpace) { 3931 if (IsTrimmableSpace(ch)) { 3932 continue; 3933 } 3934 trimSpace = false; 3935 } 3936 // If it has a non-CombiningMark class, return it. 3937 auto cls = TextAutospace::GetCharClass(ch); 3938 if (cls != CharClass::CombiningMark) { 3939 return Some(cls); 3940 } 3941 } 3942 // No (non-mark, non-trimmed) characters were found. 3943 return Nothing(); 3944 } 3945 3946 // Return the first non-mark autospace class from the end of content in aFrame. 3947 static Maybe<TextAutospace::CharClass> LastNonMarkCharClassInFrame( 3948 nsTextFrame* aFrame) { 3949 using CharClass = TextAutospace::CharClass; 3950 if (!aFrame->GetContentLength()) { 3951 return Nothing(); 3952 } 3953 Maybe<CharClass> prevClass; 3954 if (aFrame->GetTextRun(nsTextFrame::eInflated)) { 3955 // If the frame has a textrun, we can use that to find the last cluster 3956 // start character and return its class. 3957 gfxSkipCharsIterator iter = aFrame->EnsureTextRun(nsTextFrame::eInflated); 3958 iter.SetOriginalOffset(aFrame->GetContentEnd()); 3959 prevClass = LastNonMarkCharClass(iter, aFrame->GetContentOffset(), 3960 aFrame->GetTextRun(nsTextFrame::eInflated), 3961 aFrame->CharacterDataBuffer()); 3962 } else { 3963 // We can't call EnsureTextRun if it would build new textruns, because that 3964 // could destroy the glyph runs that we're currently iterating over. So 3965 // instead we fall back to inspecting the content directly. This means we 3966 // ignore CSS whitespace-collapsing, which could mean some of the content 3967 // should be skipped, but in practice none of the characters that are 3968 // relevant for autospace classes would be affected. 3969 prevClass = LastNonMarkCharClass(aFrame); 3970 } 3971 if (prevClass) { 3972 return prevClass; 3973 } 3974 if (aFrame->GetPrevInFlow()) { 3975 // If aFrame has a prev-in-flow, it is after a line-break, so autospace does 3976 // not apply here; just return Other. 3977 return Some(CharClass::Other); 3978 } 3979 return Nothing(); 3980 } 3981 3982 // Look for the autospace class of the content preceding the given aFrame 3983 // in the mapped flows of the current textrun. 3984 static Maybe<TextAutospace::CharClass> GetPrecedingCharClassFromMappedFlows( 3985 const nsTextFrame* aFrame, const gfxTextRun* aTextRun) { 3986 using CharClass = TextAutospace::CharClass; 3987 3988 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { 3989 return Nothing(); 3990 } 3991 3992 auto data = static_cast<TextRunUserData*>(aTextRun->GetUserData()); 3993 if (!data) { 3994 return Nothing(); 3995 } 3996 TextRunMappedFlow* mappedFlows = GetMappedFlows(aTextRun); 3997 3998 // Search for aFrame in the mapped flows. 3999 uint32_t i = 0; 4000 for (; i < data->mMappedFlowCount; ++i) { 4001 if (mappedFlows[i].mStartFrame == aFrame) { 4002 break; 4003 } 4004 } 4005 MOZ_ASSERT(mappedFlows[i].mStartFrame == aFrame, 4006 "aFrame not found in mapped flows!"); 4007 4008 while (i > 0) { 4009 nsTextFrame* f = mappedFlows[--i].mStartFrame->LastInFlow(); 4010 if (Maybe<CharClass> prevClass = LastNonMarkCharClassInFrame(f)) { 4011 return prevClass; 4012 } 4013 } 4014 return Nothing(); 4015 } 4016 4017 // Look for the autospace class of content preceding the given frame. 4018 static Maybe<TextAutospace::CharClass> GetPrecedingCharClassFromFrameTree( 4019 nsIFrame* aFrame) { 4020 using CharClass = TextAutospace::CharClass; 4021 while (!aFrame->GetPrevSibling() && aFrame->GetParent()->IsInlineFrame()) { 4022 // If this is the first child of an inline container, we want to ascend to 4023 // the parent and look at what precedes it. 4024 aFrame = aFrame->GetParent(); 4025 } 4026 aFrame = aFrame->GetPrevSibling(); 4027 while (aFrame) { 4028 if (aFrame->IsPlaceholderFrame()) { 4029 // Skip over out-of-flow placeholders. 4030 aFrame = aFrame->GetPrevSibling(); 4031 continue; 4032 } 4033 if (aFrame->IsInlineFrame()) { 4034 // Descend into inline containers and go backwards through their content. 4035 aFrame = aFrame->PrincipalChildList().LastChild(); 4036 continue; 4037 } 4038 if (nsTextFrame* f = do_QueryFrame(aFrame)) { 4039 // Look for the class of the last character in the textframe. 4040 Maybe<CharClass> prevClass = LastNonMarkCharClassInFrame(f); 4041 if (prevClass) { 4042 if ((*prevClass == CharClass::NonIdeographicLetter || 4043 *prevClass == CharClass::NonIdeographicNumeral) && 4044 TextAutospace::ShouldSuppressLetterNumeralSpacing(f)) { 4045 return Some(CharClass::Other); 4046 } 4047 return prevClass; 4048 } 4049 aFrame = aFrame->GetPrevSibling(); 4050 continue; 4051 } 4052 return Nothing(); 4053 } 4054 return Nothing(); 4055 } 4056 4057 static bool HasCJKGlyphRun(const gfxTextRun* aTextRun) { 4058 uint32_t numGlyphRuns; 4059 const gfxTextRun::GlyphRun* run = aTextRun->GetGlyphRuns(&numGlyphRuns); 4060 while (numGlyphRuns-- > 0) { 4061 if (run->mIsCJK) { 4062 return true; 4063 } 4064 run++; 4065 } 4066 return false; 4067 } 4068 4069 bool nsTextFrame::PropertyProvider::GetSpacingInternal(Range aRange, 4070 Spacing* aSpacing, 4071 bool aIgnoreTabs) const { 4072 MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds"); 4073 4074 std::memset(aSpacing, 0, aRange.Length() * sizeof(*aSpacing)); 4075 4076 if (mFrame->Style()->IsTextCombined()) { 4077 return false; 4078 } 4079 4080 // Track whether any non-standard spacing is actually present in this range. 4081 // If letter-spacing is non-zero this will always be true, but for the 4082 /// word-spacing and text-autospace cases it will depend on the actual text. 4083 bool spacingPresent = mLetterSpacing; 4084 4085 // First, compute the word spacing, letter spacing, and text-autospace 4086 // spacing. 4087 if (mWordSpacing || mLetterSpacing || mTextAutospace) { 4088 bool newlineIsSignificant = mTextStyle->NewlineIsSignificant(mFrame); 4089 // Which letter-spacing model are we using? 4090 // 0 - Gecko legacy model, spacing added to trailing side of letter 4091 // 1 - WebKit/Blink-compatible, spacing added to right-hand side 4092 // 2 - Symmetrical spacing, half added to each side 4093 nscoord before, after; 4094 switch (StaticPrefs::layout_css_letter_spacing_model()) { 4095 default: // use Gecko legacy behavior if pref value is unknown 4096 case 0: 4097 before = 0; 4098 after = mLetterSpacing; 4099 break; 4100 case 1: 4101 if (mTextRun->IsRightToLeft()) { 4102 before = mLetterSpacing; 4103 after = 0; 4104 } else { 4105 before = 0; 4106 after = mLetterSpacing; 4107 } 4108 break; 4109 case 2: 4110 before = NSToCoordRound(mLetterSpacing * 0.5); 4111 after = mLetterSpacing - before; 4112 break; 4113 } 4114 4115 // Find our offset into the original+transformed string 4116 gfxSkipCharsIterator start(mStart); 4117 start.SetSkippedOffset(aRange.start); 4118 bool atStart = mStartOfLineOffset == start.GetSkippedOffset() && 4119 !mFrame->IsInSVGTextSubtree(); 4120 4121 using CharClass = TextAutospace::CharClass; 4122 // The non-mark class of a previous character at a cluster start (if any). 4123 Maybe<CharClass> prevClass; 4124 4125 // Initialization of prevClass at start-of-frame may be a bit expensive, 4126 // and we don't always need that initial value, so we encapsulate it in a 4127 // helper to be called on-demand. 4128 auto findPrecedingClass = [&]() -> CharClass { 4129 // Get the class of the character immediately before the current aRange. 4130 Maybe<CharClass> prevClass; 4131 if (aRange.start > 0) { 4132 gfxSkipCharsIterator iter = start; 4133 prevClass = LastNonMarkCharClass(iter, mFrame->GetContentOffset(), 4134 mTextRun, mCharacterDataBuffer); 4135 } 4136 // If no class was found, we need to look at the preceding content (if 4137 // any) to see what it ended with. 4138 if (!prevClass) { 4139 // If we have a prev-in-flow, we're after a line-break, so autospace 4140 // does not apply here; just set prevClass to Other. 4141 if (mFrame->GetPrevInFlow()) { 4142 prevClass = Some(CharClass::Other); 4143 } else { 4144 // If the textrun is mapping multiple content flows, we may be able 4145 // to find preceding content from there (without having to walk the 4146 // potentially more complex frame tree). 4147 prevClass = GetPrecedingCharClassFromMappedFlows(mFrame, mTextRun); 4148 // If we couldn't get it from an earlier flow covered by the textrun, 4149 // we'll have to delve into the frame tree to see what preceded this. 4150 if (!prevClass) { 4151 prevClass = GetPrecedingCharClassFromFrameTree(mFrame); 4152 } 4153 } 4154 } 4155 // If no valid class was found, return `Other`, which never participates 4156 // in autospacing rules. 4157 return prevClass.valueOr(CharClass::Other); 4158 }; 4159 4160 // If text-autospace is enabled, we may be able to skip some processing if 4161 // there are no CJK glyphs in the textrun, so check for their presence. 4162 bool textIncludesCJK = mTextAutospace && mCharacterDataBuffer.Is2b() && 4163 HasCJKGlyphRun(mTextRun); 4164 4165 // Iterate over non-skipped characters 4166 nsSkipCharsRunIterator run( 4167 start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length()); 4168 while (run.NextRun()) { 4169 uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start; 4170 gfxSkipCharsIterator iter = run.GetPos(); 4171 for (int32_t i = 0; i < run.GetRunLength(); ++i) { 4172 auto currScalar = [&]() -> char32_t { 4173 iter.SetSkippedOffset(run.GetSkippedOffset() + i); 4174 return mCharacterDataBuffer.ScalarValueAt(iter.GetOriginalOffset()); 4175 }; 4176 if (!atStart && before != 0 && 4177 CanAddSpacingBefore(mTextRun, run.GetSkippedOffset() + i, 4178 newlineIsSignificant) && 4179 !intl::UnicodeProperties::IsCursiveScript(currScalar())) { 4180 aSpacing[runOffsetInSubstring + i].mBefore += before; 4181 } 4182 if (after != 0 && 4183 CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i, 4184 newlineIsSignificant)) { 4185 // End of a cluster, not in a ligature: put letter-spacing after it, 4186 // unless the base char of the cluster belonged to a cursive script. 4187 iter.SetSkippedOffset(run.GetSkippedOffset() + i); 4188 FindClusterStart(mTextRun, run.GetOriginalOffset(), &iter); 4189 char32_t baseChar = 4190 mCharacterDataBuffer.ScalarValueAt(iter.GetOriginalOffset()); 4191 if (!intl::UnicodeProperties::IsCursiveScript(baseChar)) { 4192 aSpacing[runOffsetInSubstring + i].mAfter += after; 4193 } 4194 } 4195 if (mWordSpacing && IsCSSWordSpacingSpace(mCharacterDataBuffer, 4196 i + run.GetOriginalOffset(), 4197 mFrame, mTextStyle)) { 4198 // It kinda sucks, but space characters can be part of clusters, 4199 // and even still be whitespace (I think!) 4200 iter.SetSkippedOffset(run.GetSkippedOffset() + i); 4201 FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(), 4202 &iter); 4203 uint32_t runOffset = iter.GetSkippedOffset() - aRange.start; 4204 aSpacing[runOffset].mAfter += mWordSpacing; 4205 spacingPresent = true; 4206 } 4207 // Add text-autospace spacing only at cluster starts. Always check the 4208 // character classes if the textrun includes CJK; otherwise, check only 4209 // at the frame start (as preceding content might be an ideograph 4210 // requiring autospacing). 4211 if (mTextAutospace && 4212 (textIncludesCJK || 4213 run.GetOriginalOffset() + i == mFrame->GetContentOffset()) && 4214 mTextRun->IsClusterStart(run.GetSkippedOffset() + i)) { 4215 const auto currClass = TextAutospace::GetCharClass(currScalar()); 4216 4217 // It is rare for the current class to be a combining mark, as 4218 // combining marks are not cluster starts. We still check in case a 4219 // stray mark appears at the start of a frame. 4220 if (currClass != CharClass::CombiningMark) { 4221 // We don't need to do anything if at start of line, or if the 4222 // current class is `Other`, which never participates in spacing. 4223 if (!atStart && currClass != CharClass::Other && 4224 mTextAutospace->ShouldApplySpacing( 4225 prevClass.valueOrFrom(findPrecedingClass), currClass)) { 4226 aSpacing[runOffsetInSubstring + i].mBefore += 4227 mTextAutospace->InterScriptSpacing(); 4228 spacingPresent = true; 4229 } 4230 // Even if we didn't actually need to check spacing rules here, we 4231 // record the new prevClass. (Incidentally, this ensure that we'll 4232 // only call the findPrecedingClass() helper once.) 4233 prevClass = Some(currClass); 4234 } 4235 } 4236 atStart = false; 4237 } 4238 } 4239 } 4240 4241 // Now add tab spacing, if there is any 4242 if (!aIgnoreTabs) { 4243 gfxFloat tabWidth = ComputeTabWidthAppUnits(mFrame); 4244 if (tabWidth > 0) { 4245 CalcTabWidths(aRange, tabWidth); 4246 if (mTabWidths) { 4247 mTabWidths->ApplySpacing(aSpacing, 4248 aRange.start - mStart.GetSkippedOffset(), 4249 aRange.Length()); 4250 spacingPresent = true; 4251 } 4252 } 4253 } 4254 4255 // Now add in justification spacing 4256 if (mJustificationSpacings.Length() > 0) { 4257 // If there is any spaces trimmed at the end, aStart + aLength may 4258 // be larger than the flags array. When that happens, we can simply 4259 // ignore those spaces. 4260 auto arrayEnd = mJustificationArrayStart + 4261 static_cast<uint32_t>(mJustificationSpacings.Length()); 4262 auto end = std::min(aRange.end, arrayEnd); 4263 MOZ_ASSERT(aRange.start >= mJustificationArrayStart); 4264 for (auto i = aRange.start; i < end; i++) { 4265 const auto& spacing = 4266 mJustificationSpacings[i - mJustificationArrayStart]; 4267 uint32_t offset = i - aRange.start; 4268 aSpacing[offset].mBefore += spacing.mBefore; 4269 aSpacing[offset].mAfter += spacing.mAfter; 4270 } 4271 spacingPresent = true; 4272 } 4273 4274 return spacingPresent; 4275 } 4276 4277 // aX and the result are in whole appunits. 4278 static gfxFloat AdvanceToNextTab(gfxFloat aX, gfxFloat aTabWidth, 4279 gfxFloat aMinAdvance) { 4280 // Advance aX to the next multiple of aTabWidth. We must advance 4281 // by at least aMinAdvance. 4282 gfxFloat nextPos = aX + aMinAdvance; 4283 return aTabWidth > 0.0 ? ceil(nextPos / aTabWidth) * aTabWidth : nextPos; 4284 } 4285 4286 void nsTextFrame::PropertyProvider::CalcTabWidths(Range aRange, 4287 gfxFloat aTabWidth) const { 4288 MOZ_ASSERT(aTabWidth > 0); 4289 4290 if (!mTabWidths) { 4291 if (mReflowing && !mLineContainer) { 4292 // Intrinsic width computation does its own tab processing. We 4293 // just don't do anything here. 4294 return; 4295 } 4296 if (!mReflowing) { 4297 mTabWidths = mFrame->GetProperty(TabWidthProperty()); 4298 #ifdef DEBUG 4299 // If we're not reflowing, we should have already computed the 4300 // tab widths; check that they're available as far as the last 4301 // tab character present (if any) 4302 for (uint32_t i = aRange.end; i > aRange.start; --i) { 4303 if (mTextRun->CharIsTab(i - 1)) { 4304 uint32_t startOffset = mStart.GetSkippedOffset(); 4305 NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i, 4306 "Precomputed tab widths are missing!"); 4307 break; 4308 } 4309 } 4310 #endif 4311 return; 4312 } 4313 } 4314 4315 uint32_t startOffset = mStart.GetSkippedOffset(); 4316 MOZ_ASSERT(aRange.start >= startOffset, "wrong start offset"); 4317 MOZ_ASSERT(aRange.end <= startOffset + mLength, "beyond the end"); 4318 uint32_t tabsEnd = 4319 (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset; 4320 if (tabsEnd < aRange.end) { 4321 NS_ASSERTION(mReflowing, 4322 "We need precomputed tab widths, but don't have enough."); 4323 4324 for (uint32_t i = tabsEnd; i < aRange.end; ++i) { 4325 Spacing spacing; 4326 GetSpacingInternal(Range(i, i + 1), &spacing, true); 4327 mOffsetFromBlockOriginForTabs += spacing.mBefore; 4328 4329 if (!mTextRun->CharIsTab(i)) { 4330 if (mTextRun->IsClusterStart(i)) { 4331 uint32_t clusterEnd = i + 1; 4332 while (clusterEnd < mTextRun->GetLength() && 4333 !mTextRun->IsClusterStart(clusterEnd)) { 4334 ++clusterEnd; 4335 } 4336 mOffsetFromBlockOriginForTabs += 4337 mTextRun->GetAdvanceWidth(Range(i, clusterEnd), nullptr); 4338 } 4339 } else { 4340 if (!mTabWidths) { 4341 mTabWidths = new TabWidthStore(mFrame->GetContentOffset()); 4342 mFrame->SetProperty(TabWidthProperty(), mTabWidths); 4343 } 4344 double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs, 4345 aTabWidth, MinTabAdvance()); 4346 mTabWidths->mWidths.AppendElement( 4347 TabWidth(i - startOffset, 4348 NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs))); 4349 mOffsetFromBlockOriginForTabs = nextTab; 4350 } 4351 4352 mOffsetFromBlockOriginForTabs += spacing.mAfter; 4353 } 4354 4355 if (mTabWidths) { 4356 mTabWidths->mLimit = aRange.end - startOffset; 4357 } 4358 } 4359 4360 if (!mTabWidths) { 4361 // Delete any stale property that may be left on the frame 4362 mFrame->RemoveProperty(TabWidthProperty()); 4363 mTabWidthsAnalyzedLimit = 4364 std::max(mTabWidthsAnalyzedLimit, aRange.end - startOffset); 4365 } 4366 } 4367 4368 gfxFloat nsTextFrame::PropertyProvider::GetHyphenWidth() const { 4369 if (mHyphenWidth < 0) { 4370 const auto& hyphenateChar = mTextStyle->mHyphenateCharacter; 4371 if (hyphenateChar.IsAuto()) { 4372 mHyphenWidth = GetFontGroup()->GetHyphenWidth(this); 4373 } else { 4374 RefPtr<gfxTextRun> hyphRun = GetHyphenTextRun(mFrame, nullptr); 4375 mHyphenWidth = hyphRun ? hyphRun->GetAdvanceWidth() : 0; 4376 } 4377 } 4378 return mHyphenWidth + mLetterSpacing; 4379 } 4380 4381 static inline bool IS_HYPHEN(char16_t u) { 4382 return u == char16_t('-') || // HYPHEN-MINUS 4383 u == 0x058A || // ARMENIAN HYPHEN 4384 u == 0x2010 || // HYPHEN 4385 u == 0x2012 || // FIGURE DASH 4386 u == 0x2013; // EN DASH 4387 } 4388 4389 void nsTextFrame::PropertyProvider::GetHyphenationBreaks( 4390 Range aRange, HyphenType* aBreakBefore) const { 4391 MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds"); 4392 MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length"); 4393 4394 if (!mTextStyle->WhiteSpaceCanWrap(mFrame) || 4395 mTextStyle->mHyphens == StyleHyphens::None) { 4396 memset(aBreakBefore, static_cast<uint8_t>(HyphenType::None), 4397 aRange.Length() * sizeof(HyphenType)); 4398 return; 4399 } 4400 4401 // Iterate through the original-string character runs 4402 nsSkipCharsRunIterator run( 4403 mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length()); 4404 run.SetSkippedOffset(aRange.start); 4405 // We need to visit skipped characters so that we can detect SHY 4406 run.SetVisitSkipped(); 4407 4408 int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1; 4409 bool allowHyphenBreakBeforeNextChar = 4410 prevTrailingCharOffset >= mStart.GetOriginalOffset() && 4411 prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength && 4412 mCharacterDataBuffer.CharAt( 4413 AssertedCast<uint32_t>(prevTrailingCharOffset)) == CH_SHY; 4414 4415 while (run.NextRun()) { 4416 NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs"); 4417 if (run.IsSkipped()) { 4418 // Check if there's a soft hyphen which would let us hyphenate before 4419 // the next non-skipped character. Don't look at soft hyphens followed 4420 // by other skipped characters, we won't use them. 4421 allowHyphenBreakBeforeNextChar = 4422 mCharacterDataBuffer.CharAt(AssertedCast<uint32_t>( 4423 run.GetOriginalOffset() + run.GetRunLength() - 1)) == CH_SHY; 4424 } else { 4425 int32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start; 4426 memset(aBreakBefore + runOffsetInSubstring, 4427 static_cast<uint8_t>(HyphenType::None), 4428 run.GetRunLength() * sizeof(HyphenType)); 4429 // Don't allow hyphen breaks at the start of the line 4430 aBreakBefore[runOffsetInSubstring] = 4431 allowHyphenBreakBeforeNextChar && 4432 (!mFrame->HasAnyStateBits(TEXT_START_OF_LINE) || 4433 run.GetSkippedOffset() > mStart.GetSkippedOffset()) 4434 ? HyphenType::Soft 4435 : HyphenType::None; 4436 allowHyphenBreakBeforeNextChar = false; 4437 } 4438 } 4439 4440 if (mTextStyle->mHyphens == StyleHyphens::Auto) { 4441 gfxSkipCharsIterator skipIter(mStart); 4442 for (uint32_t i = 0; i < aRange.Length(); ++i) { 4443 if (IS_HYPHEN(mCharacterDataBuffer.CharAt(AssertedCast<uint32_t>( 4444 skipIter.ConvertSkippedToOriginal(aRange.start + i))))) { 4445 if (i < aRange.Length() - 1) { 4446 aBreakBefore[i + 1] = HyphenType::Explicit; 4447 } 4448 continue; 4449 } 4450 4451 if (mTextRun->CanHyphenateBefore(aRange.start + i) && 4452 aBreakBefore[i] == HyphenType::None) { 4453 aBreakBefore[i] = HyphenType::AutoWithoutManualInSameWord; 4454 } 4455 } 4456 } 4457 } 4458 4459 void nsTextFrame::PropertyProvider::InitializeForDisplay(bool aTrimAfter) { 4460 nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets( 4461 mCharacterDataBuffer, 4462 (aTrimAfter ? nsTextFrame::TrimmedOffsetFlags::Default 4463 : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter)); 4464 mStart.SetOriginalOffset(trimmed.mStart); 4465 mLength = trimmed.mLength; 4466 if (mFrame->HasAnyStateBits(TEXT_START_OF_LINE)) { 4467 mStartOfLineOffset = mStart.GetSkippedOffset(); 4468 } 4469 SetupJustificationSpacing(true); 4470 } 4471 4472 void nsTextFrame::PropertyProvider::InitializeForMeasure() { 4473 nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets( 4474 mCharacterDataBuffer, nsTextFrame::TrimmedOffsetFlags::NotPostReflow); 4475 mStart.SetOriginalOffset(trimmed.mStart); 4476 mLength = trimmed.mLength; 4477 if (mFrame->HasAnyStateBits(TEXT_START_OF_LINE)) { 4478 mStartOfLineOffset = mStart.GetSkippedOffset(); 4479 } 4480 SetupJustificationSpacing(false); 4481 } 4482 4483 void nsTextFrame::PropertyProvider::SetupJustificationSpacing( 4484 bool aPostReflow) { 4485 MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length"); 4486 4487 if (!mFrame->HasAnyStateBits(TEXT_JUSTIFICATION_ENABLED)) { 4488 return; 4489 } 4490 4491 gfxSkipCharsIterator start(mStart), end(mStart); 4492 // We can't just use our mLength here; when InitializeForDisplay is 4493 // called with false for aTrimAfter, we still shouldn't be assigning 4494 // justification space to any trailing whitespace. 4495 nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets( 4496 mCharacterDataBuffer, 4497 (aPostReflow ? nsTextFrame::TrimmedOffsetFlags::Default 4498 : nsTextFrame::TrimmedOffsetFlags::NotPostReflow)); 4499 end.AdvanceOriginal(trimmed.mLength); 4500 gfxSkipCharsIterator realEnd(end); 4501 4502 Range range(uint32_t(start.GetOriginalOffset()), 4503 uint32_t(end.GetOriginalOffset())); 4504 nsTArray<JustificationAssignment> assignments; 4505 JustificationInfo info = ComputeJustification(range, &assignments); 4506 4507 auto assign = mFrame->GetJustificationAssignment(); 4508 auto totalGaps = JustificationUtils::CountGaps(info, assign); 4509 if (!totalGaps || assignments.IsEmpty()) { 4510 // Nothing to do, nothing is justifiable and we shouldn't have any 4511 // justification space assigned 4512 return; 4513 } 4514 4515 // Remember that textrun measurements are in the run's orientation, 4516 // so its advance "width" is actually a height in vertical writing modes, 4517 // corresponding to the inline-direction of the frame. 4518 gfxFloat naturalWidth = mTextRun->GetAdvanceWidth( 4519 Range(mStart.GetSkippedOffset(), realEnd.GetSkippedOffset()), this); 4520 if (mFrame->HasAnyStateBits(TEXT_HYPHEN_BREAK)) { 4521 naturalWidth += GetHyphenWidth(); 4522 } 4523 nscoord totalSpacing = mFrame->ISize() - naturalWidth; 4524 if (totalSpacing <= 0) { 4525 // No space available 4526 return; 4527 } 4528 4529 assignments[0].mGapsAtStart = assign.mGapsAtStart; 4530 assignments.LastElement().mGapsAtEnd = assign.mGapsAtEnd; 4531 4532 MOZ_ASSERT(mJustificationSpacings.IsEmpty()); 4533 JustificationApplicationState state(totalGaps, totalSpacing); 4534 mJustificationSpacings.SetCapacity(assignments.Length()); 4535 for (const JustificationAssignment& assign : assignments) { 4536 Spacing* spacing = mJustificationSpacings.AppendElement(); 4537 spacing->mBefore = state.Consume(assign.mGapsAtStart); 4538 spacing->mAfter = state.Consume(assign.mGapsAtEnd); 4539 } 4540 } 4541 4542 void nsTextFrame::PropertyProvider::InitFontGroupAndFontMetrics() const { 4543 if (!mFontMetrics) { 4544 if (mWhichTextRun == nsTextFrame::eInflated) { 4545 mFontMetrics = mFrame->InflatedFontMetrics(); 4546 } else { 4547 mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(mFrame, 1.0f); 4548 } 4549 } 4550 mFontGroup = mFontMetrics->GetThebesFontGroup(); 4551 } 4552 4553 void nsTextFrame::PropertyProvider::InitTextAutospace() { 4554 const auto styleTextAutospace = mTextStyle->EffectiveTextAutospace(); 4555 if (TextAutospace::Enabled(styleTextAutospace, mFrame)) { 4556 mTextAutospace.emplace(styleTextAutospace, 4557 GetFontMetrics()->InterScriptSpacingWidth()); 4558 } 4559 } 4560 4561 #ifdef ACCESSIBILITY 4562 a11y::AccType nsTextFrame::AccessibleType() { 4563 if (IsEmpty()) { 4564 RenderedText text = 4565 GetRenderedText(0, UINT32_MAX, TextOffsetType::OffsetsInContentText, 4566 TrailingWhitespace::DontTrim); 4567 if (text.mString.IsEmpty()) { 4568 return a11y::eNoType; 4569 } 4570 } 4571 4572 return a11y::eTextLeafType; 4573 } 4574 #endif 4575 4576 //----------------------------------------------------------------------------- 4577 void nsTextFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 4578 nsIFrame* aPrevInFlow) { 4579 NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!"); 4580 MOZ_ASSERT(aContent->IsText(), "Bogus content!"); 4581 4582 // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they 4583 // might be invalid if the content was modified while there was no frame 4584 if (aContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) { 4585 aContent->RemoveProperty(nsGkAtoms::newline); 4586 aContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY); 4587 } 4588 if (aContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) { 4589 aContent->RemoveProperty(nsGkAtoms::flowlength); 4590 aContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY); 4591 } 4592 4593 // Since our content has a frame now, this flag is no longer needed. 4594 aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE); 4595 4596 // We're not a continuing frame. 4597 // mContentOffset = 0; not necessary since we get zeroed out at init 4598 nsIFrame::Init(aContent, aParent, aPrevInFlow); 4599 } 4600 4601 void nsTextFrame::ClearFrameOffsetCache() { 4602 // See if we need to remove ourselves from the offset cache 4603 if (HasAnyStateBits(TEXT_IN_OFFSET_CACHE)) { 4604 nsIFrame* primaryFrame = mContent->GetPrimaryFrame(); 4605 if (primaryFrame) { 4606 // The primary frame might be null here. For example, 4607 // nsLineBox::DeleteLineList just destroys the frames in order, which 4608 // means that the primary frame is already dead if we're a continuing text 4609 // frame, in which case, all of its properties are gone, and we don't need 4610 // to worry about deleting this property here. 4611 primaryFrame->RemoveProperty(OffsetToFrameProperty()); 4612 } 4613 RemoveStateBits(TEXT_IN_OFFSET_CACHE); 4614 } 4615 } 4616 4617 void nsTextFrame::Destroy(DestroyContext& aContext) { 4618 ClearFrameOffsetCache(); 4619 4620 // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or 4621 // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame 4622 // type might be changing. Not clear whether it's worth it. 4623 ClearTextRuns(); 4624 if (mNextContinuation) { 4625 mNextContinuation->SetPrevInFlow(nullptr); 4626 } 4627 // Let the base class destroy the frame 4628 nsIFrame::Destroy(aContext); 4629 } 4630 4631 nsTArray<nsTextFrame*>* nsTextFrame::GetContinuations() { 4632 MOZ_ASSERT(NS_IsMainThread()); 4633 // Only for use on the primary frame, which has no prev-continuation. 4634 MOZ_ASSERT(!GetPrevContinuation()); 4635 if (!mNextContinuation) { 4636 return nullptr; 4637 } 4638 if (mPropertyFlags & PropertyFlags::Continuations) { 4639 return GetProperty(ContinuationsProperty()); 4640 } 4641 size_t count = 0; 4642 for (nsIFrame* f = this; f; f = f->GetNextContinuation()) { 4643 ++count; 4644 } 4645 auto* continuations = new nsTArray<nsTextFrame*>; 4646 if (continuations->SetCapacity(count, fallible)) { 4647 for (nsTextFrame* f = this; f; 4648 f = static_cast<nsTextFrame*>(f->GetNextContinuation())) { 4649 continuations->AppendElement(f); 4650 } 4651 } else { 4652 delete continuations; 4653 continuations = nullptr; 4654 } 4655 AddProperty(ContinuationsProperty(), continuations); 4656 mPropertyFlags |= PropertyFlags::Continuations; 4657 return continuations; 4658 } 4659 4660 class nsContinuingTextFrame final : public nsTextFrame { 4661 public: 4662 NS_DECL_FRAMEARENA_HELPERS(nsContinuingTextFrame) 4663 4664 friend nsIFrame* NS_NewContinuingTextFrame(mozilla::PresShell* aPresShell, 4665 ComputedStyle* aStyle); 4666 4667 void Init(nsIContent* aContent, nsContainerFrame* aParent, 4668 nsIFrame* aPrevInFlow) final; 4669 4670 void Destroy(DestroyContext&) override; 4671 4672 nsTextFrame* GetPrevContinuation() const final { return mPrevContinuation; } 4673 4674 void SetPrevContinuation(nsIFrame* aPrevContinuation) final { 4675 NS_ASSERTION(!aPrevContinuation || Type() == aPrevContinuation->Type(), 4676 "setting a prev continuation with incorrect type!"); 4677 NS_ASSERTION( 4678 !nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this), 4679 "creating a loop in continuation chain!"); 4680 mPrevContinuation = static_cast<nsTextFrame*>(aPrevContinuation); 4681 RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION); 4682 UpdateCachedContinuations(); 4683 } 4684 4685 nsTextFrame* GetPrevInFlow() const final { 4686 return HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation 4687 : nullptr; 4688 } 4689 4690 void SetPrevInFlow(nsIFrame* aPrevInFlow) final { 4691 NS_ASSERTION(!aPrevInFlow || Type() == aPrevInFlow->Type(), 4692 "setting a prev in flow with incorrect type!"); 4693 NS_ASSERTION( 4694 !nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this), 4695 "creating a loop in continuation chain!"); 4696 mPrevContinuation = static_cast<nsTextFrame*>(aPrevInFlow); 4697 AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION); 4698 UpdateCachedContinuations(); 4699 } 4700 4701 // Call this helper to update cache after mPrevContinuation is changed. 4702 void UpdateCachedContinuations() { 4703 nsTextFrame* prevFirst = mFirstContinuation; 4704 if (mPrevContinuation) { 4705 mFirstContinuation = mPrevContinuation->FirstContinuation(); 4706 if (mFirstContinuation) { 4707 mFirstContinuation->ClearCachedContinuations(); 4708 } 4709 } else { 4710 mFirstContinuation = nullptr; 4711 } 4712 if (mFirstContinuation != prevFirst) { 4713 if (prevFirst) { 4714 prevFirst->ClearCachedContinuations(); 4715 } 4716 auto* f = static_cast<nsContinuingTextFrame*>(mNextContinuation); 4717 while (f) { 4718 f->mFirstContinuation = mFirstContinuation; 4719 f = static_cast<nsContinuingTextFrame*>(f->mNextContinuation); 4720 } 4721 } 4722 } 4723 4724 nsIFrame* FirstInFlow() const final; 4725 nsTextFrame* FirstContinuation() const final { 4726 #if DEBUG 4727 // If we have a prev-continuation pointer, then our first-continuation 4728 // must be the same as that frame's. 4729 if (mPrevContinuation) { 4730 // If there's a prev-prev, then we can safely cast mPrevContinuation to 4731 // an nsContinuingTextFrame and access its mFirstContinuation pointer 4732 // directly, to avoid recursively calling FirstContinuation(), leading 4733 // to exponentially-slow behavior in the assertion. 4734 if (mPrevContinuation->GetPrevContinuation()) { 4735 auto* prev = static_cast<nsContinuingTextFrame*>(mPrevContinuation); 4736 MOZ_ASSERT(mFirstContinuation == prev->mFirstContinuation); 4737 } else { 4738 MOZ_ASSERT(mFirstContinuation == 4739 mPrevContinuation->FirstContinuation()); 4740 } 4741 } else { 4742 MOZ_ASSERT(!mFirstContinuation); 4743 } 4744 #endif 4745 return mFirstContinuation; 4746 }; 4747 4748 void AddInlineMinISize(const IntrinsicSizeInput& aInput, 4749 InlineMinISizeData* aData) final { 4750 // Do nothing, since the first-in-flow accounts for everything. 4751 } 4752 void AddInlinePrefISize(const IntrinsicSizeInput& aInput, 4753 InlinePrefISizeData* aData) final { 4754 // Do nothing, since the first-in-flow accounts for everything. 4755 } 4756 4757 protected: 4758 explicit nsContinuingTextFrame(ComputedStyle* aStyle, 4759 nsPresContext* aPresContext) 4760 : nsTextFrame(aStyle, aPresContext, kClassID) {} 4761 4762 nsTextFrame* mPrevContinuation = nullptr; 4763 nsTextFrame* mFirstContinuation = nullptr; 4764 }; 4765 4766 void nsContinuingTextFrame::Init(nsIContent* aContent, 4767 nsContainerFrame* aParent, 4768 nsIFrame* aPrevInFlow) { 4769 NS_ASSERTION(aPrevInFlow, "Must be a continuation!"); 4770 4771 // Hook the frame into the flow 4772 nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow); 4773 nsTextFrame* nextContinuation = prev->GetNextContinuation(); 4774 SetPrevInFlow(aPrevInFlow); 4775 aPrevInFlow->SetNextInFlow(this); 4776 4777 // NOTE: bypassing nsTextFrame::Init!!! 4778 nsIFrame::Init(aContent, aParent, aPrevInFlow); 4779 4780 mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint(); 4781 NS_ASSERTION( 4782 mContentOffset < int32_t(aContent->GetCharacterDataBuffer()->GetLength()), 4783 "Creating ContinuingTextFrame, but there is no more content"); 4784 if (prev->Style() != Style()) { 4785 // We're taking part of prev's text, and its style may be different 4786 // so clear its textrun which may no longer be valid (and don't set ours) 4787 prev->ClearTextRuns(); 4788 } else { 4789 float inflation = prev->GetFontSizeInflation(); 4790 SetFontSizeInflation(inflation); 4791 mTextRun = prev->GetTextRun(nsTextFrame::eInflated); 4792 if (inflation != 1.0f) { 4793 gfxTextRun* uninflatedTextRun = 4794 prev->GetTextRun(nsTextFrame::eNotInflated); 4795 if (uninflatedTextRun) { 4796 SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f); 4797 } 4798 } 4799 } 4800 if (aPrevInFlow->HasAnyStateBits(NS_FRAME_IS_BIDI)) { 4801 FrameBidiData bidiData = aPrevInFlow->GetBidiData(); 4802 bidiData.precedingControl = kBidiLevelNone; 4803 SetProperty(BidiDataProperty(), bidiData); 4804 4805 if (nextContinuation) { 4806 SetNextContinuation(nextContinuation); 4807 nextContinuation->SetPrevContinuation(this); 4808 // Adjust next-continuations' content offset as needed. 4809 while (nextContinuation && 4810 nextContinuation->GetContentOffset() < mContentOffset) { 4811 #ifdef DEBUG 4812 FrameBidiData nextBidiData = nextContinuation->GetBidiData(); 4813 NS_ASSERTION(bidiData.embeddingLevel == nextBidiData.embeddingLevel && 4814 bidiData.baseLevel == nextBidiData.baseLevel, 4815 "stealing text from different type of BIDI continuation"); 4816 MOZ_ASSERT(nextBidiData.precedingControl == kBidiLevelNone, 4817 "There shouldn't be any virtual bidi formatting character " 4818 "between continuations"); 4819 #endif 4820 nextContinuation->mContentOffset = mContentOffset; 4821 nextContinuation = nextContinuation->GetNextContinuation(); 4822 } 4823 } 4824 AddStateBits(NS_FRAME_IS_BIDI); 4825 } // prev frame is bidi 4826 } 4827 4828 void nsContinuingTextFrame::Destroy(DestroyContext& aContext) { 4829 ClearFrameOffsetCache(); 4830 4831 // The text associated with this frame will become associated with our 4832 // prev-continuation. If that means the text has changed style, then 4833 // we need to wipe out the text run for the text. 4834 // Note that mPrevContinuation can be null if we're destroying the whole 4835 // frame chain from the start to the end. 4836 // If this frame is mentioned in the userData for a textrun (say 4837 // because there's a direction change at the start of this frame), then 4838 // we have to clear the textrun because we're going away and the 4839 // textrun had better not keep a dangling reference to us. 4840 if (IsInTextRunUserData() || 4841 (mPrevContinuation && mPrevContinuation->Style() != Style())) { 4842 ClearTextRuns(); 4843 // Clear the previous continuation's text run also, so that it can rebuild 4844 // the text run to include our text. 4845 if (mPrevContinuation) { 4846 mPrevContinuation->ClearTextRuns(); 4847 } 4848 } 4849 nsSplittableFrame::RemoveFromFlow(this); 4850 // Let the base class destroy the frame 4851 nsIFrame::Destroy(aContext); 4852 } 4853 4854 nsIFrame* nsContinuingTextFrame::FirstInFlow() const { 4855 // Can't cast to |nsContinuingTextFrame*| because the first one isn't. 4856 nsIFrame *firstInFlow, 4857 *previous = const_cast<nsIFrame*>(static_cast<const nsIFrame*>(this)); 4858 do { 4859 firstInFlow = previous; 4860 previous = firstInFlow->GetPrevInFlow(); 4861 } while (previous); 4862 MOZ_ASSERT(firstInFlow, "post-condition failed"); 4863 return firstInFlow; 4864 } 4865 4866 // XXX Do we want to do all the work for the first-in-flow or do the 4867 // work for each part? (Be careful of first-letter / first-line, though, 4868 // especially first-line!) Doing all the work on the first-in-flow has 4869 // the advantage of avoiding the potential for incremental reflow bugs, 4870 // but depends on our maintining the frame tree in reasonable ways even 4871 // for edge cases (block-within-inline splits, nextBidi, etc.) 4872 4873 // XXX We really need to make :first-letter happen during frame 4874 // construction. 4875 4876 nscoord nsTextFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 4877 IntrinsicISizeType aType) { 4878 return IntrinsicISizeFromInline(aInput, aType); 4879 } 4880 4881 //---------------------------------------------------------------------- 4882 4883 #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky) 4884 static void VerifyNotDirty(nsFrameState state) { 4885 bool isZero = state & NS_FRAME_FIRST_REFLOW; 4886 bool isDirty = state & NS_FRAME_IS_DIRTY; 4887 if (!isZero && isDirty) { 4888 NS_WARNING("internal offsets may be out-of-sync"); 4889 } 4890 } 4891 # define DEBUG_VERIFY_NOT_DIRTY(state) VerifyNotDirty(state) 4892 #else 4893 # define DEBUG_VERIFY_NOT_DIRTY(state) 4894 #endif 4895 4896 nsIFrame* NS_NewTextFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 4897 return new (aPresShell) nsTextFrame(aStyle, aPresShell->GetPresContext()); 4898 } 4899 4900 NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame) 4901 4902 nsIFrame* NS_NewContinuingTextFrame(PresShell* aPresShell, 4903 ComputedStyle* aStyle) { 4904 return new (aPresShell) 4905 nsContinuingTextFrame(aStyle, aPresShell->GetPresContext()); 4906 } 4907 4908 NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame) 4909 4910 nsTextFrame::~nsTextFrame() = default; 4911 4912 nsIFrame::Cursor nsTextFrame::GetCursor(const nsPoint& aPoint) { 4913 StyleCursorKind kind = StyleUI()->Cursor().keyword; 4914 if (kind == StyleCursorKind::Auto) { 4915 if (!IsSelectable()) { 4916 kind = StyleCursorKind::Default; 4917 } else { 4918 kind = GetWritingMode().IsVertical() ? StyleCursorKind::VerticalText 4919 : StyleCursorKind::Text; 4920 } 4921 } 4922 return Cursor{kind, AllowCustomCursorImage::Yes}; 4923 } 4924 4925 nsTextFrame* nsTextFrame::LastInFlow() const { 4926 nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this); 4927 while (lastInFlow->GetNextInFlow()) { 4928 lastInFlow = lastInFlow->GetNextInFlow(); 4929 } 4930 MOZ_ASSERT(lastInFlow, "post-condition failed"); 4931 return lastInFlow; 4932 } 4933 4934 nsTextFrame* nsTextFrame::LastContinuation() const { 4935 nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this); 4936 while (lastContinuation->mNextContinuation) { 4937 lastContinuation = lastContinuation->mNextContinuation; 4938 } 4939 MOZ_ASSERT(lastContinuation, "post-condition failed"); 4940 return lastContinuation; 4941 } 4942 4943 bool nsTextFrame::ShouldSuppressLineBreak() const { 4944 // If the parent frame of the text frame is ruby content box, it must 4945 // suppress line break inside. This check is necessary, because when 4946 // a whitespace is only contained by pseudo ruby frames, its style 4947 // context won't have SuppressLineBreak bit set. 4948 if (mozilla::RubyUtils::IsRubyContentBox(GetParent()->Type())) { 4949 return true; 4950 } 4951 return Style()->ShouldSuppressLineBreak(); 4952 } 4953 4954 void nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey, 4955 bool aRebuildDisplayItems) { 4956 InvalidateSelectionState(); 4957 4958 if (IsInSVGTextSubtree()) { 4959 nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType( 4960 GetParent(), LayoutFrameType::SVGText); 4961 svgTextFrame->InvalidateFrame(); 4962 return; 4963 } 4964 nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); 4965 } 4966 4967 void nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect, 4968 uint32_t aDisplayItemKey, 4969 bool aRebuildDisplayItems) { 4970 InvalidateSelectionState(); 4971 4972 if (IsInSVGTextSubtree()) { 4973 nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType( 4974 GetParent(), LayoutFrameType::SVGText); 4975 svgTextFrame->InvalidateFrame(); 4976 return; 4977 } 4978 nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, 4979 aRebuildDisplayItems); 4980 } 4981 4982 gfxTextRun* nsTextFrame::GetUninflatedTextRun() const { 4983 return GetProperty(UninflatedTextRunProperty()); 4984 } 4985 4986 void nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun, 4987 float aInflation) { 4988 NS_ASSERTION(aTextRun, "must have text run"); 4989 4990 // Our inflated text run is always stored in mTextRun. In the cases 4991 // where our current inflation is not 1.0, however, we store two text 4992 // runs, and the uninflated one goes in a frame property. We never 4993 // store a single text run in both. 4994 if (aWhichTextRun == eInflated) { 4995 if (HasFontSizeInflation() && aInflation == 1.0f) { 4996 // FIXME: Probably shouldn't do this within each SetTextRun 4997 // method, but it doesn't hurt. 4998 ClearTextRun(nullptr, nsTextFrame::eNotInflated); 4999 } 5000 SetFontSizeInflation(aInflation); 5001 } else { 5002 MOZ_ASSERT(aInflation == 1.0f, "unexpected inflation"); 5003 if (HasFontSizeInflation()) { 5004 // Setting the property will not automatically increment the textrun's 5005 // reference count, so we need to do it here. 5006 aTextRun->AddRef(); 5007 SetProperty(UninflatedTextRunProperty(), aTextRun); 5008 return; 5009 } 5010 // fall through to setting mTextRun 5011 } 5012 5013 mTextRun = aTextRun; 5014 5015 // FIXME: Add assertions testing the relationship between 5016 // GetFontSizeInflation() and whether we have an uninflated text run 5017 // (but be aware that text runs can go away). 5018 } 5019 5020 bool nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun) { 5021 if (aTextRun == mTextRun) { 5022 mTextRun = nullptr; 5023 mFontMetrics = nullptr; 5024 return true; 5025 } 5026 if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION) && 5027 GetProperty(UninflatedTextRunProperty()) == aTextRun) { 5028 RemoveProperty(UninflatedTextRunProperty()); 5029 return true; 5030 } 5031 return false; 5032 } 5033 5034 void nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation, 5035 TextRunType aWhichTextRun) { 5036 RefPtr<gfxTextRun> textRun = GetTextRun(aWhichTextRun); 5037 if (!textRun) { 5038 return; 5039 } 5040 5041 if (aWhichTextRun == nsTextFrame::eInflated) { 5042 mFontMetrics = nullptr; 5043 } 5044 5045 DebugOnly<bool> checkmTextrun = textRun == mTextRun; 5046 UnhookTextRunFromFrames(textRun, aStartContinuation); 5047 MOZ_ASSERT(checkmTextrun ? !mTextRun 5048 : !GetProperty(UninflatedTextRunProperty())); 5049 } 5050 5051 void nsTextFrame::DisconnectTextRuns() { 5052 MOZ_ASSERT(!IsInTextRunUserData(), 5053 "Textrun mentions this frame in its user data so we can't just " 5054 "disconnect"); 5055 mTextRun = nullptr; 5056 if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION)) { 5057 RemoveProperty(UninflatedTextRunProperty()); 5058 } 5059 } 5060 5061 void nsTextFrame::NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength) { 5062 MOZ_ASSERT(mContent->IsInNativeAnonymousSubtree()); 5063 5064 MarkIntrinsicISizesDirty(); 5065 5066 // This is to avoid making a new Reflow request in CharacterDataChanged: 5067 for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) { 5068 f->MarkSubtreeDirty(); 5069 f->mReflowRequestedForCharDataChange = true; 5070 } 5071 5072 // Pretend that all the text changed. 5073 CharacterDataChangeInfo info; 5074 info.mAppend = false; 5075 info.mChangeStart = 0; 5076 info.mChangeEnd = aOldLength; 5077 info.mReplaceLength = GetContent()->TextLength(); 5078 CharacterDataChanged(info); 5079 } 5080 5081 nsresult nsTextFrame::CharacterDataChanged( 5082 const CharacterDataChangeInfo& aInfo) { 5083 if (mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) { 5084 mContent->RemoveProperty(nsGkAtoms::newline); 5085 mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY); 5086 } 5087 if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) { 5088 mContent->RemoveProperty(nsGkAtoms::flowlength); 5089 mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY); 5090 } 5091 5092 // Find the first frame whose text has changed. Frames that are entirely 5093 // before the text change are completely unaffected. 5094 nsTextFrame* next; 5095 nsTextFrame* textFrame = this; 5096 while (true) { 5097 next = textFrame->GetNextContinuation(); 5098 if (!next || next->GetContentOffset() > int32_t(aInfo.mChangeStart)) { 5099 break; 5100 } 5101 textFrame = next; 5102 } 5103 5104 int32_t endOfChangedText = aInfo.mChangeStart + aInfo.mReplaceLength; 5105 5106 // Parent of the last frame that we passed to FrameNeedsReflow (or noticed 5107 // had already received an earlier FrameNeedsReflow call). 5108 // (For subsequent frames with this same parent, we can just set their 5109 // dirty bit without bothering to call FrameNeedsReflow again.) 5110 nsIFrame* lastDirtiedFrameParent = nullptr; 5111 5112 mozilla::PresShell* presShell = PresShell(); 5113 do { 5114 // textFrame contained deleted text (or the insertion point, 5115 // if this was a pure insertion). 5116 textFrame->RemoveStateBits(TEXT_WHITESPACE_FLAGS); 5117 textFrame->ClearTextRuns(); 5118 5119 nsIFrame* parentOfTextFrame = textFrame->GetParent(); 5120 bool areAncestorsAwareOfReflowRequest = false; 5121 if (lastDirtiedFrameParent == parentOfTextFrame) { 5122 // An earlier iteration of this loop already called 5123 // FrameNeedsReflow for a sibling of |textFrame|. 5124 areAncestorsAwareOfReflowRequest = true; 5125 } else { 5126 lastDirtiedFrameParent = parentOfTextFrame; 5127 } 5128 5129 if (textFrame->mReflowRequestedForCharDataChange) { 5130 // We already requested a reflow for this frame; nothing to do. 5131 MOZ_ASSERT(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY), 5132 "mReflowRequestedForCharDataChange should only be set " 5133 "on dirty frames"); 5134 } else { 5135 // Make sure textFrame is queued up for a reflow. Also set a flag so we 5136 // don't waste time doing this again in repeated calls to this method. 5137 textFrame->mReflowRequestedForCharDataChange = true; 5138 if (!areAncestorsAwareOfReflowRequest) { 5139 // Ask the parent frame to reflow me. 5140 presShell->FrameNeedsReflow( 5141 textFrame, IntrinsicDirty::FrameAncestorsAndDescendants, 5142 NS_FRAME_IS_DIRTY); 5143 } else { 5144 // We already called FrameNeedsReflow on behalf of an earlier sibling, 5145 // so we can just mark this frame as dirty and don't need to bother 5146 // telling its ancestors. 5147 // Note: if the parent is a block, we're cheating here because we should 5148 // be marking our line dirty, but we're not. nsTextFrame::SetLength will 5149 // do that when it gets called during reflow. 5150 textFrame->MarkSubtreeDirty(); 5151 } 5152 } 5153 textFrame->InvalidateFrame(); 5154 5155 // Below, frames that start after the deleted text will be adjusted so that 5156 // their offsets move with the trailing unchanged text. If this change 5157 // deletes more text than it inserts, those frame offsets will decrease. 5158 // We need to maintain the invariant that mContentOffset is non-decreasing 5159 // along the continuation chain. So we need to ensure that frames that 5160 // started in the deleted text are all still starting before the 5161 // unchanged text. 5162 if (textFrame->mContentOffset > endOfChangedText) { 5163 textFrame->mContentOffset = endOfChangedText; 5164 } 5165 5166 textFrame = textFrame->GetNextContinuation(); 5167 } while (textFrame && 5168 textFrame->GetContentOffset() < int32_t(aInfo.mChangeEnd)); 5169 5170 // This is how much the length of the string changed by --- i.e., 5171 // how much the trailing unchanged text moved. 5172 int32_t sizeChange = 5173 aInfo.mChangeStart + aInfo.mReplaceLength - aInfo.mChangeEnd; 5174 5175 if (sizeChange) { 5176 // Fix the offsets of the text frames that start in the trailing 5177 // unchanged text. 5178 while (textFrame) { 5179 textFrame->mContentOffset += sizeChange; 5180 // XXX we could rescue some text runs by adjusting their user data 5181 // to reflect the change in DOM offsets 5182 textFrame->ClearTextRuns(); 5183 textFrame = textFrame->GetNextContinuation(); 5184 } 5185 } 5186 5187 return NS_OK; 5188 } 5189 5190 struct TextCombineData { 5191 // Measured advance of the text before any text-combine scaling is applied. 5192 nscoord mNaturalWidth = 0; 5193 // Inline offset to place this text within the 1-em block of the upright- 5194 // combined cell. 5195 nscoord mOffset = 0; 5196 // Inline scaling factor to apply (always <= 1.0, as the text may be 5197 // compressed but is never expanded to fit the 1-em cell). 5198 float mScale = 1.0f; 5199 }; 5200 5201 NS_DECLARE_FRAME_PROPERTY_DELETABLE(TextCombineDataProperty, TextCombineData) 5202 5203 float nsTextFrame::GetTextCombineScale() const { 5204 const auto* data = GetProperty(TextCombineDataProperty()); 5205 return data ? data->mScale : 1.0f; 5206 } 5207 5208 std::pair<nscoord, float> nsTextFrame::GetTextCombineOffsetAndScale() const { 5209 const auto* data = GetProperty(TextCombineDataProperty()); 5210 return data ? std::pair(data->mOffset, data->mScale) : std::pair(0, 1.0f); 5211 } 5212 5213 void nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 5214 const nsDisplayListSet& aLists) { 5215 if (!IsVisibleForPainting()) { 5216 return; 5217 } 5218 5219 DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame"); 5220 5221 const nsStyleText* st = StyleText(); 5222 bool isTextTransparent = 5223 NS_GET_A(st->mWebkitTextFillColor.CalcColor(this)) == 0 && 5224 NS_GET_A(st->mWebkitTextStrokeColor.CalcColor(this)) == 0; 5225 if ((HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS) || 5226 (isTextTransparent && !StyleText()->HasTextShadow())) && 5227 aBuilder->IsForPainting() && !IsInSVGTextSubtree()) { 5228 if (!IsSelected()) { 5229 TextDecorations textDecs; 5230 GetTextDecorations(PresContext(), eResolvedColors, textDecs); 5231 if (!textDecs.HasDecorationLines()) { 5232 if (auto* currentPresContext = aBuilder->CurrentPresContext()) { 5233 currentPresContext->SetBuiltInvisibleText(); 5234 } 5235 return; 5236 } 5237 } 5238 } 5239 5240 aLists.Content()->AppendNewToTop<nsDisplayText>(aBuilder, this); 5241 } 5242 5243 UniquePtr<SelectionDetails> nsTextFrame::GetSelectionDetails() { 5244 const nsFrameSelection* frameSelection = GetConstFrameSelection(); 5245 if (frameSelection->IsInTableSelectionMode()) { 5246 return nullptr; 5247 } 5248 UniquePtr<SelectionDetails> details = frameSelection->LookUpSelection( 5249 mContent, GetContentOffset(), GetContentLength(), 5250 // We don't want to paint text as selected if this is not selectable. 5251 // Note if this is editable, this is always treated as selectable, i.e., 5252 // if `user-select` is specified to `none` so that we never stop painting 5253 // selections when there is IME composition which may need normal 5254 // selection as a part of it. 5255 ShouldPaintNormalSelection() 5256 ? nsFrameSelection::IgnoreNormalSelection::No 5257 : nsFrameSelection::IgnoreNormalSelection::Yes); 5258 for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) { 5259 sd->mStart += mContentOffset; 5260 sd->mEnd += mContentOffset; 5261 } 5262 return details; 5263 } 5264 5265 static void PaintSelectionBackground( 5266 DrawTarget& aDrawTarget, nscolor aColor, const LayoutDeviceRect& aDirtyRect, 5267 const LayoutDeviceRect& aRect, nsTextFrame::DrawPathCallbacks* aCallbacks) { 5268 Rect rect = aRect.Intersect(aDirtyRect).ToUnknownRect(); 5269 MaybeSnapToDevicePixels(rect, aDrawTarget); 5270 5271 if (aCallbacks) { 5272 aCallbacks->NotifySelectionBackgroundNeedsFill(rect, aColor, aDrawTarget); 5273 } else { 5274 ColorPattern color(ToDeviceColor(aColor)); 5275 aDrawTarget.FillRect(rect, color); 5276 } 5277 } 5278 5279 // Attempt to get the LineBaselineOffset property of aChildFrame 5280 // If not set, calculate this value for all child frames of aBlockFrame 5281 static nscoord LazyGetLineBaselineOffset(nsIFrame* aChildFrame, 5282 nsBlockFrame* aBlockFrame) { 5283 bool offsetFound; 5284 nscoord offset = 5285 aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(), &offsetFound); 5286 5287 if (!offsetFound) { 5288 for (const auto& line : aBlockFrame->Lines()) { 5289 if (line.IsInline()) { 5290 int32_t n = line.GetChildCount(); 5291 nscoord lineBaseline = line.BStart() + line.GetLogicalAscent(); 5292 for (auto* lineFrame = line.mFirstChild; n > 0; 5293 lineFrame = lineFrame->GetNextSibling(), --n) { 5294 offset = lineBaseline - lineFrame->GetNormalPosition().y; 5295 lineFrame->SetProperty(nsIFrame::LineBaselineOffset(), offset); 5296 } 5297 } 5298 } 5299 return aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(), 5300 &offsetFound); 5301 } else { 5302 return offset; 5303 } 5304 } 5305 5306 static bool IsUnderlineRight(const ComputedStyle& aStyle) { 5307 // Check for 'left' or 'right' explicitly specified in the property; 5308 // if neither is there, we use auto positioning based on lang. 5309 const auto position = aStyle.StyleText()->mTextUnderlinePosition; 5310 if (position.IsLeft()) { 5311 return false; 5312 } 5313 if (position.IsRight()) { 5314 return true; 5315 } 5316 // If neither 'left' nor 'right' was specified, check the language. 5317 nsAtom* langAtom = aStyle.StyleFont()->mLanguage; 5318 if (!langAtom) { 5319 return false; 5320 } 5321 return nsStyleUtil::MatchesLanguagePrefix(langAtom, u"ja") || 5322 nsStyleUtil::MatchesLanguagePrefix(langAtom, u"ko") || 5323 nsStyleUtil::MatchesLanguagePrefix(langAtom, u"mn"); 5324 } 5325 5326 static bool FrameStopsLineDecorationPropagation(nsIFrame* aFrame, 5327 nsCompatibility aCompatMode) { 5328 // In all modes, if we're on an inline-block/table/grid/flex, we're done. 5329 // If we're on a ruby frame other than ruby text container, we 5330 // should continue. 5331 mozilla::StyleDisplay display = aFrame->GetDisplay(); 5332 if (!display.IsInlineFlow() && 5333 (!display.IsRuby() || 5334 display == mozilla::StyleDisplay::RubyTextContainer) && 5335 display.IsInlineOutside()) { 5336 return true; 5337 } 5338 // In quirks mode, if we're on an HTML table element, we're done. 5339 if (aCompatMode == eCompatibility_NavQuirks && 5340 aFrame->GetContent()->IsHTMLElement(nsGkAtoms::table)) { 5341 return true; 5342 } 5343 // If we're on an absolutely-positioned element or a floating 5344 // element, we're done. 5345 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { 5346 return true; 5347 } 5348 // If we're an outer <svg> element, which is classified as an atomic 5349 // inline-level element, we're done. 5350 if (aFrame->IsSVGOuterSVGFrame()) { 5351 return true; 5352 } 5353 return false; 5354 } 5355 5356 void nsTextFrame::GetTextDecorations( 5357 nsPresContext* aPresContext, 5358 nsTextFrame::TextDecorationColorResolution aColorResolution, 5359 nsTextFrame::TextDecorations& aDecorations) { 5360 const nsCompatibility compatMode = aPresContext->CompatibilityMode(); 5361 5362 bool useOverride = false; 5363 nscolor overrideColor = NS_RGBA(0, 0, 0, 0); 5364 5365 bool nearestBlockFound = false; 5366 // Use writing mode of parent frame for orthogonal text frame to work. 5367 // See comment in nsTextFrame::DrawTextRunAndDecorations. 5368 WritingMode wm = GetParent()->GetWritingMode(); 5369 bool vertical = wm.IsVertical(); 5370 5371 nscoord ascent = GetLogicalBaseline(wm); 5372 // physicalBlockStartOffset represents the offset from our baseline 5373 // to f's physical block start, which is top in horizontal writing 5374 // mode, and left in vertical writing modes, in our coordinate space. 5375 // This physical block start is logical block start in most cases, 5376 // but for vertical-rl, it is logical block end, and consequently in 5377 // that case, it starts from the descent instead of ascent. 5378 nscoord physicalBlockStartOffset = 5379 wm.IsVerticalRL() ? GetSize().width - ascent : ascent; 5380 // baselineOffset represents the offset from our baseline to f's baseline or 5381 // the nearest block's baseline, in our coordinate space, whichever is closest 5382 // during the particular iteration 5383 nscoord baselineOffset = 0; 5384 5385 for (nsIFrame *f = this, *fChild = nullptr; f; 5386 fChild = f, f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) { 5387 ComputedStyle* const context = f->Style(); 5388 if (!context->HasTextDecorationLines()) { 5389 break; 5390 } 5391 5392 if (context->GetPseudoType() == PseudoStyleType::marker && 5393 (context->StyleList()->mListStylePosition == 5394 StyleListStylePosition::Outside || 5395 !context->StyleDisplay()->IsInlineOutsideStyle())) { 5396 // Outside ::marker pseudos, and inside markers that aren't inlines, don't 5397 // have text decorations. 5398 break; 5399 } 5400 5401 const nsStyleTextReset* const styleTextReset = context->StyleTextReset(); 5402 StyleTextDecorationLine textDecorations = 5403 styleTextReset->mTextDecorationLine; 5404 bool ignoreSubproperties = false; 5405 5406 auto lineStyle = styleTextReset->mTextDecorationStyle; 5407 if (textDecorations == StyleTextDecorationLine::SPELLING_ERROR || 5408 textDecorations == StyleTextDecorationLine::GRAMMAR_ERROR) { 5409 nscolor lineColor; 5410 float relativeSize; 5411 useOverride = nsTextPaintStyle::GetSelectionUnderline( 5412 this, nsTextPaintStyle::SelectionStyleIndex::SpellChecker, &lineColor, 5413 &relativeSize, &lineStyle); 5414 if (useOverride) { 5415 // We don't currently have a SelectionStyleIndex::GrammarChecker; for 5416 // now just use SpellChecker and change its color to green. 5417 overrideColor = 5418 textDecorations == StyleTextDecorationLine::SPELLING_ERROR 5419 ? lineColor 5420 : NS_RGBA(0, 128, 0, 255); 5421 textDecorations = StyleTextDecorationLine::UNDERLINE; 5422 ignoreSubproperties = true; 5423 } 5424 } 5425 5426 if (!useOverride && 5427 (StyleTextDecorationLine::COLOR_OVERRIDE & textDecorations)) { 5428 // This handles the <a href="blah.html"><font color="green">La 5429 // la la</font></a> case. The link underline should be green. 5430 useOverride = true; 5431 overrideColor = nsLayoutUtils::GetTextColor( 5432 f, &nsStyleTextReset::mTextDecorationColor); 5433 } 5434 5435 nsBlockFrame* fBlock = do_QueryFrame(f); 5436 const bool firstBlock = !nearestBlockFound && fBlock; 5437 5438 // Not updating positions once we hit a parent block is equivalent to 5439 // the CSS 2.1 spec that blocks should propagate decorations down to their 5440 // children (albeit the style should be preserved) 5441 // However, if we're vertically aligned within a block, then we need to 5442 // recover the correct baseline from the line by querying the FrameProperty 5443 // that should be set (see nsLineLayout::VerticalAlignLine). 5444 if (firstBlock) { 5445 // At this point, fChild can't be null since TextFrames can't be blocks 5446 Maybe<StyleVerticalAlignKeyword> verticalAlign = 5447 fChild->VerticalAlignEnum(); 5448 if (verticalAlign != Some(StyleVerticalAlignKeyword::Baseline)) { 5449 // Since offset is the offset in the child's coordinate space, we have 5450 // to undo the accumulation to bring the transform out of the block's 5451 // coordinate space 5452 const nscoord lineBaselineOffset = 5453 LazyGetLineBaselineOffset(fChild, fBlock); 5454 5455 baselineOffset = physicalBlockStartOffset - lineBaselineOffset - 5456 (vertical ? fChild->GetNormalPosition().x 5457 : fChild->GetNormalPosition().y); 5458 } 5459 } else if (!nearestBlockFound) { 5460 // offset here is the offset from f's baseline to f's top/left 5461 // boundary. It's descent for vertical-rl, and ascent otherwise. 5462 nscoord offset = wm.IsVerticalRL() 5463 ? f->GetSize().width - f->GetLogicalBaseline(wm) 5464 : f->GetLogicalBaseline(wm); 5465 baselineOffset = physicalBlockStartOffset - offset; 5466 } 5467 5468 nearestBlockFound = nearestBlockFound || firstBlock; 5469 physicalBlockStartOffset += 5470 vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y; 5471 5472 if (textDecorations) { 5473 nscolor color; 5474 if (useOverride) { 5475 color = overrideColor; 5476 } else if (IsInSVGTextSubtree()) { 5477 // XXX We might want to do something with text-decoration-color when 5478 // painting SVG text, but it's not clear what we should do. We 5479 // at least need SVG text decorations to paint with 'fill' if 5480 // text-decoration-color has its initial value currentColor. 5481 // We could choose to interpret currentColor as "currentFill" 5482 // for SVG text, and have e.g. text-decoration-color:red to 5483 // override the fill paint of the decoration. 5484 color = aColorResolution == eResolvedColors 5485 ? nsLayoutUtils::GetTextColor(f, &nsStyleSVG::mFill) 5486 : NS_SAME_AS_FOREGROUND_COLOR; 5487 } else { 5488 color = nsLayoutUtils::GetTextColor( 5489 f, &nsStyleTextReset::mTextDecorationColor); 5490 } 5491 5492 bool swapUnderlineAndOverline = 5493 wm.IsCentralBaseline() && IsUnderlineRight(*context); 5494 const auto kUnderline = swapUnderlineAndOverline 5495 ? StyleTextDecorationLine::OVERLINE 5496 : StyleTextDecorationLine::UNDERLINE; 5497 const auto kOverline = swapUnderlineAndOverline 5498 ? StyleTextDecorationLine::UNDERLINE 5499 : StyleTextDecorationLine::OVERLINE; 5500 5501 const nsStyleText* const styleText = context->StyleText(); 5502 const auto position = ignoreSubproperties 5503 ? StyleTextUnderlinePosition::AUTO 5504 : styleText->mTextUnderlinePosition; 5505 const auto offset = ignoreSubproperties ? LengthPercentageOrAuto::Auto() 5506 : styleText->mTextUnderlineOffset; 5507 const auto thickness = ignoreSubproperties 5508 ? StyleTextDecorationLength::Auto() 5509 : styleTextReset->mTextDecorationThickness; 5510 5511 if (textDecorations & kUnderline) { 5512 aDecorations.mUnderlines.AppendElement(nsTextFrame::LineDecoration( 5513 f, baselineOffset, position, offset, thickness, color, lineStyle, 5514 !ignoreSubproperties)); 5515 } 5516 if (textDecorations & kOverline) { 5517 aDecorations.mOverlines.AppendElement(nsTextFrame::LineDecoration( 5518 f, baselineOffset, position, offset, thickness, color, lineStyle, 5519 !ignoreSubproperties)); 5520 } 5521 if (textDecorations & StyleTextDecorationLine::LINE_THROUGH) { 5522 aDecorations.mStrikes.AppendElement(nsTextFrame::LineDecoration( 5523 f, baselineOffset, position, offset, thickness, color, lineStyle, 5524 !ignoreSubproperties)); 5525 } 5526 } 5527 if (FrameStopsLineDecorationPropagation(f, compatMode)) { 5528 break; 5529 } 5530 } 5531 } 5532 5533 static float GetInflationForTextDecorations(nsIFrame* aFrame, 5534 nscoord aInflationMinFontSize) { 5535 if (aFrame->IsInSVGTextSubtree()) { 5536 auto* container = 5537 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText); 5538 MOZ_ASSERT(container); 5539 return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor(); 5540 } 5541 return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize); 5542 } 5543 5544 struct EmphasisMarkInfo { 5545 RefPtr<gfxTextRun> textRun; 5546 gfxFloat advance; 5547 gfxFloat baselineOffset; 5548 }; 5549 5550 NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty, EmphasisMarkInfo) 5551 5552 static void ComputeTextEmphasisStyleString(const StyleTextEmphasisStyle& aStyle, 5553 nsAString& aOut) { 5554 MOZ_ASSERT(!aStyle.IsNone()); 5555 if (aStyle.IsString()) { 5556 nsDependentCSubstring string = aStyle.AsString().AsString(); 5557 AppendUTF8toUTF16(string, aOut); 5558 return; 5559 } 5560 const auto& keyword = aStyle.AsKeyword(); 5561 const bool fill = keyword.fill == StyleTextEmphasisFillMode::Filled; 5562 switch (keyword.shape) { 5563 case StyleTextEmphasisShapeKeyword::Dot: 5564 return aOut.AppendLiteral(fill ? u"\u2022" : u"\u25e6"); 5565 case StyleTextEmphasisShapeKeyword::Circle: 5566 return aOut.AppendLiteral(fill ? u"\u25cf" : u"\u25cb"); 5567 case StyleTextEmphasisShapeKeyword::DoubleCircle: 5568 return aOut.AppendLiteral(fill ? u"\u25c9" : u"\u25ce"); 5569 case StyleTextEmphasisShapeKeyword::Triangle: 5570 return aOut.AppendLiteral(fill ? u"\u25b2" : u"\u25b3"); 5571 case StyleTextEmphasisShapeKeyword::Sesame: 5572 return aOut.AppendLiteral(fill ? u"\ufe45" : u"\ufe46"); 5573 default: 5574 MOZ_ASSERT_UNREACHABLE("Unknown emphasis style shape"); 5575 } 5576 } 5577 5578 static already_AddRefed<gfxTextRun> GenerateTextRunForEmphasisMarks( 5579 nsTextFrame* aFrame, gfxFontGroup* aFontGroup, 5580 ComputedStyle* aComputedStyle, const nsStyleText* aStyleText) { 5581 nsAutoString string; 5582 ComputeTextEmphasisStyleString(aStyleText->mTextEmphasisStyle, string); 5583 5584 RefPtr<DrawTarget> dt = CreateReferenceDrawTarget(aFrame); 5585 auto appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel(); 5586 gfx::ShapedTextFlags flags = 5587 nsLayoutUtils::GetTextRunOrientFlagsForStyle(aComputedStyle); 5588 if (flags == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { 5589 // The emphasis marks should always be rendered upright per spec. 5590 flags = gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; 5591 } 5592 return aFontGroup->MakeTextRun<char16_t>(string.get(), string.Length(), dt, 5593 appUnitsPerDevUnit, flags, 5594 nsTextFrameUtils::Flags(), nullptr); 5595 } 5596 5597 static nsRubyFrame* FindFurthestInlineRubyAncestor(nsTextFrame* aFrame) { 5598 nsRubyFrame* rubyFrame = nullptr; 5599 for (nsIFrame* frame = aFrame->GetParent(); 5600 frame && frame->IsLineParticipant(); frame = frame->GetParent()) { 5601 if (frame->IsRubyFrame()) { 5602 rubyFrame = static_cast<nsRubyFrame*>(frame); 5603 } 5604 } 5605 return rubyFrame; 5606 } 5607 5608 nsRect nsTextFrame::UpdateTextEmphasis(WritingMode aWM, 5609 PropertyProvider& aProvider) { 5610 const nsStyleText* styleText = StyleText(); 5611 if (!styleText->HasEffectiveTextEmphasis()) { 5612 RemoveProperty(EmphasisMarkProperty()); 5613 return nsRect(); 5614 } 5615 5616 ComputedStyle* computedStyle = Style(); 5617 bool isTextCombined = computedStyle->IsTextCombined(); 5618 if (isTextCombined) { 5619 computedStyle = GetParent()->Style(); 5620 } 5621 RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsOfEmphasisMarks( 5622 computedStyle, PresContext(), GetFontSizeInflation()); 5623 EmphasisMarkInfo* info = new EmphasisMarkInfo; 5624 info->textRun = GenerateTextRunForEmphasisMarks( 5625 this, fm->GetThebesFontGroup(), computedStyle, styleText); 5626 info->advance = info->textRun->GetAdvanceWidth(); 5627 5628 bool normalizeRubyMetrics = PresContext()->NormalizeRubyMetrics(); 5629 float rubyMetricsFactor = 5630 normalizeRubyMetrics ? PresContext()->RubyPositioningFactor() : 0.0f; 5631 5632 // Calculate the baseline offset 5633 LogicalSide side = styleText->TextEmphasisSide(aWM, StyleFont()->mLanguage); 5634 LogicalSize frameSize = GetLogicalSize(aWM); 5635 // The overflow rect is inflated in the inline direction by half 5636 // advance of the emphasis mark on each side, so that even if a mark 5637 // is drawn for a zero-width character, it won't be clipped. 5638 LogicalRect overflowRect( 5639 aWM, -info->advance / 2, /* BStart to be computed below */ 0, 5640 frameSize.ISize(aWM) + info->advance, 5641 normalizeRubyMetrics 5642 ? rubyMetricsFactor * (fm->TrimmedAscent() + fm->TrimmedDescent()) 5643 : fm->MaxAscent() + fm->MaxDescent()); 5644 RefPtr<nsFontMetrics> baseFontMetrics = 5645 isTextCombined 5646 ? nsLayoutUtils::GetInflatedFontMetricsForFrame(GetParent()) 5647 : do_AddRef(aProvider.GetFontMetrics()); 5648 // When the writing mode is vertical-lr the line is inverted, and thus 5649 // the ascent and descent are swapped. 5650 bool startSideOrInvertedLine = 5651 (side == LogicalSide::BStart) != aWM.IsLineInverted(); 5652 nscoord absOffset; 5653 if (normalizeRubyMetrics) { 5654 absOffset = startSideOrInvertedLine 5655 ? baseFontMetrics->TrimmedAscent() + fm->TrimmedDescent() 5656 : baseFontMetrics->TrimmedDescent() + fm->TrimmedAscent(); 5657 absOffset *= rubyMetricsFactor; 5658 } else { 5659 absOffset = startSideOrInvertedLine 5660 ? baseFontMetrics->MaxAscent() + fm->MaxDescent() 5661 : baseFontMetrics->MaxDescent() + fm->MaxAscent(); 5662 } 5663 RubyBlockLeadings leadings; 5664 if (nsRubyFrame* ruby = FindFurthestInlineRubyAncestor(this)) { 5665 leadings = ruby->GetBlockLeadings(); 5666 if (normalizeRubyMetrics) { 5667 // Adjust absOffset to account for any ruby annotations that effectively 5668 // added to the trimmed height of the base text. 5669 auto [ascent, descent] = ruby->RubyMetrics(rubyMetricsFactor); 5670 absOffset = std::max(absOffset, side == LogicalSide::BStart 5671 ? ascent + fm->TrimmedDescent() 5672 : descent + fm->TrimmedAscent()); 5673 } 5674 } 5675 if (side == LogicalSide::BStart) { 5676 info->baselineOffset = 5677 normalizeRubyMetrics ? -absOffset : -absOffset - leadings.mStart; 5678 overflowRect.BStart(aWM) = -overflowRect.BSize(aWM) - leadings.mStart; 5679 } else { 5680 MOZ_ASSERT(side == LogicalSide::BEnd); 5681 info->baselineOffset = 5682 normalizeRubyMetrics ? absOffset : absOffset + leadings.mEnd; 5683 overflowRect.BStart(aWM) = frameSize.BSize(aWM) + leadings.mEnd; 5684 } 5685 // If text combined, fix the gap between the text frame and its parent. 5686 if (isTextCombined) { 5687 nscoord height = 5688 normalizeRubyMetrics 5689 ? rubyMetricsFactor * (baseFontMetrics->TrimmedAscent() + 5690 baseFontMetrics->TrimmedDescent()) 5691 : baseFontMetrics->MaxHeight(); 5692 nscoord gap = (height - frameSize.BSize(aWM)) / 2; 5693 overflowRect.BStart(aWM) += gap * (side == LogicalSide::BStart ? -1 : 1); 5694 } 5695 5696 SetProperty(EmphasisMarkProperty(), info); 5697 return overflowRect.GetPhysicalRect(aWM, frameSize.GetPhysicalSize(aWM)); 5698 } 5699 5700 // helper function for implementing text-decoration-thickness 5701 // https://drafts.csswg.org/css-text-decor-4/#text-decoration-width-property 5702 // Returns the thickness in device pixels. 5703 static gfxFloat ComputeDecorationLineThickness( 5704 const StyleTextDecorationLength& aThickness, const gfxFloat aAutoValue, 5705 const gfxFont::Metrics& aFontMetrics, const gfxFloat aAppUnitsPerDevPixel, 5706 const nsIFrame* aFrame) { 5707 if (aThickness.IsAuto()) { 5708 return widget::ThemeDrawing::SnapBorderWidth(aAutoValue); 5709 } 5710 if (aThickness.IsFromFont()) { 5711 return widget::ThemeDrawing::SnapBorderWidth(aFontMetrics.underlineSize); 5712 } 5713 auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); }; 5714 return widget::ThemeDrawing::SnapBorderWidth( 5715 aThickness.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel); 5716 } 5717 5718 // Helper function for implementing text-underline-offset and -position 5719 // https://drafts.csswg.org/css-text-decor-4/#underline-offset 5720 // Returns the offset in device pixels. 5721 static gfxFloat ComputeDecorationLineOffset( 5722 StyleTextDecorationLine aLineType, 5723 const StyleTextUnderlinePosition& aPosition, 5724 const LengthPercentageOrAuto& aOffset, const gfxFont::Metrics& aFontMetrics, 5725 const gfxFloat aAppUnitsPerDevPixel, const nsIFrame* aFrame, 5726 bool aIsCentralBaseline, bool aSwappedUnderline) { 5727 // Em value to use if we need to resolve a percentage length. 5728 auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); }; 5729 // If we're in vertical-upright typographic mode, we need to compute the 5730 // offset of the decoration line from the default central baseline. 5731 if (aIsCentralBaseline) { 5732 // Line-through simply goes at the (central) baseline. 5733 if (aLineType == StyleTextDecorationLine::LINE_THROUGH) { 5734 return 0; 5735 } 5736 5737 // Compute "zero position" for the under- or overline. 5738 gfxFloat zeroPos = 0.5 * aFontMetrics.emHeight; 5739 5740 // aOffset applies to underline only; for overline (or offset:auto) we use 5741 // a somewhat arbitrary offset of half the font's (horziontal-mode) value 5742 // for underline-offset, to get a little bit of separation between glyph 5743 // edges and the line in typical cases. 5744 // If we have swapped under-/overlines for text-underline-position:right, 5745 // we need to take account of this to determine which decoration lines are 5746 // "real" underlines which should respect the text-underline-* values. 5747 bool isUnderline = 5748 (aLineType == StyleTextDecorationLine::UNDERLINE) != aSwappedUnderline; 5749 gfxFloat offset = 5750 isUnderline && !aOffset.IsAuto() 5751 ? aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel 5752 : aFontMetrics.underlineOffset * -0.5; 5753 5754 // Direction of the decoration line's offset from the central baseline. 5755 gfxFloat dir = aLineType == StyleTextDecorationLine::OVERLINE ? 1.0 : -1.0; 5756 return dir * (zeroPos + offset); 5757 } 5758 5759 // Compute line offset for horizontal typographic mode. 5760 if (aLineType == StyleTextDecorationLine::UNDERLINE) { 5761 if (aPosition.IsFromFont()) { 5762 gfxFloat zeroPos = aFontMetrics.underlineOffset; 5763 gfxFloat offset = 5764 aOffset.IsAuto() 5765 ? 0 5766 : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel; 5767 return zeroPos - offset; 5768 } 5769 5770 if (aPosition.IsUnder()) { 5771 gfxFloat zeroPos = -aFontMetrics.maxDescent; 5772 gfxFloat offset = 5773 aOffset.IsAuto() 5774 ? -0.5 * aFontMetrics.underlineOffset 5775 : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel; 5776 return zeroPos - offset; 5777 } 5778 5779 // text-underline-position must be 'auto', so zero position is the 5780 // baseline and 'auto' offset will apply the font's underline-offset. 5781 // 5782 // If offset is `auto`, we clamp the offset (in horizontal typographic mode) 5783 // to a minimum of 1/16 em (equivalent to 1px at font-size 16px) to mitigate 5784 // skip-ink issues with fonts that leave the underlineOffset field as zero. 5785 MOZ_ASSERT(aPosition.IsAuto()); 5786 return aOffset.IsAuto() ? std::min(aFontMetrics.underlineOffset, 5787 -aFontMetrics.emHeight / 16.0) 5788 : -aOffset.AsLengthPercentage().Resolve(em) / 5789 aAppUnitsPerDevPixel; 5790 } 5791 5792 if (aLineType == StyleTextDecorationLine::OVERLINE) { 5793 return aFontMetrics.maxAscent; 5794 } 5795 5796 if (aLineType == StyleTextDecorationLine::LINE_THROUGH) { 5797 return aFontMetrics.strikeoutOffset; 5798 } 5799 5800 MOZ_ASSERT_UNREACHABLE("unknown decoration line type"); 5801 return 0; 5802 } 5803 5804 // Helper to determine decoration inset. 5805 // Returns false if the inset would cut off the decoration entirely. 5806 // If aOnlyExtend is true, this will only consider cases with negative inset 5807 // (i.e. the line will actually be extended beyond the normal length). 5808 static bool ComputeDecorationInset( 5809 nsTextFrame* aFrame, const nsPresContext* aPresCtx, 5810 const nsIFrame* aDecFrame, const gfxFont::Metrics& aMetrics, 5811 nsCSSRendering::DecorationRectParams& aParams, bool aOnlyExtend = false) { 5812 const WritingMode wm = aDecFrame->GetWritingMode(); 5813 bool verticalDec = wm.IsVertical(); 5814 5815 aParams.insetLeft = 0.0; 5816 aParams.insetRight = 0.0; 5817 5818 // Find the decoration-line inset values for this frame. 5819 const StyleTextDecorationInset& cssInset = 5820 aDecFrame->StyleTextReset()->mTextDecorationInset; 5821 nscoord insetLeft, insetRight; 5822 if (cssInset.IsAuto()) { 5823 // Use an inset factor of 1/12.5, so we get 2px of inset (resulting in 4px 5824 // gap between adjacent lines) at font-size 25px. 5825 constexpr gfxFloat kAutoInsetFactor = 1.0 / 12.5; 5826 // Use the EM size multiplied by kAutoInsetFactor, with a minimum of one 5827 // CSS pixel to ensure that at least some separation occurs. 5828 const nscoord autoDecorationInset = 5829 std::max(aPresCtx->DevPixelsToAppUnits( 5830 NS_round(aMetrics.emHeight * kAutoInsetFactor)), 5831 nsPresContext::CSSPixelsToAppUnits(1)); 5832 insetLeft = autoDecorationInset; 5833 insetRight = autoDecorationInset; 5834 } else { 5835 MOZ_ASSERT(cssInset.IsLength(), "Impossible text-decoration-inset"); 5836 const auto& length = cssInset.AsLength(); 5837 if (length.start.IsZero() && length.end.IsZero()) { 5838 // We can avoid doing the geometric calculations below, potentially 5839 // walking up and back down the frame tree, and walking continuations. 5840 return true; 5841 } 5842 insetLeft = length.start.ToAppUnits(); 5843 insetRight = length.end.ToAppUnits(); 5844 } 5845 5846 // If we only care about extended lines (for UnionAdditionalOverflow), 5847 // we can bail out if neither inset value is negative. 5848 if (aOnlyExtend && insetLeft >= 0 && insetRight >= 0) { 5849 return true; 5850 } 5851 5852 if (wm.IsInlineReversed()) { 5853 std::swap(insetLeft, insetRight); 5854 } 5855 5856 // The rect of the decorating box (if an inline) or of the current line (if 5857 // the decoration is propagated from a block ancestor). We will need to 5858 // compare this with the rect of the current frame, which may be only a 5859 // sub-range of the entire decorated range. 5860 nsRect decRect; 5861 5862 // The container of the decoration, or the fragment of it on this line. 5863 const nsIFrame* decContainer; 5864 5865 // If the decorating frame is an inline frame, we can use it as the 5866 // reference frame for measurements. 5867 // If the decorating frame is not inline, then we will need to consider 5868 // text indentation and calculate geometry using line boxes. 5869 if (aDecFrame->IsInlineFrame()) { 5870 decRect = aDecFrame->GetContentRectRelativeToSelf(); 5871 decContainer = aDecFrame; 5872 } else { 5873 nsIFrame* const lineContainer = FindLineContainer(aFrame); 5874 // If the frame specifies text-combined, then it might have an orthogonal 5875 // writing mode to the line container. 5876 MOZ_ASSERT( 5877 !wm.IsOrthogonalTo(lineContainer->GetWritingMode()) || 5878 aFrame->Style()->IsTextCombined(), 5879 "Decorating frame and line container must have writing modes in the " 5880 "same axis"); 5881 if (nsILineIterator* const iter = lineContainer->GetLineIterator()) { 5882 const int32_t lineNum = GetFrameLineNum(aFrame, iter); 5883 const nsILineIterator::LineInfo lineInfo = 5884 iter->GetLine(lineNum).unwrap(); 5885 decRect = lineInfo.mLineBounds; 5886 5887 // Account for text-indent, which will push text frames into the line box. 5888 const StyleTextIndent& textIndent = aFrame->StyleText()->mTextIndent; 5889 if (!textIndent.length.IsDefinitelyZero()) { 5890 bool isFirstLineOrAfterHardBreak = true; 5891 if (lineNum > 0 && !textIndent.each_line) { 5892 isFirstLineOrAfterHardBreak = false; 5893 } else if (nsBlockFrame* prevBlock = 5894 do_QueryFrame(lineContainer->GetPrevInFlow())) { 5895 if (!(textIndent.each_line && 5896 (prevBlock->Lines().empty() || 5897 !prevBlock->LinesEnd().prev()->IsLineWrapped()))) { 5898 isFirstLineOrAfterHardBreak = false; 5899 } 5900 } 5901 if (isFirstLineOrAfterHardBreak != textIndent.hanging) { 5902 // Determine which side to shrink. 5903 const Side side = wm.PhysicalSide(LogicalSide::IStart); 5904 // Calculate the text indent, and shrink the line box by this amount 5905 // to account for the indent size at the start of the line. 5906 const nscoord basis = lineContainer->GetLogicalSize(wm).ISize(wm); 5907 nsMargin indentMargin; 5908 indentMargin.Side(side) = textIndent.length.Resolve(basis); 5909 decRect.Deflate(indentMargin); 5910 } 5911 } 5912 5913 // We can't allow a block frame to retain a line iterator if we're 5914 // currently in reflow, as it will become invalid as the line list is 5915 // reflowed. 5916 if (lineContainer->HasAnyStateBits(NS_FRAME_IN_REFLOW) && 5917 lineContainer->IsBlockFrameOrSubclass()) { 5918 static_cast<nsBlockFrame*>(lineContainer)->ClearLineIterator(); 5919 } 5920 } else { 5921 // Not a block or similar container with multiple lines; just use the 5922 // content rect directly. 5923 decRect = lineContainer->GetContentRectRelativeToSelf(); 5924 } 5925 5926 decContainer = lineContainer; 5927 } 5928 5929 // The rect of the current frame, mapped to the same coordinate space as 5930 // decRect so that we can compare their edges. 5931 const nsRect frameRect = 5932 aFrame->GetRectRelativeToSelf() + aFrame->GetOffsetTo(decContainer); 5933 5934 // The nominal size of the decoration (prior to insets being applied) is 5935 // reduced by any margin, border, and padding present on frames intervening 5936 // between aFrame and decContainer. 5937 for (const nsIFrame* parent = aFrame->GetParent(); parent != decContainer; 5938 parent = parent->GetParent()) { 5939 decRect.Deflate(parent->GetUsedMargin()); 5940 decRect.Deflate(parent->GetUsedBorderAndPadding()); 5941 } 5942 5943 // Find the margin of the of this frame inside its container. 5944 nscoord marginLeft, marginRight, frameSize; 5945 const nsMargin difference = decRect - frameRect; 5946 if (verticalDec) { 5947 marginLeft = difference.top; 5948 marginRight = difference.bottom; 5949 frameSize = frameRect.height; 5950 } else { 5951 marginLeft = difference.left; 5952 marginRight = difference.right; 5953 frameSize = frameRect.width; 5954 } 5955 5956 const bool cloneDecBreak = aDecFrame->StyleBorder()->mBoxDecorationBreak == 5957 StyleBoxDecorationBreak::Clone; 5958 // TODO alaskanemily: This will not correctly account for the case that the 5959 // continuations are bidi continuations. 5960 bool applyLeft = cloneDecBreak || (!aFrame->GetPrevContinuation() && 5961 !aDecFrame->GetPrevContinuation()); 5962 bool applyRight = cloneDecBreak || (!aFrame->GetNextContinuation() && 5963 !aDecFrame->GetNextContinuation()); 5964 if (wm.IsInlineReversed()) { 5965 std::swap(applyLeft, applyRight); 5966 } 5967 if (applyLeft) { 5968 insetLeft -= marginLeft; 5969 } else { 5970 insetLeft = 0; 5971 } 5972 if (applyRight) { 5973 insetRight -= marginRight; 5974 } else { 5975 insetRight = 0; 5976 } 5977 5978 if (insetLeft + insetRight >= frameSize) { 5979 // This frame does not contain the decoration at all. 5980 return false; 5981 } 5982 // TODO alaskanemily: We currently determine if we should have a negative 5983 // inset value by checking if we are at the edge of frame from which the 5984 // decloration comes from. 5985 // 5986 // This is not absolutely correct, there could in theory be a zero-width 5987 // frame before/after this frame, and we will draw the decoration extension 5988 // twice (causing a visible inaccuracy for semi-transparent decorations). 5989 // 5990 // I am unsure if it's possible that the first/last frame might be inset 5991 // for some reason, as well, in which case we will not draw the outset 5992 // decorations. 5993 if (insetLeft > 0 || marginLeft == 0) { 5994 aParams.insetLeft = aPresCtx->AppUnitsToFloatDevPixels(insetLeft); 5995 } 5996 if (insetRight > 0 || marginRight == 0) { 5997 aParams.insetRight = aPresCtx->AppUnitsToFloatDevPixels(insetRight); 5998 } 5999 return true; 6000 } 6001 6002 void nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext, 6003 nsIFrame* aBlock, 6004 PropertyProvider& aProvider, 6005 nsRect* aInkOverflowRect, 6006 bool aIncludeTextDecorations, 6007 bool aIncludeShadows) { 6008 const WritingMode wm = GetWritingMode(); 6009 bool verticalRun = mTextRun->IsVertical(); 6010 const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(); 6011 6012 if (IsFloatingFirstLetterChild()) { 6013 bool inverted = wm.IsLineInverted(); 6014 // The underline/overline drawable area must be contained in the overflow 6015 // rect when this is in floating first letter frame at *both* modes. 6016 // In this case, aBlock is the ::first-letter frame. 6017 auto decorationStyle = 6018 aBlock->Style()->StyleTextReset()->mTextDecorationStyle; 6019 // If the style is none, let's include decoration line rect as solid style 6020 // since changing the style from none to solid/dotted/dashed doesn't cause 6021 // reflow. 6022 if (decorationStyle == StyleTextDecorationStyle::None) { 6023 decorationStyle = StyleTextDecorationStyle::Solid; 6024 } 6025 nsCSSRendering::DecorationRectParams params; 6026 bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline(); 6027 nsFontMetrics* fontMetrics = aProvider.GetFontMetrics(); 6028 RefPtr<gfxFont> font = 6029 fontMetrics->GetThebesFontGroup()->GetFirstValidFont(); 6030 const gfxFont::Metrics& metrics = 6031 font->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical 6032 : nsFontMetrics::eHorizontal); 6033 6034 params.defaultLineThickness = metrics.underlineSize; 6035 params.lineSize.height = ComputeDecorationLineThickness( 6036 aBlock->Style()->StyleTextReset()->mTextDecorationThickness, 6037 params.defaultLineThickness, metrics, appUnitsPerDevUnit, this); 6038 6039 const auto* styleText = aBlock->StyleText(); 6040 bool swapUnderline = 6041 wm.IsCentralBaseline() && IsUnderlineRight(*aBlock->Style()); 6042 params.offset = ComputeDecorationLineOffset( 6043 StyleTextDecorationLine::UNDERLINE, styleText->mTextUnderlinePosition, 6044 styleText->mTextUnderlineOffset, metrics, appUnitsPerDevUnit, this, 6045 wm.IsCentralBaseline(), swapUnderline); 6046 6047 nscoord maxAscent = 6048 inverted ? fontMetrics->MaxDescent() : fontMetrics->MaxAscent(); 6049 6050 Float gfxWidth = 6051 (verticalRun ? aInkOverflowRect->height : aInkOverflowRect->width) / 6052 appUnitsPerDevUnit; 6053 params.lineSize.width = gfxWidth; 6054 params.ascent = gfxFloat(mAscent) / appUnitsPerDevUnit; 6055 params.style = decorationStyle; 6056 params.vertical = verticalRun; 6057 params.sidewaysLeft = mTextRun->IsSidewaysLeft(); 6058 params.decoration = StyleTextDecorationLine::UNDERLINE; 6059 nsRect underlineRect = 6060 nsCSSRendering::GetTextDecorationRect(aPresContext, params); 6061 6062 // TODO(jfkthame): 6063 // Should we actually be calling ComputeDecorationLineOffset again here? 6064 params.offset = maxAscent / appUnitsPerDevUnit; 6065 params.decoration = StyleTextDecorationLine::OVERLINE; 6066 nsRect overlineRect = 6067 nsCSSRendering::GetTextDecorationRect(aPresContext, params); 6068 6069 aInkOverflowRect->UnionRect(*aInkOverflowRect, underlineRect); 6070 aInkOverflowRect->UnionRect(*aInkOverflowRect, overlineRect); 6071 6072 // XXX If strikeoutSize is much thicker than the underlineSize, it may 6073 // cause overflowing from the overflow rect. However, such case 6074 // isn't realistic, we don't need to compute it now. 6075 } 6076 if (aIncludeTextDecorations) { 6077 // Use writing mode of parent frame for orthogonal text frame to 6078 // work. See comment in nsTextFrame::DrawTextRunAndDecorations. 6079 WritingMode parentWM = GetParent()->GetWritingMode(); 6080 bool verticalDec = parentWM.IsVertical(); 6081 bool useVerticalMetrics = 6082 verticalDec != verticalRun 6083 ? verticalDec 6084 : verticalRun && mTextRun->UseCenterBaseline(); 6085 6086 // Since CSS 2.1 requires that text-decoration defined on ancestors maintain 6087 // style and position, they can be drawn at virtually any y-offset, so 6088 // maxima and minima are required to reliably generate the rectangle for 6089 // them 6090 TextDecorations textDecs; 6091 GetTextDecorations(aPresContext, eResolvedColors, textDecs); 6092 if (textDecs.HasDecorationLines()) { 6093 nscoord inflationMinFontSize = 6094 nsLayoutUtils::InflationMinFontSizeFor(aBlock); 6095 6096 const nscoord measure = verticalDec ? GetSize().height : GetSize().width; 6097 gfxFloat gfxWidth = measure / appUnitsPerDevUnit; 6098 gfxFloat ascent = 6099 gfxFloat(GetLogicalBaseline(parentWM)) / appUnitsPerDevUnit; 6100 nscoord frameBStart = 0; 6101 if (parentWM.IsVerticalRL()) { 6102 frameBStart = GetSize().width; 6103 ascent = -ascent; 6104 } 6105 6106 nsCSSRendering::DecorationRectParams params; 6107 params.lineSize = Size(gfxWidth, 0); 6108 params.ascent = ascent; 6109 params.vertical = verticalDec; 6110 params.sidewaysLeft = mTextRun->IsSidewaysLeft(); 6111 6112 typedef gfxFont::Metrics Metrics; 6113 auto accumulateDecorationRect = 6114 [&](const LineDecoration& dec, gfxFloat Metrics::* lineSize, 6115 mozilla::StyleTextDecorationLine lineType) { 6116 params.style = dec.mStyle; 6117 // If the style is solid, let's include decoration line rect of 6118 // solid style since changing the style from none to 6119 // solid/dotted/dashed doesn't cause reflow. 6120 if (params.style == StyleTextDecorationStyle::None) { 6121 params.style = StyleTextDecorationStyle::Solid; 6122 } 6123 6124 float inflation = GetInflationForTextDecorations( 6125 dec.mFrame, inflationMinFontSize); 6126 const Metrics metrics = 6127 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation), 6128 useVerticalMetrics); 6129 6130 params.defaultLineThickness = metrics.*lineSize; 6131 params.lineSize.height = ComputeDecorationLineThickness( 6132 dec.mTextDecorationThickness, params.defaultLineThickness, 6133 metrics, appUnitsPerDevUnit, this); 6134 6135 bool swapUnderline = 6136 parentWM.IsCentralBaseline() && IsUnderlineRight(*Style()); 6137 params.offset = ComputeDecorationLineOffset( 6138 lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset, 6139 metrics, appUnitsPerDevUnit, this, parentWM.IsCentralBaseline(), 6140 swapUnderline); 6141 6142 if (!ComputeDecorationInset(this, aPresContext, dec.mFrame, metrics, 6143 params, /* aOnlyExtend = */ true)) { 6144 return; 6145 } 6146 6147 nsRect decorationRect = 6148 nsCSSRendering::GetTextDecorationRect(aPresContext, params) + 6149 (verticalDec ? nsPoint(frameBStart - dec.mBaselineOffset, 0) 6150 : nsPoint(0, -dec.mBaselineOffset)); 6151 6152 aInkOverflowRect->UnionRect(*aInkOverflowRect, decorationRect); 6153 }; 6154 6155 // Below we loop through all text decorations and compute the rectangle 6156 // containing all of them, in this frame's coordinate space 6157 params.decoration = StyleTextDecorationLine::UNDERLINE; 6158 for (const LineDecoration& dec : textDecs.mUnderlines) { 6159 accumulateDecorationRect(dec, &Metrics::underlineSize, 6160 params.decoration); 6161 } 6162 params.decoration = StyleTextDecorationLine::OVERLINE; 6163 for (const LineDecoration& dec : textDecs.mOverlines) { 6164 accumulateDecorationRect(dec, &Metrics::underlineSize, 6165 params.decoration); 6166 } 6167 params.decoration = StyleTextDecorationLine::LINE_THROUGH; 6168 for (const LineDecoration& dec : textDecs.mStrikes) { 6169 accumulateDecorationRect(dec, &Metrics::strikeoutSize, 6170 params.decoration); 6171 } 6172 } 6173 6174 aInkOverflowRect->UnionRect(*aInkOverflowRect, 6175 UpdateTextEmphasis(parentWM, aProvider)); 6176 } 6177 6178 // text-stroke overflows: add half of text-stroke-width on all sides 6179 nscoord textStrokeWidth = StyleText()->mWebkitTextStrokeWidth; 6180 if (textStrokeWidth > 0) { 6181 // Inflate rect by stroke-width/2; we add an extra pixel to allow for 6182 // antialiasing, rounding errors, etc. 6183 nsRect strokeRect = *aInkOverflowRect; 6184 strokeRect.Inflate(textStrokeWidth / 2 + appUnitsPerDevUnit); 6185 aInkOverflowRect->UnionRect(*aInkOverflowRect, strokeRect); 6186 } 6187 6188 // Text-shadow overflows 6189 if (aIncludeShadows) { 6190 *aInkOverflowRect = 6191 nsLayoutUtils::GetTextShadowRectsUnion(*aInkOverflowRect, this); 6192 } 6193 6194 // When this frame is not selected, the text-decoration area must be in 6195 // frame bounds. 6196 if (!IsSelected() || 6197 !CombineSelectionUnderlineRect(aPresContext, *aInkOverflowRect)) { 6198 return; 6199 } 6200 AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED); 6201 } 6202 6203 nscoord nsTextFrame::ComputeLineHeight() const { 6204 return ReflowInput::CalcLineHeight(*Style(), PresContext(), GetContent(), 6205 NS_UNCONSTRAINEDSIZE, 6206 GetFontSizeInflation()); 6207 } 6208 6209 gfxFloat nsTextFrame::ComputeDescentLimitForSelectionUnderline( 6210 nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics) { 6211 const gfxFloat lineHeight = 6212 gfxFloat(ComputeLineHeight()) / aPresContext->AppUnitsPerDevPixel(); 6213 if (lineHeight <= aFontMetrics.maxHeight) { 6214 return aFontMetrics.maxDescent; 6215 } 6216 return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2; 6217 } 6218 6219 // Make sure this stays in sync with DrawSelectionDecorations below 6220 static constexpr SelectionTypeMask kSelectionTypesWithDecorations = 6221 ToSelectionTypeMask(SelectionType::eNormal) | 6222 ToSelectionTypeMask(SelectionType::eTargetText) | 6223 ToSelectionTypeMask(SelectionType::eHighlight) | 6224 ToSelectionTypeMask(SelectionType::eSpellCheck) | 6225 ToSelectionTypeMask(SelectionType::eURLStrikeout) | 6226 ToSelectionTypeMask(SelectionType::eIMERawClause) | 6227 ToSelectionTypeMask(SelectionType::eIMESelectedRawClause) | 6228 ToSelectionTypeMask(SelectionType::eIMEConvertedClause) | 6229 ToSelectionTypeMask(SelectionType::eIMESelectedClause); 6230 6231 /* static */ 6232 gfxFloat nsTextFrame::ComputeSelectionUnderlineHeight( 6233 nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics, 6234 SelectionType aSelectionType) { 6235 switch (aSelectionType) { 6236 case SelectionType::eNormal: 6237 case SelectionType::eTargetText: 6238 case SelectionType::eHighlight: 6239 case SelectionType::eIMERawClause: 6240 case SelectionType::eIMESelectedRawClause: 6241 case SelectionType::eIMEConvertedClause: 6242 case SelectionType::eIMESelectedClause: 6243 return aFontMetrics.underlineSize; 6244 case SelectionType::eSpellCheck: { 6245 // The thickness of the spellchecker underline shouldn't honor the font 6246 // metrics. It should be constant pixels value which is decided from the 6247 // default font size. Note that if the actual font size is smaller than 6248 // the default font size, we should use the actual font size because the 6249 // computed value from the default font size can be too thick for the 6250 // current font size. 6251 Length defaultFontSize = 6252 aPresContext->Document() 6253 ->GetFontPrefsForLang(nullptr) 6254 ->GetDefaultFont(StyleGenericFontFamily::None) 6255 ->size; 6256 int32_t zoomedFontSize = aPresContext->CSSPixelsToDevPixels( 6257 nsStyleFont::ZoomText(*aPresContext->Document(), defaultFontSize) 6258 .ToCSSPixels()); 6259 gfxFloat fontSize = 6260 std::min(gfxFloat(zoomedFontSize), aFontMetrics.emHeight); 6261 fontSize = std::max(fontSize, 1.0); 6262 return ceil(fontSize / 20); 6263 } 6264 default: 6265 NS_WARNING("Requested underline style is not valid"); 6266 return aFontMetrics.underlineSize; 6267 } 6268 } 6269 6270 enum class DecorationType { Normal, Selection }; 6271 struct nsTextFrame::PaintDecorationLineParams 6272 : nsCSSRendering::DecorationRectParams { 6273 gfxContext* context = nullptr; 6274 LayoutDeviceRect dirtyRect; 6275 Point pt; 6276 const nscolor* overrideColor = nullptr; 6277 nscolor color = NS_RGBA(0, 0, 0, 0); 6278 gfxFloat icoordInFrame = 0.0f; 6279 gfxFloat baselineOffset = 0.0f; 6280 DecorationType decorationType = DecorationType::Normal; 6281 DrawPathCallbacks* callbacks = nullptr; 6282 bool paintingShadows = false; 6283 bool allowInkSkipping = true; 6284 StyleTextDecorationSkipInk skipInk = StyleTextDecorationSkipInk::None; 6285 }; 6286 6287 void nsTextFrame::PaintDecorationLine( 6288 const PaintDecorationLineParams& aParams) { 6289 nsCSSRendering::PaintDecorationLineParams params; 6290 static_cast<nsCSSRendering::DecorationRectParams&>(params) = aParams; 6291 params.dirtyRect = aParams.dirtyRect.ToUnknownRect(); 6292 params.pt = aParams.pt; 6293 params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color; 6294 params.icoordInFrame = Float(aParams.icoordInFrame); 6295 params.baselineOffset = Float(aParams.baselineOffset); 6296 // Disable ink-skipping for frames with text-combine-upright. 6297 params.allowInkSkipping = 6298 aParams.allowInkSkipping && !Style()->IsTextCombined(); 6299 params.skipInk = aParams.skipInk; 6300 if (aParams.callbacks) { 6301 Rect path = nsCSSRendering::DecorationLineToPath(params); 6302 if (aParams.decorationType == DecorationType::Normal) { 6303 aParams.callbacks->PaintDecorationLine(path, aParams.paintingShadows, 6304 params.color); 6305 } else { 6306 aParams.callbacks->PaintSelectionDecorationLine( 6307 path, aParams.paintingShadows, params.color); 6308 } 6309 } else { 6310 nsCSSRendering::PaintDecorationLine(this, *aParams.context->GetDrawTarget(), 6311 params); 6312 } 6313 } 6314 6315 static StyleTextDecorationStyle ToStyleLineStyle(const TextRangeStyle& aStyle) { 6316 switch (aStyle.mLineStyle) { 6317 case TextRangeStyle::LineStyle::None: 6318 return StyleTextDecorationStyle::None; 6319 case TextRangeStyle::LineStyle::Solid: 6320 return StyleTextDecorationStyle::Solid; 6321 case TextRangeStyle::LineStyle::Dotted: 6322 return StyleTextDecorationStyle::Dotted; 6323 case TextRangeStyle::LineStyle::Dashed: 6324 return StyleTextDecorationStyle::Dashed; 6325 case TextRangeStyle::LineStyle::Double: 6326 return StyleTextDecorationStyle::Double; 6327 case TextRangeStyle::LineStyle::Wavy: 6328 return StyleTextDecorationStyle::Wavy; 6329 } 6330 MOZ_ASSERT_UNREACHABLE("Invalid line style"); 6331 return StyleTextDecorationStyle::None; 6332 } 6333 6334 /** 6335 * This, plus kSelectionTypesWithDecorations, encapsulates all knowledge 6336 * about drawing text decoration for selections. 6337 */ 6338 void nsTextFrame::DrawSelectionDecorations( 6339 gfxContext* aContext, const LayoutDeviceRect& aDirtyRect, 6340 SelectionType aSelectionType, nsAtom* aHighlightName, 6341 nsTextPaintStyle& aTextPaintStyle, const TextRangeStyle& aRangeStyle, 6342 const Point& aPt, gfxFloat aICoordInFrame, gfxFloat aWidth, 6343 gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics, 6344 DrawPathCallbacks* aCallbacks, bool aVertical, 6345 StyleTextDecorationLine aDecoration, const Range& aGlyphRange, 6346 PropertyProvider* aProvider) { 6347 PaintDecorationLineParams params; 6348 params.context = aContext; 6349 params.dirtyRect = aDirtyRect; 6350 params.pt = aPt; 6351 params.lineSize.width = aWidth; 6352 params.ascent = aAscent; 6353 params.decoration = aDecoration; 6354 params.decorationType = DecorationType::Selection; 6355 params.callbacks = aCallbacks; 6356 params.vertical = aVertical; 6357 params.sidewaysLeft = mTextRun->IsSidewaysLeft(); 6358 params.descentLimit = ComputeDescentLimitForSelectionUnderline( 6359 aTextPaintStyle.PresContext(), aFontMetrics); 6360 params.glyphRange = aGlyphRange; 6361 params.provider = aProvider; 6362 6363 float relativeSize = 1.f; 6364 const auto& decThickness = StyleTextReset()->mTextDecorationThickness; 6365 const gfxFloat appUnitsPerDevPixel = 6366 aTextPaintStyle.PresContext()->AppUnitsPerDevPixel(); 6367 6368 const WritingMode wm = GetWritingMode(); 6369 switch (aSelectionType) { 6370 case SelectionType::eNormal: 6371 case SelectionType::eHighlight: 6372 case SelectionType::eTargetText: { 6373 RefPtr computedStyleFromPseudo = 6374 aTextPaintStyle.GetComputedStyleForSelectionPseudo(aSelectionType, 6375 aHighlightName); 6376 const bool hasTextDecorations = 6377 computedStyleFromPseudo 6378 ? computedStyleFromPseudo->HasTextDecorationLines() 6379 : false; 6380 if (!hasTextDecorations) { 6381 return; 6382 } 6383 params.style = 6384 computedStyleFromPseudo->StyleTextReset()->mTextDecorationStyle; 6385 params.color = computedStyleFromPseudo->StyleTextReset() 6386 ->mTextDecorationColor.CalcColor(this); 6387 params.decoration = 6388 computedStyleFromPseudo->StyleTextReset()->mTextDecorationLine; 6389 params.descentLimit = -1.f; 6390 params.defaultLineThickness = ComputeSelectionUnderlineHeight( 6391 aTextPaintStyle.PresContext(), aFontMetrics, aSelectionType); 6392 params.lineSize.height = ComputeDecorationLineThickness( 6393 computedStyleFromPseudo->StyleTextReset()->mTextDecorationThickness, 6394 params.defaultLineThickness, aFontMetrics, appUnitsPerDevPixel, this); 6395 6396 const bool swapUnderline = 6397 wm.IsCentralBaseline() && IsUnderlineRight(*Style()); 6398 params.icoordInFrame = aICoordInFrame; 6399 auto paintForLine = [&](StyleTextDecorationLine decoration) { 6400 if (!(computedStyleFromPseudo->StyleTextReset()->mTextDecorationLine & 6401 decoration)) { 6402 return; 6403 } 6404 6405 params.allowInkSkipping = true; 6406 params.skipInk = 6407 computedStyleFromPseudo->StyleText()->mTextDecorationSkipInk; 6408 params.decoration = decoration; 6409 params.offset = ComputeDecorationLineOffset( 6410 params.decoration, 6411 computedStyleFromPseudo->StyleText()->mTextUnderlinePosition, 6412 computedStyleFromPseudo->StyleText()->mTextUnderlineOffset, 6413 aFontMetrics, appUnitsPerDevPixel, this, wm.IsCentralBaseline(), 6414 swapUnderline); 6415 6416 PaintDecorationLine(params); 6417 }; 6418 paintForLine(StyleTextDecorationLine::UNDERLINE); 6419 paintForLine(StyleTextDecorationLine::OVERLINE); 6420 paintForLine(StyleTextDecorationLine::LINE_THROUGH); 6421 return; 6422 } 6423 case SelectionType::eIMERawClause: 6424 case SelectionType::eIMESelectedRawClause: 6425 case SelectionType::eIMEConvertedClause: 6426 case SelectionType::eIMESelectedClause: 6427 case SelectionType::eSpellCheck: { 6428 auto index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType( 6429 aSelectionType); 6430 bool weDefineSelectionUnderline = 6431 aTextPaintStyle.GetSelectionUnderlineForPaint( 6432 index, ¶ms.color, &relativeSize, ¶ms.style); 6433 params.defaultLineThickness = ComputeSelectionUnderlineHeight( 6434 aTextPaintStyle.PresContext(), aFontMetrics, aSelectionType); 6435 params.lineSize.height = ComputeDecorationLineThickness( 6436 decThickness, params.defaultLineThickness, aFontMetrics, 6437 appUnitsPerDevPixel, this); 6438 6439 bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style()); 6440 const auto* styleText = StyleText(); 6441 params.offset = ComputeDecorationLineOffset( 6442 aDecoration, styleText->mTextUnderlinePosition, 6443 styleText->mTextUnderlineOffset, aFontMetrics, appUnitsPerDevPixel, 6444 this, wm.IsCentralBaseline(), swapUnderline); 6445 6446 bool isIMEType = aSelectionType != SelectionType::eSpellCheck; 6447 6448 if (isIMEType) { 6449 // IME decoration lines should not be drawn on the both ends, i.e., we 6450 // need to cut both edges of the decoration lines. Because same style 6451 // IME selections can adjoin, but the users need to be able to know 6452 // where are the boundaries of the selections. 6453 // 6454 // X: underline 6455 // 6456 // IME selection #1 IME selection #2 IME selection #3 6457 // | | | 6458 // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX 6459 // +---------------------+----------------------+-------------------- 6460 // ^ ^ ^ ^ ^ 6461 // gap gap gap 6462 params.pt.x += 1.0; 6463 params.lineSize.width -= 2.0; 6464 } 6465 if (isIMEType && aRangeStyle.IsDefined()) { 6466 // If IME defines the style, that should override our definition. 6467 if (aRangeStyle.IsLineStyleDefined()) { 6468 if (aRangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) { 6469 return; 6470 } 6471 params.style = ToStyleLineStyle(aRangeStyle); 6472 relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f; 6473 } else if (!weDefineSelectionUnderline) { 6474 // There is no underline style definition. 6475 return; 6476 } 6477 // If underline color is defined and that doesn't depend on the 6478 // foreground color, we should use the color directly. 6479 if (aRangeStyle.IsUnderlineColorDefined() && 6480 (!aRangeStyle.IsForegroundColorDefined() || 6481 aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor)) { 6482 params.color = aRangeStyle.mUnderlineColor; 6483 } 6484 // If foreground color or background color is defined, the both colors 6485 // are computed by GetSelectionTextColors(). Then, we should use its 6486 // foreground color always. The color should have sufficient contrast 6487 // with the background color. 6488 else if (aRangeStyle.IsForegroundColorDefined() || 6489 aRangeStyle.IsBackgroundColorDefined()) { 6490 nscolor bg; 6491 GetSelectionTextColors(aSelectionType, nullptr, aTextPaintStyle, 6492 aRangeStyle, ¶ms.color, &bg); 6493 } 6494 // Otherwise, use the foreground color of the frame. 6495 else { 6496 params.color = aTextPaintStyle.GetTextColor(); 6497 } 6498 } else if (!weDefineSelectionUnderline) { 6499 // IME doesn't specify the selection style and we don't define selection 6500 // underline. 6501 return; 6502 } 6503 break; 6504 } 6505 case SelectionType::eURLStrikeout: { 6506 nscoord inflationMinFontSize = 6507 nsLayoutUtils::InflationMinFontSizeFor(this); 6508 float inflation = 6509 GetInflationForTextDecorations(this, inflationMinFontSize); 6510 const gfxFont::Metrics metrics = 6511 GetFirstFontMetrics(GetFontGroupForFrame(this, inflation), aVertical); 6512 6513 relativeSize = 2.0f; 6514 aTextPaintStyle.GetURLSecondaryColor(¶ms.color); 6515 params.style = StyleTextDecorationStyle::Solid; 6516 params.defaultLineThickness = metrics.strikeoutSize; 6517 params.lineSize.height = ComputeDecorationLineThickness( 6518 decThickness, params.defaultLineThickness, metrics, 6519 appUnitsPerDevPixel, this); 6520 // TODO(jfkthame): ComputeDecorationLineOffset? check vertical mode! 6521 params.offset = metrics.strikeoutOffset + 0.5; 6522 params.decoration = StyleTextDecorationLine::LINE_THROUGH; 6523 break; 6524 } 6525 default: 6526 NS_WARNING("Requested selection decorations when there aren't any"); 6527 return; 6528 } 6529 params.lineSize.height *= relativeSize; 6530 params.defaultLineThickness *= relativeSize; 6531 params.icoordInFrame = 6532 (aVertical ? params.pt.y - aPt.y : params.pt.x - aPt.x) + aICoordInFrame; 6533 PaintDecorationLine(params); 6534 } 6535 6536 /* static */ 6537 bool nsTextFrame::GetSelectionTextColors(SelectionType aSelectionType, 6538 nsAtom* aHighlightName, 6539 nsTextPaintStyle& aTextPaintStyle, 6540 const TextRangeStyle& aRangeStyle, 6541 nscolor* aForeground, 6542 nscolor* aBackground) { 6543 switch (aSelectionType) { 6544 case SelectionType::eNormal: 6545 return aTextPaintStyle.GetSelectionColors(aForeground, aBackground); 6546 case SelectionType::eFind: 6547 aTextPaintStyle.GetHighlightColors(aForeground, aBackground); 6548 return true; 6549 case SelectionType::eHighlight: { 6550 // Intentionally not short-cutting here because the called methods have 6551 // side-effects that affect outparams. 6552 bool hasForeground = aTextPaintStyle.GetCustomHighlightTextColor( 6553 aHighlightName, aForeground); 6554 bool hasBackground = aTextPaintStyle.GetCustomHighlightBackgroundColor( 6555 aHighlightName, aBackground); 6556 return hasForeground || hasBackground; 6557 } 6558 case SelectionType::eTargetText: { 6559 aTextPaintStyle.GetTargetTextColors(aForeground, aBackground); 6560 return true; 6561 } 6562 case SelectionType::eURLSecondary: 6563 aTextPaintStyle.GetURLSecondaryColor(aForeground); 6564 *aBackground = NS_RGBA(0, 0, 0, 0); 6565 return true; 6566 case SelectionType::eIMERawClause: 6567 case SelectionType::eIMESelectedRawClause: 6568 case SelectionType::eIMEConvertedClause: 6569 case SelectionType::eIMESelectedClause: 6570 if (aRangeStyle.IsDefined()) { 6571 if (!aRangeStyle.IsForegroundColorDefined() && 6572 !aRangeStyle.IsBackgroundColorDefined()) { 6573 *aForeground = aTextPaintStyle.GetTextColor(); 6574 *aBackground = NS_RGBA(0, 0, 0, 0); 6575 return false; 6576 } 6577 if (aRangeStyle.IsForegroundColorDefined()) { 6578 *aForeground = aRangeStyle.mForegroundColor; 6579 if (aRangeStyle.IsBackgroundColorDefined()) { 6580 *aBackground = aRangeStyle.mBackgroundColor; 6581 } else { 6582 // If foreground color is defined but background color isn't 6583 // defined, we can guess that IME must expect that the background 6584 // color is system's default field background color. 6585 *aBackground = aTextPaintStyle.GetSystemFieldBackgroundColor(); 6586 } 6587 } else { // aRangeStyle.IsBackgroundColorDefined() is true 6588 *aBackground = aRangeStyle.mBackgroundColor; 6589 // If background color is defined but foreground color isn't defined, 6590 // we can assume that IME must expect that the foreground color is 6591 // same as system's field text color. 6592 *aForeground = aTextPaintStyle.GetSystemFieldForegroundColor(); 6593 } 6594 return true; 6595 } 6596 aTextPaintStyle.GetIMESelectionColors( 6597 nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType( 6598 aSelectionType), 6599 aForeground, aBackground); 6600 return true; 6601 default: 6602 *aForeground = aTextPaintStyle.GetTextColor(); 6603 *aBackground = NS_RGBA(0, 0, 0, 0); 6604 return false; 6605 } 6606 } 6607 6608 /** 6609 * This sets *aShadows to the appropriate shadows, if any, for the given 6610 * type of selection. 6611 * If text-shadow was not specified, *aShadows is left untouched. 6612 */ 6613 void nsTextFrame::GetSelectionTextShadow( 6614 SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle, 6615 Span<const StyleSimpleShadow>* aShadows) { 6616 if (aSelectionType != SelectionType::eNormal) { 6617 return; 6618 } 6619 aTextPaintStyle.GetSelectionShadow(aShadows); 6620 } 6621 6622 /** 6623 * This class lets us iterate over chunks of text recorded in an array of 6624 * resolved selection ranges, observing cluster boundaries, in content order, 6625 * maintaining the current x-offset as we go, and telling whether the text 6626 * chunk has a hyphen after it or not. 6627 * In addition to returning the selected chunks, the iterator is responsible 6628 * to interpolate unselected chunks in any gaps between them. 6629 * The caller is responsible for actually computing the advance width of each 6630 * chunk. 6631 */ 6632 class MOZ_STACK_CLASS SelectionRangeIterator { 6633 using PropertyProvider = nsTextFrame::PropertyProvider; 6634 using CombinedSelectionRange = nsTextFrame::PriorityOrderedSelectionsForRange; 6635 6636 public: 6637 // aSelectionRanges and aRange are according to the original string. 6638 SelectionRangeIterator( 6639 const nsTArray<CombinedSelectionRange>& aSelectionRanges, 6640 gfxTextRun::Range aRange, PropertyProvider& aProvider, 6641 gfxTextRun* aTextRun, gfxFloat aXOffset); 6642 6643 bool GetNextSegment(gfxFloat* aXOffset, gfxTextRun::Range* aRange, 6644 gfxFloat* aHyphenWidth, 6645 nsTArray<SelectionType>& aSelectionType, 6646 nsTArray<RefPtr<nsAtom>>& aHighlightName, 6647 nsTArray<TextRangeStyle>& aStyle); 6648 6649 void UpdateWithAdvance(gfxFloat aAdvance) { 6650 mXOffset += aAdvance * mTextRun->GetDirection(); 6651 } 6652 6653 private: 6654 const nsTArray<CombinedSelectionRange>& mSelectionRanges; 6655 PropertyProvider& mProvider; 6656 gfxTextRun* mTextRun; 6657 gfxSkipCharsIterator mIterator; 6658 gfxTextRun::Range mOriginalRange; 6659 gfxFloat mXOffset; 6660 uint32_t mIndex; 6661 }; 6662 6663 SelectionRangeIterator::SelectionRangeIterator( 6664 const nsTArray<nsTextFrame::PriorityOrderedSelectionsForRange>& 6665 aSelectionRanges, 6666 gfxTextRun::Range aRange, PropertyProvider& aProvider, gfxTextRun* aTextRun, 6667 gfxFloat aXOffset) 6668 : mSelectionRanges(aSelectionRanges), 6669 mProvider(aProvider), 6670 mTextRun(aTextRun), 6671 mIterator(aProvider.GetStart()), 6672 mOriginalRange(aRange), 6673 mXOffset(aXOffset), 6674 mIndex(0) { 6675 mIterator.SetOriginalOffset(int32_t(aRange.start)); 6676 } 6677 6678 bool SelectionRangeIterator::GetNextSegment( 6679 gfxFloat* aXOffset, gfxTextRun::Range* aRange, gfxFloat* aHyphenWidth, 6680 nsTArray<SelectionType>& aSelectionType, 6681 nsTArray<RefPtr<nsAtom>>& aHighlightName, 6682 nsTArray<TextRangeStyle>& aStyle) { 6683 if (mIterator.GetOriginalOffset() >= int32_t(mOriginalRange.end)) { 6684 return false; 6685 } 6686 6687 uint32_t runOffset = mIterator.GetSkippedOffset(); 6688 uint32_t segmentEnd = mOriginalRange.end; 6689 6690 aSelectionType.Clear(); 6691 aHighlightName.Clear(); 6692 aStyle.Clear(); 6693 6694 if (mIndex == mSelectionRanges.Length() || 6695 mIterator.GetOriginalOffset() < 6696 int32_t(mSelectionRanges[mIndex].mRange.start)) { 6697 // There's an unselected segment before the next range (or at the end). 6698 aSelectionType.AppendElement(SelectionType::eNone); 6699 aHighlightName.AppendElement(); 6700 aStyle.AppendElement(TextRangeStyle()); 6701 if (mIndex < mSelectionRanges.Length()) { 6702 segmentEnd = mSelectionRanges[mIndex].mRange.start; 6703 } 6704 } else { 6705 // Get the selection details for the next segment, and increment index. 6706 for (const SelectionDetails* sdptr : 6707 mSelectionRanges[mIndex].mSelectionRanges) { 6708 aSelectionType.AppendElement(sdptr->mSelectionType); 6709 aHighlightName.AppendElement(sdptr->mHighlightData.mHighlightName); 6710 aStyle.AppendElement(sdptr->mTextRangeStyle); 6711 } 6712 segmentEnd = mSelectionRanges[mIndex].mRange.end; 6713 ++mIndex; 6714 } 6715 6716 // Advance iterator to the end of the segment. 6717 mIterator.SetOriginalOffset(int32_t(segmentEnd)); 6718 6719 // Further advance if necessary to a cluster boundary. 6720 while (mIterator.GetOriginalOffset() < int32_t(mOriginalRange.end) && 6721 !mIterator.IsOriginalCharSkipped() && 6722 !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) { 6723 mIterator.AdvanceOriginal(1); 6724 } 6725 6726 aRange->start = runOffset; 6727 aRange->end = mIterator.GetSkippedOffset(); 6728 *aXOffset = mXOffset; 6729 *aHyphenWidth = 0; 6730 if (mIterator.GetOriginalOffset() == int32_t(mOriginalRange.end) && 6731 mProvider.GetFrame()->HasAnyStateBits(TEXT_HYPHEN_BREAK)) { 6732 *aHyphenWidth = mProvider.GetHyphenWidth(); 6733 } 6734 6735 return true; 6736 } 6737 6738 static void AddHyphenToMetrics(nsTextFrame* aTextFrame, bool aIsRightToLeft, 6739 gfxTextRun::Metrics* aMetrics, 6740 gfxFont::BoundingBoxType aBoundingBoxType, 6741 DrawTarget* aDrawTarget) { 6742 // Fix up metrics to include hyphen 6743 RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(aTextFrame, aDrawTarget); 6744 if (!hyphenTextRun) { 6745 return; 6746 } 6747 6748 gfxTextRun::Metrics hyphenMetrics = 6749 hyphenTextRun->MeasureText(aBoundingBoxType, aDrawTarget); 6750 if (aTextFrame->GetWritingMode().IsLineInverted()) { 6751 hyphenMetrics.mBoundingBox.y = -hyphenMetrics.mBoundingBox.YMost(); 6752 } 6753 aMetrics->CombineWith(hyphenMetrics, aIsRightToLeft); 6754 } 6755 6756 void nsTextFrame::PaintOneShadow(const PaintShadowParams& aParams, 6757 const StyleSimpleShadow& aShadowDetails, 6758 gfxRect& aBoundingBox, uint32_t aBlurFlags) { 6759 AUTO_PROFILER_LABEL("nsTextFrame::PaintOneShadow", GRAPHICS); 6760 6761 nsPoint shadowOffset(aShadowDetails.horizontal.ToAppUnits(), 6762 aShadowDetails.vertical.ToAppUnits()); 6763 nscoord blurRadius = std::max(aShadowDetails.blur.ToAppUnits(), 0); 6764 6765 nscolor shadowColor = aShadowDetails.color.CalcColor(aParams.foregroundColor); 6766 6767 if (auto* textDrawer = aParams.context->GetTextDrawer()) { 6768 wr::Shadow wrShadow; 6769 6770 wrShadow.offset = {PresContext()->AppUnitsToFloatDevPixels(shadowOffset.x), 6771 PresContext()->AppUnitsToFloatDevPixels(shadowOffset.y)}; 6772 6773 wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(blurRadius); 6774 wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor)); 6775 6776 bool inflate = true; 6777 textDrawer->AppendShadow(wrShadow, inflate); 6778 return; 6779 } 6780 6781 // This rect is the box which is equivalent to where the shadow will be 6782 // painted. The origin of aBoundingBox is the text baseline left, so we must 6783 // translate it by that much in order to make the origin the top-left corner 6784 // of the text bounding box. Note that aLeftSideOffset is line-left, so 6785 // actually means top offset in vertical writing modes. 6786 gfxRect shadowGfxRect; 6787 WritingMode wm = GetWritingMode(); 6788 if (wm.IsVertical()) { 6789 shadowGfxRect = aBoundingBox; 6790 if (wm.IsVerticalRL()) { 6791 // for vertical-RL, reverse direction of x-coords of bounding box 6792 shadowGfxRect.x = -shadowGfxRect.XMost(); 6793 } 6794 shadowGfxRect += gfxPoint(aParams.textBaselinePt.x, 6795 aParams.framePt.y + aParams.leftSideOffset); 6796 } else { 6797 shadowGfxRect = 6798 aBoundingBox + gfxPoint(aParams.framePt.x + aParams.leftSideOffset, 6799 aParams.textBaselinePt.y); 6800 } 6801 Point shadowGfxOffset(shadowOffset.x, shadowOffset.y); 6802 shadowGfxRect += gfxPoint(shadowGfxOffset.x, shadowOffset.y); 6803 6804 nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()), 6805 NSToCoordRound(shadowGfxRect.Y()), 6806 NSToCoordRound(shadowGfxRect.Width()), 6807 NSToCoordRound(shadowGfxRect.Height())); 6808 6809 nsContextBoxBlur contextBoxBlur; 6810 const auto A2D = PresContext()->AppUnitsPerDevPixel(); 6811 gfxContext* shadowContext = 6812 contextBoxBlur.Init(shadowRect, 0, blurRadius, A2D, aParams.context, 6813 LayoutDevicePixel::ToAppUnits(aParams.dirtyRect, A2D), 6814 nullptr, aBlurFlags); 6815 if (!shadowContext) { 6816 return; 6817 } 6818 6819 aParams.context->Save(); 6820 aParams.context->SetColor(sRGBColor::FromABGR(shadowColor)); 6821 6822 // Draw the text onto our alpha-only surface to capture the alpha values. 6823 // Remember that the box blur context has a device offset on it, so we don't 6824 // need to translate any coordinates to fit on the surface. 6825 gfxFloat advanceWidth; 6826 nsTextPaintStyle textPaintStyle(this); 6827 DrawTextParams params(shadowContext, PresContext()->FontPaletteCache()); 6828 params.paintingShadows = true; 6829 params.advanceWidth = &advanceWidth; 6830 params.dirtyRect = aParams.dirtyRect; 6831 params.framePt = aParams.framePt + shadowGfxOffset; 6832 params.provider = aParams.provider; 6833 params.textStyle = &textPaintStyle; 6834 params.textColor = 6835 aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0); 6836 params.callbacks = aParams.callbacks; 6837 params.clipEdges = aParams.clipEdges; 6838 params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK); 6839 // Multi-color shadow is not allowed, so we use the same color as the text 6840 // color. 6841 params.decorationOverrideColor = ¶ms.textColor; 6842 params.fontPalette = StyleFont()->GetFontPaletteAtom(); 6843 6844 DrawText(aParams.range, aParams.textBaselinePt + shadowGfxOffset, params); 6845 6846 contextBoxBlur.DoPaint(); 6847 aParams.context->Restore(); 6848 } 6849 6850 /* static */ 6851 SelectionTypeMask nsTextFrame::CreateSelectionRangeList( 6852 const SelectionDetails* aDetails, SelectionType aSelectionType, 6853 const PaintTextSelectionParams& aParams, 6854 nsTArray<SelectionRange>& aSelectionRanges, bool* aAnyBackgrounds) { 6855 SelectionTypeMask allTypes = 0; 6856 bool anyBackgrounds = false; 6857 6858 uint32_t priorityOfInsertionOrder = 0; 6859 for (const SelectionDetails* sd = aDetails; sd; sd = sd->mNext.get()) { 6860 MOZ_ASSERT(sd->mStart >= 0 && sd->mEnd >= 0); // XXX make unsigned? 6861 uint32_t start = std::max(aParams.contentRange.start, uint32_t(sd->mStart)); 6862 uint32_t end = std::min(aParams.contentRange.end, uint32_t(sd->mEnd)); 6863 if (start < end) { 6864 // The PaintTextWithSelectionColors caller passes SelectionType::eNone, 6865 // so we collect all selections that set colors, and prioritize them 6866 // according to selection type (lower types take precedence). 6867 if (aSelectionType == SelectionType::eNone) { 6868 allTypes |= ToSelectionTypeMask(sd->mSelectionType); 6869 // Ignore selections that don't set colors. 6870 nscolor foreground(0), background(0); 6871 if (GetSelectionTextColors(sd->mSelectionType, 6872 sd->mHighlightData.mHighlightName, 6873 *aParams.textPaintStyle, sd->mTextRangeStyle, 6874 &foreground, &background)) { 6875 if (NS_GET_A(background) > 0) { 6876 anyBackgrounds = true; 6877 } 6878 aSelectionRanges.AppendElement( 6879 SelectionRange{sd, {start, end}, priorityOfInsertionOrder++}); 6880 } 6881 } else if (sd->mSelectionType == aSelectionType) { 6882 // The PaintSelectionTextDecorations caller passes a specific type, 6883 // so we include only ranges of that type, and keep them in order 6884 // so that later ones take precedence over earlier. 6885 aSelectionRanges.AppendElement( 6886 SelectionRange{sd, {start, end}, priorityOfInsertionOrder++}); 6887 } 6888 } 6889 } 6890 if (aAnyBackgrounds) { 6891 *aAnyBackgrounds = anyBackgrounds; 6892 } 6893 return allTypes; 6894 } 6895 6896 /* static */ 6897 void nsTextFrame::CombineSelectionRanges( 6898 const nsTArray<SelectionRange>& aSelectionRanges, 6899 nsTArray<PriorityOrderedSelectionsForRange>& aCombinedSelectionRanges) { 6900 struct SelectionRangeEndCmp { 6901 bool Equals(const SelectionRange* a, const SelectionRange* b) const { 6902 return a->mRange.end == b->mRange.end; 6903 } 6904 bool LessThan(const SelectionRange* a, const SelectionRange* b) const { 6905 return a->mRange.end < b->mRange.end; 6906 } 6907 }; 6908 6909 struct SelectionRangePriorityCmp { 6910 bool Equals(const SelectionRange* a, const SelectionRange* b) const { 6911 const SelectionDetails* aDetails = a->mDetails; 6912 const SelectionDetails* bDetails = b->mDetails; 6913 if (aDetails->mSelectionType != bDetails->mSelectionType) { 6914 return false; 6915 } 6916 if (aDetails->mSelectionType != SelectionType::eHighlight) { 6917 return a->mPriority == b->mPriority; 6918 } 6919 if (aDetails->mHighlightData.mHighlight->Priority() != 6920 bDetails->mHighlightData.mHighlight->Priority()) { 6921 return false; 6922 } 6923 return a->mPriority == b->mPriority; 6924 } 6925 6926 bool LessThan(const SelectionRange* a, const SelectionRange* b) const { 6927 if (a->mDetails->mSelectionType != b->mDetails->mSelectionType) { 6928 // Even though this looks counter-intuitive, 6929 // this is intended, as values in `SelectionType` are inverted: 6930 // a lower value indicates a higher priority. 6931 return a->mDetails->mSelectionType > b->mDetails->mSelectionType; 6932 } 6933 6934 if (a->mDetails->mSelectionType != SelectionType::eHighlight) { 6935 // for non-highlights, the selection which was added later 6936 // has a higher priority. 6937 return a->mPriority < b->mPriority; 6938 } 6939 6940 if (a->mDetails->mHighlightData.mHighlight->Priority() != 6941 b->mDetails->mHighlightData.mHighlight->Priority()) { 6942 // For highlights, first compare the priorities set by the user. 6943 return a->mDetails->mHighlightData.mHighlight->Priority() < 6944 b->mDetails->mHighlightData.mHighlight->Priority(); 6945 } 6946 // only if the user priorities are equal, let the highlight that was added 6947 // later take precedence. 6948 return a->mPriority < b->mPriority; 6949 } 6950 }; 6951 6952 uint32_t currentOffset = 0; 6953 AutoTArray<const SelectionRange*, 1> activeSelectionsForCurrentSegment; 6954 size_t rangeIndex = 0; 6955 6956 // Divide the given selection ranges into segments which share the same 6957 // set of selections. 6958 // The following algorithm iterates `aSelectionRanges`, assuming 6959 // that its elements are sorted by their start offset. 6960 // Each time a new selection starts, it is pushed into an array of 6961 // "currently present" selections, sorted by their *end* offset. 6962 // For each iteration the next segment end offset is determined, 6963 // which is either the start offset of the next selection or 6964 // the next end offset of all "currently present" selections 6965 // (which is always the first element of the array because of its order). 6966 // Then, a `CombinedSelectionRange` can be constructed, which describes 6967 // the text segment until its end offset (as determined above), and contains 6968 // all elements of the "currently present" selection list, now sorted 6969 // by their priority. 6970 // If a range ends at the given offset, it is removed from the array. 6971 while (rangeIndex < aSelectionRanges.Length() || 6972 !activeSelectionsForCurrentSegment.IsEmpty()) { 6973 uint32_t currentSegmentEndOffset = 6974 activeSelectionsForCurrentSegment.IsEmpty() 6975 ? -1 6976 : activeSelectionsForCurrentSegment[0]->mRange.end; 6977 uint32_t nextRangeStartOffset = 6978 rangeIndex < aSelectionRanges.Length() 6979 ? aSelectionRanges[rangeIndex].mRange.start 6980 : -1; 6981 uint32_t nextOffset = 6982 std::min(currentSegmentEndOffset, nextRangeStartOffset); 6983 if (!activeSelectionsForCurrentSegment.IsEmpty() && 6984 currentOffset != nextOffset) { 6985 auto activeSelectionRangesSortedByPriority = 6986 activeSelectionsForCurrentSegment.Clone(); 6987 activeSelectionRangesSortedByPriority.Sort(SelectionRangePriorityCmp()); 6988 6989 AutoTArray<const SelectionDetails*, 1> selectionDetails; 6990 selectionDetails.SetCapacity( 6991 activeSelectionRangesSortedByPriority.Length()); 6992 // ensure that overlapping highlights which have the same name 6993 // are only added once. If added each time, they would be painted 6994 // several times (see wpt 6995 // /css/css-highlight-api/painting/custom-highlight-painting-003.html) 6996 // Comparing the highlight name with the previous one is 6997 // sufficient here because selections are already sorted 6998 // in a way that ensures that highlights of the same name are 6999 // grouped together. 7000 nsAtom* currentHighlightName = nullptr; 7001 for (const auto* selectionRange : activeSelectionRangesSortedByPriority) { 7002 if (selectionRange->mDetails->mSelectionType == 7003 SelectionType::eHighlight) { 7004 if (selectionRange->mDetails->mHighlightData.mHighlightName == 7005 currentHighlightName) { 7006 continue; 7007 } 7008 currentHighlightName = 7009 selectionRange->mDetails->mHighlightData.mHighlightName; 7010 } 7011 selectionDetails.AppendElement(selectionRange->mDetails); 7012 } 7013 aCombinedSelectionRanges.AppendElement(PriorityOrderedSelectionsForRange{ 7014 std::move(selectionDetails), {currentOffset, nextOffset}}); 7015 } 7016 currentOffset = nextOffset; 7017 7018 if (nextRangeStartOffset < currentSegmentEndOffset) { 7019 activeSelectionsForCurrentSegment.InsertElementSorted( 7020 &aSelectionRanges[rangeIndex++], SelectionRangeEndCmp()); 7021 } else { 7022 activeSelectionsForCurrentSegment.RemoveElementAt(0); 7023 } 7024 } 7025 } 7026 7027 SelectionTypeMask nsTextFrame::ResolveSelections( 7028 const PaintTextSelectionParams& aParams, const SelectionDetails* aDetails, 7029 nsTArray<PriorityOrderedSelectionsForRange>& aResult, 7030 SelectionType aSelectionType, bool* aAnyBackgrounds) const { 7031 AutoTArray<SelectionRange, 4> selectionRanges; 7032 7033 SelectionTypeMask allTypes = CreateSelectionRangeList( 7034 aDetails, aSelectionType, aParams, selectionRanges, aAnyBackgrounds); 7035 7036 if (selectionRanges.IsEmpty()) { 7037 return allTypes; 7038 } 7039 7040 struct SelectionRangeStartCmp { 7041 bool Equals(const SelectionRange& a, const SelectionRange& b) const { 7042 return a.mRange.start == b.mRange.start; 7043 } 7044 bool LessThan(const SelectionRange& a, const SelectionRange& b) const { 7045 return a.mRange.start < b.mRange.start; 7046 } 7047 }; 7048 selectionRanges.Sort(SelectionRangeStartCmp()); 7049 7050 CombineSelectionRanges(selectionRanges, aResult); 7051 7052 return allTypes; 7053 } 7054 7055 // Paints selection backgrounds and text in the correct colors. Also computes 7056 // aAllSelectionTypeMask, the union of all selection types that are applying to 7057 // this text. 7058 bool nsTextFrame::PaintTextWithSelectionColors( 7059 const PaintTextSelectionParams& aParams, 7060 const UniquePtr<SelectionDetails>& aDetails, 7061 SelectionTypeMask* aAllSelectionTypeMask, const ClipEdges& aClipEdges) { 7062 bool anyBackgrounds = false; 7063 AutoTArray<PriorityOrderedSelectionsForRange, 8> selectionRanges; 7064 7065 *aAllSelectionTypeMask = 7066 ResolveSelections(aParams, aDetails.get(), selectionRanges, 7067 SelectionType::eNone, &anyBackgrounds); 7068 bool vertical = mTextRun->IsVertical(); 7069 const gfxFloat startIOffset = 7070 vertical ? aParams.textBaselinePt.y - aParams.framePt.y 7071 : aParams.textBaselinePt.x - aParams.framePt.x; 7072 gfxFloat iOffset, hyphenWidth; 7073 Range range; // in transformed string 7074 7075 const gfxTextRun::Range& contentRange = aParams.contentRange; 7076 auto* textDrawer = aParams.context->GetTextDrawer(); 7077 7078 if (anyBackgrounds && !aParams.IsGenerateTextMask()) { 7079 int32_t appUnitsPerDevPixel = 7080 aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel(); 7081 SelectionRangeIterator iterator(selectionRanges, contentRange, 7082 *aParams.provider, mTextRun, startIOffset); 7083 AutoTArray<SelectionType, 1> selectionTypes; 7084 AutoTArray<RefPtr<nsAtom>, 1> highlightNames; 7085 AutoTArray<TextRangeStyle, 1> rangeStyles; 7086 while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth, 7087 selectionTypes, highlightNames, 7088 rangeStyles)) { 7089 nscolor foreground(0), background(0); 7090 gfxFloat advance = 7091 hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider); 7092 nsRect bgRect; 7093 gfxFloat offs = iOffset - (mTextRun->IsInlineReversed() ? advance : 0); 7094 if (vertical) { 7095 bgRect = nsRect(nscoord(aParams.framePt.x), 7096 nscoord(aParams.framePt.y + offs), GetSize().width, 7097 nscoord(advance)); 7098 } else { 7099 // For text-combine-upright, we may have collapsed the frame height, 7100 // so we instead use 1em to ensure the selection is visible. 7101 nscoord height = Style()->IsTextCombined() 7102 ? aParams.provider->GetFontMetrics()->EmHeight() 7103 : GetSize().height; 7104 bgRect = nsRect(nscoord(aParams.framePt.x + offs), 7105 nscoord(aParams.framePt.y), nscoord(advance), height); 7106 } 7107 7108 LayoutDeviceRect selectionRect = 7109 LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel); 7110 // The elements in `selectionTypes` are ordered ascending by their 7111 // priority. To account for non-opaque overlapping selections, all 7112 // selection backgrounds are painted. 7113 for (size_t index = 0; index < selectionTypes.Length(); ++index) { 7114 GetSelectionTextColors(selectionTypes[index], highlightNames[index], 7115 *aParams.textPaintStyle, rangeStyles[index], 7116 &foreground, &background); 7117 7118 // Draw background color 7119 if (NS_GET_A(background) > 0) { 7120 if (textDrawer) { 7121 textDrawer->AppendSelectionRect(selectionRect, 7122 ToDeviceColor(background)); 7123 } else { 7124 PaintSelectionBackground(*aParams.context->GetDrawTarget(), 7125 background, aParams.dirtyRect, 7126 selectionRect, aParams.callbacks); 7127 } 7128 } 7129 } 7130 iterator.UpdateWithAdvance(advance); 7131 } 7132 } 7133 7134 gfxFloat advance; 7135 DrawTextParams params(aParams.context, PresContext()->FontPaletteCache()); 7136 params.dirtyRect = aParams.dirtyRect; 7137 params.framePt = aParams.framePt; 7138 params.provider = aParams.provider; 7139 params.textStyle = aParams.textPaintStyle; 7140 params.clipEdges = &aClipEdges; 7141 params.advanceWidth = &advance; 7142 params.callbacks = aParams.callbacks; 7143 params.glyphRange = aParams.glyphRange; 7144 params.fontPalette = StyleFont()->GetFontPaletteAtom(); 7145 params.hasTextShadow = !StyleText()->mTextShadow.IsEmpty(); 7146 7147 PaintShadowParams shadowParams(aParams); 7148 shadowParams.provider = aParams.provider; 7149 shadowParams.callbacks = aParams.callbacks; 7150 shadowParams.clipEdges = &aClipEdges; 7151 7152 // Draw text 7153 const nsStyleText* textStyle = StyleText(); 7154 SelectionRangeIterator iterator(selectionRanges, contentRange, 7155 *aParams.provider, mTextRun, startIOffset); 7156 AutoTArray<SelectionType, 1> selectionTypes; 7157 AutoTArray<RefPtr<nsAtom>, 1> highlightNames; 7158 AutoTArray<TextRangeStyle, 1> rangeStyles; 7159 while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth, selectionTypes, 7160 highlightNames, rangeStyles)) { 7161 nscolor foreground(0), background(0); 7162 if (aParams.IsGenerateTextMask()) { 7163 foreground = NS_RGBA(0, 0, 0, 255); 7164 } else { 7165 nscolor tmpForeground(0); 7166 bool colorHasBeenSet = false; 7167 for (size_t index = 0; index < selectionTypes.Length(); ++index) { 7168 if (selectionTypes[index] == SelectionType::eHighlight) { 7169 if (aParams.textPaintStyle->GetCustomHighlightTextColor( 7170 highlightNames[index], &tmpForeground)) { 7171 foreground = tmpForeground; 7172 colorHasBeenSet = true; 7173 } 7174 7175 } else { 7176 GetSelectionTextColors(selectionTypes[index], highlightNames[index], 7177 *aParams.textPaintStyle, rangeStyles[index], 7178 &foreground, &background); 7179 colorHasBeenSet = true; 7180 } 7181 } 7182 if (!colorHasBeenSet) { 7183 foreground = tmpForeground; 7184 } 7185 } 7186 7187 gfx::Point textBaselinePt = 7188 vertical 7189 ? gfx::Point(aParams.textBaselinePt.x, aParams.framePt.y + iOffset) 7190 : gfx::Point(aParams.framePt.x + iOffset, aParams.textBaselinePt.y); 7191 7192 // Determine what shadow, if any, to draw - either from textStyle 7193 // or from the ::-moz-selection pseudo-class if specified there 7194 Span<const StyleSimpleShadow> shadows = textStyle->mTextShadow.AsSpan(); 7195 for (auto selectionType : selectionTypes) { 7196 GetSelectionTextShadow(selectionType, *aParams.textPaintStyle, &shadows); 7197 } 7198 if (!shadows.IsEmpty()) { 7199 nscoord startEdge = iOffset; 7200 if (mTextRun->IsInlineReversed()) { 7201 startEdge -= 7202 hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider); 7203 } 7204 shadowParams.range = range; 7205 shadowParams.textBaselinePt = textBaselinePt; 7206 shadowParams.foregroundColor = foreground; 7207 shadowParams.leftSideOffset = startEdge; 7208 PaintShadows(shadows, shadowParams); 7209 } 7210 7211 // Draw text segment 7212 params.textColor = foreground; 7213 params.textStrokeColor = aParams.textPaintStyle->GetWebkitTextStrokeColor(); 7214 params.textStrokeWidth = aParams.textPaintStyle->GetWebkitTextStrokeWidth(); 7215 params.drawSoftHyphen = hyphenWidth > 0; 7216 DrawText(range, textBaselinePt, params); 7217 advance += hyphenWidth; 7218 iterator.UpdateWithAdvance(advance); 7219 } 7220 return true; 7221 } 7222 7223 void nsTextFrame::PaintTextSelectionDecorations( 7224 const PaintTextSelectionParams& aParams, 7225 const UniquePtr<SelectionDetails>& aDetails, SelectionType aSelectionType) { 7226 // Hide text decorations if we're currently hiding @font-face fallback text 7227 if (aParams.provider->GetFontGroup()->ShouldSkipDrawing()) { 7228 return; 7229 } 7230 7231 AutoTArray<PriorityOrderedSelectionsForRange, 8> selectionRanges; 7232 ResolveSelections(aParams, aDetails.get(), selectionRanges, aSelectionType); 7233 7234 RefPtr<gfxFont> firstFont = 7235 aParams.provider->GetFontGroup()->GetFirstValidFont(); 7236 bool verticalRun = mTextRun->IsVertical(); 7237 bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline(); 7238 bool rightUnderline = useVerticalMetrics && IsUnderlineRight(*Style()); 7239 const auto kDecoration = rightUnderline ? StyleTextDecorationLine::OVERLINE 7240 : StyleTextDecorationLine::UNDERLINE; 7241 gfxFont::Metrics decorationMetrics( 7242 firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical 7243 : nsFontMetrics::eHorizontal)); 7244 decorationMetrics.underlineOffset = 7245 aParams.provider->GetFontGroup()->GetUnderlineOffset(); 7246 7247 const gfxTextRun::Range& contentRange = aParams.contentRange; 7248 gfxFloat startIOffset = verticalRun 7249 ? aParams.textBaselinePt.y - aParams.framePt.y 7250 : aParams.textBaselinePt.x - aParams.framePt.x; 7251 SelectionRangeIterator iterator(selectionRanges, contentRange, 7252 *aParams.provider, mTextRun, startIOffset); 7253 gfxFloat iOffset, hyphenWidth; 7254 Range range; 7255 int32_t app = aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel(); 7256 // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint? 7257 Point pt; 7258 if (verticalRun) { 7259 pt.x = (aParams.textBaselinePt.x - mAscent) / app; 7260 } else { 7261 pt.y = (aParams.textBaselinePt.y - mAscent) / app; 7262 } 7263 AutoTArray<SelectionType, 1> nextSelectionTypes; 7264 AutoTArray<RefPtr<nsAtom>, 1> highlightNames; 7265 AutoTArray<TextRangeStyle, 1> selectedStyles; 7266 7267 while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth, 7268 nextSelectionTypes, highlightNames, 7269 selectedStyles)) { 7270 gfxFloat advance = 7271 hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider); 7272 for (size_t index = 0; index < nextSelectionTypes.Length(); ++index) { 7273 if (nextSelectionTypes[index] == aSelectionType) { 7274 if (verticalRun) { 7275 pt.y = (aParams.framePt.y + iOffset - 7276 (mTextRun->IsInlineReversed() ? advance : 0)) / 7277 app; 7278 } else { 7279 pt.x = (aParams.framePt.x + iOffset - 7280 (mTextRun->IsInlineReversed() ? advance : 0)) / 7281 app; 7282 } 7283 gfxFloat width = Abs(advance) / app; 7284 gfxFloat xInFrame = pt.x - (aParams.framePt.x / app); 7285 DrawSelectionDecorations( 7286 aParams.context, aParams.dirtyRect, aSelectionType, 7287 highlightNames[index], *aParams.textPaintStyle, 7288 selectedStyles[index], pt, xInFrame, width, mAscent / app, 7289 decorationMetrics, aParams.callbacks, verticalRun, kDecoration, 7290 aParams.glyphRange, aParams.provider); 7291 } 7292 } 7293 iterator.UpdateWithAdvance(advance); 7294 } 7295 } 7296 7297 bool nsTextFrame::PaintTextWithSelection( 7298 const PaintTextSelectionParams& aParams, const ClipEdges& aClipEdges) { 7299 NS_ASSERTION(GetContent()->IsMaybeSelected(), "wrong paint path"); 7300 7301 UniquePtr<SelectionDetails> details = GetSelectionDetails(); 7302 if (!details) { 7303 return false; 7304 } 7305 7306 SelectionTypeMask allSelectionTypeMask; 7307 if (!PaintTextWithSelectionColors(aParams, details, &allSelectionTypeMask, 7308 aClipEdges)) { 7309 return false; 7310 } 7311 // Iterate through just the selection rawSelectionTypes that paint decorations 7312 // and paint decorations for any that actually occur in this frame. Paint 7313 // higher-numbered selection rawSelectionTypes below lower-numered ones on the 7314 // general principal that lower-numbered selections are higher priority. 7315 allSelectionTypeMask &= kSelectionTypesWithDecorations; 7316 MOZ_ASSERT(kPresentSelectionTypes[0] == SelectionType::eNormal, 7317 "The following for loop assumes that the first item of " 7318 "kPresentSelectionTypes is SelectionType::eNormal"); 7319 7320 Span presentTypes(kPresentSelectionTypes); 7321 for (SelectionType selectionType : Reversed(presentTypes)) { 7322 if (ToSelectionTypeMask(selectionType) & allSelectionTypeMask) { 7323 // There is some selection of this selectionType. Try to paint its 7324 // decorations (there might not be any for this type but that's OK, 7325 // PaintTextSelectionDecorations will exit early). 7326 PaintTextSelectionDecorations(aParams, details, selectionType); 7327 } 7328 } 7329 7330 return true; 7331 } 7332 7333 void nsTextFrame::DrawEmphasisMarks(gfxContext* aContext, WritingMode aWM, 7334 const gfx::Point& aTextBaselinePt, 7335 const gfx::Point& aFramePt, Range aRange, 7336 const nscolor* aDecorationOverrideColor, 7337 PropertyProvider* aProvider) { 7338 const EmphasisMarkInfo* info = GetProperty(EmphasisMarkProperty()); 7339 if (!info) { 7340 return; 7341 } 7342 7343 bool isTextCombined = Style()->IsTextCombined(); 7344 if (isTextCombined && !aWM.IsVertical()) { 7345 // XXX This only happens when the parent is display:contents with an 7346 // orthogonal writing mode. This should be rare, and don't have use 7347 // cases, so we don't care. It is non-trivial to implement a sane 7348 // behavior for that case: if you treat the text as not combined, 7349 // the marks would spread wider than the text (which is rendered as 7350 // combined); if you try to draw a single mark, selecting part of 7351 // the text could dynamically create multiple new marks. 7352 NS_WARNING("Give up on combined text with horizontal wm"); 7353 return; 7354 } 7355 nscolor color = 7356 aDecorationOverrideColor 7357 ? *aDecorationOverrideColor 7358 : nsLayoutUtils::GetTextColor(this, &nsStyleText::mTextEmphasisColor); 7359 aContext->SetColor(sRGBColor::FromABGR(color)); 7360 gfx::Point pt; 7361 if (!isTextCombined) { 7362 pt = aTextBaselinePt; 7363 } else { 7364 MOZ_ASSERT(aWM.IsVertical()); 7365 pt = aFramePt; 7366 if (aWM.IsVerticalRL()) { 7367 pt.x += GetSize().width - GetLogicalBaseline(aWM); 7368 } else { 7369 pt.x += GetLogicalBaseline(aWM); 7370 } 7371 } 7372 if (!aWM.IsVertical()) { 7373 pt.y += info->baselineOffset; 7374 } else { 7375 if (aWM.IsVerticalRL()) { 7376 pt.x -= info->baselineOffset; 7377 } else { 7378 pt.x += info->baselineOffset; 7379 } 7380 } 7381 if (!isTextCombined) { 7382 mTextRun->DrawEmphasisMarks(aContext, info->textRun.get(), info->advance, 7383 pt, aRange, aProvider, 7384 PresContext()->FontPaletteCache()); 7385 } else { 7386 pt.y += (GetSize().height - info->advance) / 2; 7387 gfxTextRun::DrawParams params(aContext, PresContext()->FontPaletteCache()); 7388 info->textRun->Draw(Range(info->textRun.get()), pt, params); 7389 } 7390 } 7391 7392 nscolor nsTextFrame::GetCaretColorAt(int32_t aOffset) { 7393 MOZ_ASSERT(aOffset >= 0, "aOffset must be positive"); 7394 7395 nscolor result = nsIFrame::GetCaretColorAt(aOffset); 7396 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 7397 PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics); 7398 int32_t contentOffset = provider.GetStart().GetOriginalOffset(); 7399 int32_t contentLength = provider.GetOriginalLength(); 7400 MOZ_ASSERT( 7401 aOffset >= contentOffset && aOffset <= contentOffset + contentLength, 7402 "aOffset must be in the frame's range"); 7403 7404 int32_t offsetInFrame = aOffset - contentOffset; 7405 if (offsetInFrame < 0 || offsetInFrame >= contentLength) { 7406 return result; 7407 } 7408 7409 bool isSolidTextColor = true; 7410 if (IsInSVGTextSubtree()) { 7411 const nsStyleSVG* style = StyleSVG(); 7412 if (!style->mFill.kind.IsNone() && !style->mFill.kind.IsColor()) { 7413 isSolidTextColor = false; 7414 } 7415 } 7416 7417 nsTextPaintStyle textPaintStyle(this); 7418 textPaintStyle.SetResolveColors(isSolidTextColor); 7419 UniquePtr<SelectionDetails> details = GetSelectionDetails(); 7420 SelectionType selectionType = SelectionType::eNone; 7421 for (SelectionDetails* sdptr = details.get(); sdptr; 7422 sdptr = sdptr->mNext.get()) { 7423 int32_t start = std::max(0, sdptr->mStart - contentOffset); 7424 int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset); 7425 if (start <= offsetInFrame && offsetInFrame < end && 7426 (selectionType == SelectionType::eNone || 7427 sdptr->mSelectionType < selectionType)) { 7428 nscolor foreground, background; 7429 if (GetSelectionTextColors(sdptr->mSelectionType, 7430 sdptr->mHighlightData.mHighlightName, 7431 textPaintStyle, sdptr->mTextRangeStyle, 7432 &foreground, &background)) { 7433 if (!isSolidTextColor && NS_IS_SELECTION_SPECIAL_COLOR(foreground)) { 7434 result = NS_RGBA(0, 0, 0, 255); 7435 } else { 7436 result = foreground; 7437 } 7438 selectionType = sdptr->mSelectionType; 7439 } 7440 } 7441 } 7442 7443 return result; 7444 } 7445 7446 static gfxTextRun::Range ComputeTransformedRange( 7447 nsTextFrame::PropertyProvider& aProvider) { 7448 gfxSkipCharsIterator iter(aProvider.GetStart()); 7449 uint32_t start = iter.GetSkippedOffset(); 7450 iter.AdvanceOriginal(aProvider.GetOriginalLength()); 7451 return gfxTextRun::Range(start, iter.GetSkippedOffset()); 7452 } 7453 7454 bool nsTextFrame::MeasureCharClippedText(nscoord aVisIStartEdge, 7455 nscoord aVisIEndEdge, 7456 nscoord* aSnappedStartEdge, 7457 nscoord* aSnappedEndEdge) { 7458 // We need a *reference* rendering context (not one that might have a 7459 // transform), so we don't have a rendering context argument. 7460 // XXX get the block and line passed to us somehow! This is slow! 7461 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 7462 if (!mTextRun) { 7463 return false; 7464 } 7465 7466 PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics); 7467 // Trim trailing whitespace 7468 provider.InitializeForDisplay(true); 7469 7470 Range range = ComputeTransformedRange(provider); 7471 uint32_t startOffset = range.start; 7472 uint32_t maxLength = range.Length(); 7473 return MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge, 7474 &startOffset, &maxLength, aSnappedStartEdge, 7475 aSnappedEndEdge); 7476 } 7477 7478 static uint32_t GetClusterLength(const gfxTextRun* aTextRun, 7479 uint32_t aStartOffset, uint32_t aMaxLength) { 7480 uint32_t clusterLength = 0; 7481 while (++clusterLength < aMaxLength) { 7482 if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) { 7483 return clusterLength; 7484 } 7485 } 7486 return aMaxLength; 7487 } 7488 7489 bool nsTextFrame::MeasureCharClippedText( 7490 PropertyProvider& aProvider, nscoord aVisIStartEdge, nscoord aVisIEndEdge, 7491 uint32_t* aStartOffset, uint32_t* aMaxLength, nscoord* aSnappedStartEdge, 7492 nscoord* aSnappedEndEdge) { 7493 *aSnappedStartEdge = 0; 7494 *aSnappedEndEdge = 0; 7495 if (aVisIStartEdge <= 0 && aVisIEndEdge <= 0) { 7496 return true; 7497 } 7498 7499 uint32_t offset = *aStartOffset; 7500 uint32_t maxLength = *aMaxLength; 7501 const nscoord frameISize = ISize(); 7502 const bool rtl = mTextRun->IsRightToLeft(); 7503 gfxFloat advanceWidth = 0; 7504 const nscoord startEdge = rtl ? aVisIEndEdge : aVisIStartEdge; 7505 if (startEdge > 0) { 7506 const gfxFloat maxAdvance = gfxFloat(startEdge); 7507 while (maxLength > 0) { 7508 uint32_t clusterLength = GetClusterLength(mTextRun, offset, maxLength); 7509 advanceWidth += mTextRun->GetAdvanceWidth( 7510 Range(offset, offset + clusterLength), &aProvider); 7511 maxLength -= clusterLength; 7512 offset += clusterLength; 7513 if (advanceWidth >= maxAdvance) { 7514 break; 7515 } 7516 } 7517 nscoord* snappedStartEdge = rtl ? aSnappedEndEdge : aSnappedStartEdge; 7518 *snappedStartEdge = NSToCoordFloor(advanceWidth); 7519 *aStartOffset = offset; 7520 } 7521 7522 const nscoord endEdge = rtl ? aVisIStartEdge : aVisIEndEdge; 7523 if (endEdge > 0) { 7524 const gfxFloat maxAdvance = gfxFloat(frameISize - endEdge); 7525 while (maxLength > 0) { 7526 uint32_t clusterLength = GetClusterLength(mTextRun, offset, maxLength); 7527 gfxFloat nextAdvance = 7528 advanceWidth + mTextRun->GetAdvanceWidth( 7529 Range(offset, offset + clusterLength), &aProvider); 7530 if (nextAdvance > maxAdvance) { 7531 break; 7532 } 7533 // This cluster fits, include it. 7534 advanceWidth = nextAdvance; 7535 maxLength -= clusterLength; 7536 offset += clusterLength; 7537 } 7538 maxLength = offset - *aStartOffset; 7539 nscoord* snappedEndEdge = rtl ? aSnappedStartEdge : aSnappedEndEdge; 7540 *snappedEndEdge = NSToCoordFloor(gfxFloat(frameISize) - advanceWidth); 7541 } 7542 *aMaxLength = maxLength; 7543 return maxLength != 0; 7544 } 7545 7546 void nsTextFrame::PaintShadows(Span<const StyleSimpleShadow> aShadows, 7547 const PaintShadowParams& aParams) { 7548 if (aShadows.IsEmpty()) { 7549 return; 7550 } 7551 7552 gfxTextRun::Metrics shadowMetrics = mTextRun->MeasureText( 7553 aParams.range, gfxFont::LOOSE_INK_EXTENTS, nullptr, aParams.provider); 7554 if (GetWritingMode().IsLineInverted()) { 7555 std::swap(shadowMetrics.mAscent, shadowMetrics.mDescent); 7556 shadowMetrics.mBoundingBox.y = -shadowMetrics.mBoundingBox.YMost(); 7557 } 7558 if (HasAnyStateBits(TEXT_HYPHEN_BREAK)) { 7559 AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &shadowMetrics, 7560 gfxFont::LOOSE_INK_EXTENTS, 7561 aParams.context->GetDrawTarget()); 7562 } 7563 // Add bounds of text decorations 7564 gfxRect decorationRect(0, -shadowMetrics.mAscent, shadowMetrics.mAdvanceWidth, 7565 shadowMetrics.mAscent + shadowMetrics.mDescent); 7566 shadowMetrics.mBoundingBox.UnionRect(shadowMetrics.mBoundingBox, 7567 decorationRect); 7568 7569 // If the textrun uses any color or SVG fonts, we need to force use of a mask 7570 // for shadow rendering even if blur radius is zero. 7571 uint32_t blurFlags = 0; 7572 uint32_t numGlyphRuns; 7573 const gfxTextRun::GlyphRun* run = mTextRun->GetGlyphRuns(&numGlyphRuns); 7574 while (numGlyphRuns-- > 0) { 7575 if (run->mFont->AlwaysNeedsMaskForShadow()) { 7576 blurFlags |= nsContextBoxBlur::FORCE_MASK; 7577 break; 7578 } 7579 run++; 7580 } 7581 7582 if (mTextRun->IsVertical()) { 7583 std::swap(shadowMetrics.mBoundingBox.x, shadowMetrics.mBoundingBox.y); 7584 std::swap(shadowMetrics.mBoundingBox.width, 7585 shadowMetrics.mBoundingBox.height); 7586 } 7587 7588 for (const auto& shadow : Reversed(aShadows)) { 7589 PaintOneShadow(aParams, shadow, shadowMetrics.mBoundingBox, blurFlags); 7590 } 7591 } 7592 7593 void nsTextFrame::PaintText(const PaintTextParams& aParams, 7594 const nscoord aVisIStartEdge, 7595 const nscoord aVisIEndEdge, 7596 const nsPoint& aToReferenceFrame, 7597 const bool aIsSelected, 7598 float aOpacity /* = 1.0f */) { 7599 #ifdef DEBUG 7600 if (IsInSVGTextSubtree()) { 7601 auto* container = 7602 nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::SVGText); 7603 MOZ_ASSERT(container); 7604 MOZ_ASSERT(!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || 7605 !aParams.IsPaintText(), 7606 "Expecting IsPaintText to be false for a clipPath"); 7607 } 7608 #endif 7609 7610 // Don't pass in the rendering context here, because we need a 7611 // *reference* context and rendering context might have some transform 7612 // in it 7613 // XXX get the block and line passed to us somehow! This is slow! 7614 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 7615 if (!mTextRun) { 7616 return; 7617 } 7618 7619 PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics); 7620 7621 // Trim trailing whitespace, unless we're painting a selection highlight, 7622 // which should include trailing spaces if present (bug 1146754). 7623 provider.InitializeForDisplay(!aIsSelected); 7624 7625 const bool reversed = mTextRun->IsInlineReversed(); 7626 const bool verticalRun = mTextRun->IsVertical(); 7627 WritingMode wm = GetWritingMode(); 7628 const float frameWidth = GetSize().width; 7629 const float frameHeight = GetSize().height; 7630 gfx::Point textBaselinePt; 7631 if (verticalRun) { 7632 if (wm.IsVerticalLR()) { 7633 textBaselinePt.x = nsLayoutUtils::GetMaybeSnappedBaselineX( 7634 this, aParams.context, nscoord(aParams.framePt.x), mAscent); 7635 } else { 7636 textBaselinePt.x = nsLayoutUtils::GetMaybeSnappedBaselineX( 7637 this, aParams.context, nscoord(aParams.framePt.x) + frameWidth, 7638 -mAscent); 7639 } 7640 textBaselinePt.y = reversed ? aParams.framePt.y.value + frameHeight 7641 : aParams.framePt.y.value; 7642 } else { 7643 textBaselinePt = 7644 gfx::Point(reversed ? aParams.framePt.x.value + frameWidth 7645 : aParams.framePt.x.value, 7646 nsLayoutUtils::GetMaybeSnappedBaselineY( 7647 this, aParams.context, aParams.framePt.y, mAscent)); 7648 } 7649 Range range = ComputeTransformedRange(provider); 7650 uint32_t startOffset = range.start; 7651 uint32_t maxLength = range.Length(); 7652 nscoord snappedStartEdge, snappedEndEdge; 7653 if (!MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge, 7654 &startOffset, &maxLength, &snappedStartEdge, 7655 &snappedEndEdge)) { 7656 return; 7657 } 7658 if (verticalRun) { 7659 textBaselinePt.y += reversed ? -snappedEndEdge : snappedStartEdge; 7660 } else { 7661 textBaselinePt.x += reversed ? -snappedEndEdge : snappedStartEdge; 7662 } 7663 const ClipEdges clipEdges(this, aToReferenceFrame, snappedStartEdge, 7664 snappedEndEdge); 7665 nsTextPaintStyle textPaintStyle(this); 7666 textPaintStyle.SetResolveColors(!aParams.callbacks); 7667 7668 // Fork off to the (slower) paint-with-selection path if necessary. 7669 if (aIsSelected) { 7670 MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!"); 7671 gfxSkipCharsIterator tmp(provider.GetStart()); 7672 Range contentRange( 7673 uint32_t(tmp.ConvertSkippedToOriginal(startOffset)), 7674 uint32_t(tmp.ConvertSkippedToOriginal(startOffset + maxLength))); 7675 PaintTextSelectionParams params(aParams); 7676 params.textBaselinePt = textBaselinePt; 7677 params.provider = &provider; 7678 params.contentRange = contentRange; 7679 params.textPaintStyle = &textPaintStyle; 7680 params.glyphRange = range; 7681 if (PaintTextWithSelection(params, clipEdges)) { 7682 return; 7683 } 7684 } 7685 7686 nscolor foregroundColor = aParams.IsGenerateTextMask() 7687 ? NS_RGBA(0, 0, 0, 255) 7688 : textPaintStyle.GetTextColor(); 7689 if (aOpacity != 1.0f) { 7690 gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(foregroundColor); 7691 gfxColor.a *= aOpacity; 7692 foregroundColor = gfxColor.ToABGR(); 7693 } 7694 7695 nscolor textStrokeColor = aParams.IsGenerateTextMask() 7696 ? NS_RGBA(0, 0, 0, 255) 7697 : textPaintStyle.GetWebkitTextStrokeColor(); 7698 if (aOpacity != 1.0f) { 7699 gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(textStrokeColor); 7700 gfxColor.a *= aOpacity; 7701 textStrokeColor = gfxColor.ToABGR(); 7702 } 7703 7704 range = Range(startOffset, startOffset + maxLength); 7705 if (aParams.IsPaintText()) { 7706 const nsStyleText* textStyle = StyleText(); 7707 PaintShadowParams shadowParams(aParams); 7708 shadowParams.range = range; 7709 shadowParams.textBaselinePt = textBaselinePt; 7710 shadowParams.leftSideOffset = snappedStartEdge; 7711 shadowParams.provider = &provider; 7712 shadowParams.callbacks = aParams.callbacks; 7713 shadowParams.foregroundColor = foregroundColor; 7714 shadowParams.clipEdges = &clipEdges; 7715 PaintShadows(textStyle->mTextShadow.AsSpan(), shadowParams); 7716 } 7717 7718 gfxFloat advanceWidth; 7719 DrawTextParams params(aParams.context, PresContext()->FontPaletteCache()); 7720 params.dirtyRect = aParams.dirtyRect; 7721 params.framePt = aParams.framePt; 7722 params.provider = &provider; 7723 params.advanceWidth = &advanceWidth; 7724 params.textStyle = &textPaintStyle; 7725 params.textColor = foregroundColor; 7726 params.textStrokeColor = textStrokeColor; 7727 params.textStrokeWidth = textPaintStyle.GetWebkitTextStrokeWidth(); 7728 params.clipEdges = &clipEdges; 7729 params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK); 7730 params.contextPaint = aParams.contextPaint; 7731 params.callbacks = aParams.callbacks; 7732 params.glyphRange = range; 7733 params.fontPalette = StyleFont()->GetFontPaletteAtom(); 7734 params.hasTextShadow = !StyleText()->mTextShadow.IsEmpty(); 7735 7736 DrawText(range, textBaselinePt, params); 7737 } 7738 7739 static void DrawTextRun(const gfxTextRun* aTextRun, 7740 const gfx::Point& aTextBaselinePt, 7741 gfxTextRun::Range aRange, 7742 const nsTextFrame::DrawTextRunParams& aParams, 7743 nsTextFrame* aFrame) { 7744 gfxTextRun::DrawParams params(aParams.context, aParams.paletteCache); 7745 params.provider = aParams.provider; 7746 params.advanceWidth = aParams.advanceWidth; 7747 params.contextPaint = aParams.contextPaint; 7748 params.fontPalette = aParams.fontPalette; 7749 params.callbacks = aParams.callbacks; 7750 params.hasTextShadow = aParams.hasTextShadow; 7751 if (aParams.callbacks) { 7752 aParams.callbacks->NotifyBeforeText(aParams.paintingShadows, 7753 aParams.textColor); 7754 params.drawMode = DrawMode::GLYPH_PATH; 7755 aTextRun->Draw(aRange, aTextBaselinePt, params); 7756 aParams.callbacks->NotifyAfterText(); 7757 } else { 7758 auto* textDrawer = aParams.context->GetTextDrawer(); 7759 if (NS_GET_A(aParams.textColor) != 0 || textDrawer || 7760 aParams.textStrokeWidth == 0.0f) { 7761 aParams.context->SetColor(sRGBColor::FromABGR(aParams.textColor)); 7762 } else { 7763 params.drawMode = DrawMode::GLYPH_STROKE; 7764 } 7765 7766 if ((NS_GET_A(aParams.textStrokeColor) != 0 || textDrawer) && 7767 aParams.textStrokeWidth != 0.0f) { 7768 if (textDrawer) { 7769 textDrawer->FoundUnsupportedFeature(); 7770 return; 7771 } 7772 params.drawMode |= DrawMode::GLYPH_STROKE; 7773 7774 // Check the paint-order property; if we find stroke before fill, 7775 // then change mode to GLYPH_STROKE_UNDERNEATH. 7776 uint32_t paintOrder = aFrame->StyleSVG()->mPaintOrder; 7777 while (paintOrder) { 7778 auto component = StylePaintOrder(paintOrder & kPaintOrderMask); 7779 switch (component) { 7780 case StylePaintOrder::Fill: 7781 // Just break the loop, no need to check further 7782 paintOrder = 0; 7783 break; 7784 case StylePaintOrder::Stroke: 7785 params.drawMode |= DrawMode::GLYPH_STROKE_UNDERNEATH; 7786 paintOrder = 0; 7787 break; 7788 default: 7789 MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?"); 7790 case StylePaintOrder::Markers: 7791 case StylePaintOrder::Normal: 7792 break; 7793 } 7794 paintOrder >>= kPaintOrderShift; 7795 } 7796 7797 // Use ROUND joins as they are less likely to produce ugly artifacts 7798 // when stroking glyphs with sharp angles (see bug 1546985). 7799 StrokeOptions strokeOpts(aParams.textStrokeWidth, JoinStyle::ROUND); 7800 params.textStrokeColor = aParams.textStrokeColor; 7801 params.strokeOpts = &strokeOpts; 7802 aTextRun->Draw(aRange, aTextBaselinePt, params); 7803 } else { 7804 aTextRun->Draw(aRange, aTextBaselinePt, params); 7805 } 7806 } 7807 } 7808 7809 void nsTextFrame::DrawTextRun(Range aRange, const gfx::Point& aTextBaselinePt, 7810 const DrawTextRunParams& aParams) { 7811 MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth"); 7812 7813 ::DrawTextRun(mTextRun, aTextBaselinePt, aRange, aParams, this); 7814 7815 if (aParams.drawSoftHyphen) { 7816 // Don't use ctx as the context, because we need a reference context here, 7817 // ctx may be transformed. 7818 DrawTextRunParams params = aParams; 7819 params.provider = nullptr; 7820 params.advanceWidth = nullptr; 7821 RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(this, nullptr); 7822 if (hyphenTextRun) { 7823 gfx::Point p(aTextBaselinePt); 7824 bool vertical = GetWritingMode().IsVertical(); 7825 // For right-to-left text runs, the soft-hyphen is positioned at the left 7826 // of the text. 7827 float shift = mTextRun->GetDirection() * (*aParams.advanceWidth); 7828 if (vertical) { 7829 p.y += shift; 7830 } else { 7831 p.x += shift; 7832 } 7833 ::DrawTextRun(hyphenTextRun.get(), p, Range(hyphenTextRun.get()), params, 7834 this); 7835 } 7836 } 7837 } 7838 7839 void nsTextFrame::DrawTextRunAndDecorations( 7840 Range aRange, const gfx::Point& aTextBaselinePt, 7841 const DrawTextParams& aParams, const TextDecorations& aDecorations) { 7842 const gfxFloat app = aParams.textStyle->PresContext()->AppUnitsPerDevPixel(); 7843 // Writing mode of parent frame is used because the text frame may 7844 // be orthogonal to its parent when text-combine-upright is used or 7845 // its parent has "display: contents", and in those cases, we want 7846 // to draw the decoration lines according to parents' direction 7847 // rather than ours. 7848 const WritingMode wm = GetParent()->GetWritingMode(); 7849 bool verticalDec = wm.IsVertical(); 7850 bool verticalRun = mTextRun->IsVertical(); 7851 // If the text run and the decoration is orthogonal, we choose the 7852 // metrics for decoration so that decoration line won't be broken. 7853 bool useVerticalMetrics = verticalDec != verticalRun 7854 ? verticalDec 7855 : verticalRun && mTextRun->UseCenterBaseline(); 7856 7857 // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint? 7858 nscoord x = NSToCoordRound(aParams.framePt.x); 7859 nscoord y = NSToCoordRound(aParams.framePt.y); 7860 7861 // 'measure' here is textrun-relative, so for a horizontal run it's the 7862 // width, while for a vertical run it's the height of the decoration 7863 const nsSize frameSize = GetSize(); 7864 nscoord measure = verticalDec ? frameSize.height : frameSize.width; 7865 7866 if (verticalDec) { 7867 aParams.clipEdges->Intersect(&y, &measure); 7868 } else { 7869 aParams.clipEdges->Intersect(&x, &measure); 7870 } 7871 7872 // decSize is a textrun-relative size, so its 'width' field is actually 7873 // the run-relative measure, and 'height' will be the line thickness 7874 gfxFloat ascent = gfxFloat(GetLogicalBaseline(wm)) / app; 7875 // The starting edge of the frame in block direction 7876 gfxFloat frameBStart = verticalDec ? aParams.framePt.x : aParams.framePt.y; 7877 7878 // In vertical-rl mode, block coordinates are measured from the 7879 // right, so we need to adjust here. 7880 if (wm.IsVerticalRL()) { 7881 frameBStart += frameSize.width; 7882 ascent = -ascent; 7883 } 7884 7885 nscoord inflationMinFontSize = nsLayoutUtils::InflationMinFontSizeFor(this); 7886 7887 PaintDecorationLineParams params; 7888 params.context = aParams.context; 7889 params.dirtyRect = aParams.dirtyRect; 7890 params.overrideColor = aParams.decorationOverrideColor; 7891 params.callbacks = aParams.callbacks; 7892 params.glyphRange = aParams.glyphRange; 7893 params.provider = aParams.provider; 7894 params.paintingShadows = aParams.paintingShadows; 7895 // pt is the physical point where the decoration is to be drawn, 7896 // relative to the frame; one of its coordinates will be updated below. 7897 params.pt = Point(x / app, y / app); 7898 Float& bCoord = verticalDec ? params.pt.x.value : params.pt.y.value; 7899 params.lineSize = Size(measure / app, 0); 7900 params.ascent = ascent; 7901 params.vertical = verticalDec; 7902 params.sidewaysLeft = mTextRun->IsSidewaysLeft(); 7903 7904 // The matrix of the context may have been altered for text-combine- 7905 // upright. However, we want to draw decoration lines unscaled, thus 7906 // we need to revert the scaling here. 7907 gfxContextMatrixAutoSaveRestore scaledRestorer; 7908 if (Style()->IsTextCombined()) { 7909 float scaleFactor = GetTextCombineScale(); 7910 if (scaleFactor != 1.0f) { 7911 scaledRestorer.SetContext(aParams.context); 7912 gfxMatrix unscaled = aParams.context->CurrentMatrixDouble(); 7913 gfxPoint pt(x / app, y / app); 7914 if (GetTextRun(nsTextFrame::eInflated)->IsRightToLeft()) { 7915 pt.x += gfxFloat(frameSize.width) / app; 7916 } 7917 unscaled.PreTranslate(pt) 7918 .PreScale(1.0f / scaleFactor, 1.0f) 7919 .PreTranslate(-pt); 7920 aParams.context->SetMatrixDouble(unscaled); 7921 } 7922 } 7923 7924 // We create a clip region in order to draw the decoration lines only in the 7925 // range of the text. Restricting the draw area prevents the decoration lines 7926 // to be drawn multiple times when a part of the text is selected. 7927 Maybe<gfxRect> clipRect; 7928 7929 // We skip clipping for the following cases: 7930 // - drawing the whole text 7931 // - having different orientation of the text and the writing-mode, such as 7932 // "text-combine-upright" (Bug 1408825) 7933 if (aRange.Length() != mTextRun->GetLength() && verticalDec == verticalRun) { 7934 // Get the inline-size according to the specified range. 7935 gfxFloat clipLength = mTextRun->GetAdvanceWidth(aRange, aParams.provider); 7936 nsRect visualRect = InkOverflowRect(); 7937 7938 const bool isInlineReversed = mTextRun->IsInlineReversed(); 7939 gfxFloat x, y, w, h; 7940 if (verticalDec) { 7941 x = aParams.framePt.x + visualRect.x; 7942 y = isInlineReversed ? aTextBaselinePt.y.value - clipLength 7943 : aTextBaselinePt.y.value; 7944 w = visualRect.width; 7945 h = clipLength; 7946 } else { 7947 x = isInlineReversed ? aTextBaselinePt.x.value - clipLength 7948 : aTextBaselinePt.x.value; 7949 y = aParams.framePt.y + visualRect.y; 7950 w = clipLength; 7951 h = visualRect.height; 7952 } 7953 clipRect.emplace(x, y, w, h); 7954 clipRect->Scale(1 / app); 7955 clipRect->Round(); 7956 } 7957 7958 typedef gfxFont::Metrics Metrics; 7959 auto paintDecorationLine = [&](const LineDecoration& dec, 7960 gfxFloat Metrics::* lineSize, 7961 StyleTextDecorationLine lineType) { 7962 if (dec.mStyle == StyleTextDecorationStyle::None) { 7963 return; 7964 } 7965 7966 float inflation = 7967 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); 7968 const Metrics metrics = GetFirstFontMetrics( 7969 GetFontGroupForFrame(dec.mFrame, inflation), useVerticalMetrics); 7970 if (!ComputeDecorationInset(this, aParams.textStyle->PresContext(), 7971 dec.mFrame, metrics, params)) { 7972 return; 7973 } 7974 bCoord = (frameBStart - dec.mBaselineOffset) / app; 7975 7976 params.color = dec.mColor; 7977 params.baselineOffset = dec.mBaselineOffset / app; 7978 params.defaultLineThickness = metrics.*lineSize; 7979 params.lineSize.height = ComputeDecorationLineThickness( 7980 dec.mTextDecorationThickness, params.defaultLineThickness, metrics, app, 7981 dec.mFrame); 7982 7983 bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style()); 7984 params.offset = ComputeDecorationLineOffset( 7985 lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset, metrics, 7986 app, dec.mFrame, wm.IsCentralBaseline(), swapUnderline); 7987 7988 params.style = dec.mStyle; 7989 params.allowInkSkipping = dec.mAllowInkSkipping; 7990 params.skipInk = StyleText()->mTextDecorationSkipInk; 7991 gfxClipAutoSaveRestore clipRestore(params.context); 7992 // If we have a negative inset value, then the decoration will extend 7993 // outside the edges of the text. 7994 // TODO alaskanemily: Ideally we would adjust the clipping rect, but as 7995 // an initial pass we just disable clipping in this case. 7996 if (clipRect && !params.HasNegativeInset()) { 7997 clipRestore.Clip(*clipRect); 7998 } 7999 PaintDecorationLine(params); 8000 }; 8001 8002 // Underlines 8003 params.decoration = StyleTextDecorationLine::UNDERLINE; 8004 for (const LineDecoration& dec : Reversed(aDecorations.mUnderlines)) { 8005 paintDecorationLine(dec, &Metrics::underlineSize, params.decoration); 8006 } 8007 8008 // Overlines 8009 params.decoration = StyleTextDecorationLine::OVERLINE; 8010 for (const LineDecoration& dec : Reversed(aDecorations.mOverlines)) { 8011 paintDecorationLine(dec, &Metrics::underlineSize, params.decoration); 8012 } 8013 8014 // Some glyphs and emphasis marks may extend outside the region, so we do 8015 // not set the clip region here to the clip rect. For an example, italic 8016 // glyphs. 8017 8018 { 8019 gfxContextMatrixAutoSaveRestore unscaledRestorer; 8020 if (scaledRestorer.HasMatrix()) { 8021 unscaledRestorer.SetContext(aParams.context); 8022 aParams.context->SetMatrix(scaledRestorer.Matrix()); 8023 } 8024 8025 // CSS 2.1 mandates that text be painted after over/underlines, 8026 // and *then* line-throughs 8027 DrawTextRun(aRange, aTextBaselinePt, aParams); 8028 } 8029 8030 // Emphasis marks 8031 DrawEmphasisMarks(aParams.context, wm, aTextBaselinePt, aParams.framePt, 8032 aRange, aParams.decorationOverrideColor, aParams.provider); 8033 8034 // Line-throughs 8035 params.decoration = StyleTextDecorationLine::LINE_THROUGH; 8036 for (const LineDecoration& dec : Reversed(aDecorations.mStrikes)) { 8037 paintDecorationLine(dec, &Metrics::strikeoutSize, params.decoration); 8038 } 8039 } 8040 8041 void nsTextFrame::DrawText(Range aRange, const gfx::Point& aTextBaselinePt, 8042 const DrawTextParams& aParams) { 8043 TextDecorations decorations; 8044 GetTextDecorations(aParams.textStyle->PresContext(), 8045 aParams.callbacks ? eUnresolvedColors : eResolvedColors, 8046 decorations); 8047 8048 // Hide text decorations if we're currently hiding @font-face fallback text 8049 const bool drawDecorations = 8050 !aParams.provider->GetFontGroup()->ShouldSkipDrawing() && 8051 (decorations.HasDecorationLines() || 8052 StyleText()->HasEffectiveTextEmphasis()); 8053 if (drawDecorations) { 8054 DrawTextRunAndDecorations(aRange, aTextBaselinePt, aParams, decorations); 8055 } else { 8056 DrawTextRun(aRange, aTextBaselinePt, aParams); 8057 } 8058 8059 if (auto* textDrawer = aParams.context->GetTextDrawer()) { 8060 textDrawer->TerminateShadows(); 8061 } 8062 } 8063 8064 NS_DECLARE_FRAME_PROPERTY_DELETABLE(WebRenderTextBounds, nsRect) 8065 8066 nsRect nsTextFrame::WebRenderBounds() { 8067 // WR text bounds is just our ink overflow rect but without shadows. So if we 8068 // have no shadows, just use the layout bounds. 8069 if (!StyleText()->HasTextShadow()) { 8070 return InkOverflowRect(); 8071 } 8072 nsRect* cachedBounds = GetProperty(WebRenderTextBounds()); 8073 if (!cachedBounds) { 8074 OverflowAreas overflowAreas; 8075 ComputeCustomOverflowInternal(overflowAreas, false); 8076 cachedBounds = new nsRect(overflowAreas.InkOverflow()); 8077 SetProperty(WebRenderTextBounds(), cachedBounds); 8078 } 8079 return *cachedBounds; 8080 } 8081 8082 int16_t nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags) { 8083 nsISelectionController* const selCon = GetSelectionController(); 8084 if (MOZ_UNLIKELY(!selCon)) { 8085 return nsISelectionController::SELECTION_OFF; 8086 } 8087 8088 selCon->GetSelectionFlags(aSelectionFlags); 8089 8090 int16_t selectionValue = nsISelectionController::SELECTION_OFF; 8091 selCon->GetDisplaySelection(&selectionValue); 8092 return selectionValue; 8093 } 8094 8095 bool nsTextFrame::IsEntirelyWhitespace() const { 8096 const auto& characterDataBuffer = mContent->AsText()->DataBuffer(); 8097 for (uint32_t index = 0; index < characterDataBuffer.GetLength(); ++index) { 8098 const char16_t ch = characterDataBuffer.CharAt(index); 8099 if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == 0xa0) { 8100 continue; 8101 } 8102 return false; 8103 } 8104 return true; 8105 } 8106 8107 /** 8108 * Compute the longest prefix of text whose width is <= aWidth. Return 8109 * the length of the prefix. Also returns the width of the prefix in aFitWidth. 8110 */ 8111 static uint32_t CountCharsFit(const gfxTextRun* aTextRun, 8112 gfxTextRun::Range aRange, gfxFloat aWidth, 8113 nsTextFrame::PropertyProvider* aProvider, 8114 gfxFloat* aFitWidth) { 8115 uint32_t last = 0; 8116 gfxFloat width = 0; 8117 for (uint32_t i = 1; i <= aRange.Length(); ++i) { 8118 if (i == aRange.Length() || aTextRun->IsClusterStart(aRange.start + i)) { 8119 gfxTextRun::Range range(aRange.start + last, aRange.start + i); 8120 gfxFloat nextWidth = width + aTextRun->GetAdvanceWidth(range, aProvider); 8121 if (nextWidth > aWidth) { 8122 break; 8123 } 8124 last = i; 8125 width = nextWidth; 8126 } 8127 } 8128 *aFitWidth = width; 8129 return last; 8130 } 8131 8132 nsIFrame::ContentOffsets nsTextFrame::CalcContentOffsetsFromFramePoint( 8133 const nsPoint& aPoint) { 8134 return GetCharacterOffsetAtFramePointInternal(aPoint, true); 8135 } 8136 8137 nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePoint( 8138 const nsPoint& aPoint) { 8139 return GetCharacterOffsetAtFramePointInternal(aPoint, false); 8140 } 8141 8142 nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePointInternal( 8143 const nsPoint& aPoint, bool aForInsertionPoint) { 8144 ContentOffsets offsets; 8145 8146 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 8147 if (!mTextRun) { 8148 return offsets; 8149 } 8150 8151 PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics); 8152 // Trim leading but not trailing whitespace if possible 8153 provider.InitializeForDisplay(false); 8154 gfxFloat width = 8155 mTextRun->IsVertical() 8156 ? (mTextRun->IsInlineReversed() ? mRect.height - aPoint.y : aPoint.y) 8157 : (mTextRun->IsInlineReversed() ? mRect.width - aPoint.x : aPoint.x); 8158 if (Style()->IsTextCombined()) { 8159 width /= GetTextCombineScale(); 8160 } 8161 gfxFloat fitWidth; 8162 Range skippedRange = ComputeTransformedRange(provider); 8163 8164 uint32_t charsFit = 8165 CountCharsFit(mTextRun, skippedRange, width, &provider, &fitWidth); 8166 8167 int32_t selectedOffset; 8168 if (charsFit < skippedRange.Length()) { 8169 // charsFit characters fitted, but no more could fit. See if we're 8170 // more than halfway through the cluster.. If we are, choose the next 8171 // cluster. 8172 gfxSkipCharsIterator extraCluster(provider.GetStart()); 8173 extraCluster.AdvanceSkipped(charsFit); 8174 8175 bool allowSplitLigature = true; // Allow selection of partial ligature... 8176 8177 // ...but don't let selection/insertion-point split two Regional Indicator 8178 // chars that are ligated in the textrun to form a single flag symbol. 8179 uint32_t offs = extraCluster.GetOriginalOffset(); 8180 const auto& characterDataBuffer = CharacterDataBuffer(); 8181 if (characterDataBuffer.IsHighSurrogateFollowedByLowSurrogateAt(offs) && 8182 gfxFontUtils::IsRegionalIndicator( 8183 characterDataBuffer.ScalarValueAt(offs))) { 8184 allowSplitLigature = false; 8185 if (extraCluster.GetSkippedOffset() > 1 && 8186 !mTextRun->IsLigatureGroupStart(extraCluster.GetSkippedOffset())) { 8187 // CountCharsFit() left us in the middle of the flag; back up over the 8188 // first character of the ligature, and adjust fitWidth accordingly. 8189 extraCluster.AdvanceSkipped(-2); // it's a surrogate pair: 2 code units 8190 fitWidth -= mTextRun->GetAdvanceWidth( 8191 Range(extraCluster.GetSkippedOffset(), 8192 extraCluster.GetSkippedOffset() + 2), 8193 &provider); 8194 } 8195 } 8196 8197 gfxSkipCharsIterator extraClusterLastChar(extraCluster); 8198 FindClusterEnd( 8199 mTextRun, 8200 provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(), 8201 &extraClusterLastChar, allowSplitLigature); 8202 PropertyProvider::Spacing spacing; 8203 Range extraClusterRange(extraCluster.GetSkippedOffset(), 8204 extraClusterLastChar.GetSkippedOffset() + 1); 8205 gfxFloat charWidth = 8206 mTextRun->GetAdvanceWidth(extraClusterRange, &provider, &spacing); 8207 charWidth -= spacing.mBefore + spacing.mAfter; 8208 selectedOffset = !aForInsertionPoint || 8209 width <= fitWidth + spacing.mBefore + charWidth / 2 8210 ? extraCluster.GetOriginalOffset() 8211 : extraClusterLastChar.GetOriginalOffset() + 1; 8212 } else { 8213 // All characters fitted, we're at (or beyond) the end of the text. 8214 // XXX This could be some pathological situation where negative spacing 8215 // caused characters to move backwards. We can't really handle that 8216 // in the current frame system because frames can't have negative 8217 // intrinsic widths. 8218 selectedOffset = 8219 provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(); 8220 // If we're at the end of a preformatted line which has a terminating 8221 // linefeed, we want to reduce the offset by one to make sure that the 8222 // selection is placed before the linefeed character. 8223 if (HasSignificantTerminalNewline()) { 8224 --selectedOffset; 8225 } 8226 } 8227 8228 offsets.content = GetContent(); 8229 offsets.offset = offsets.secondaryOffset = selectedOffset; 8230 offsets.associate = mContentOffset == offsets.offset 8231 ? CaretAssociationHint::After 8232 : CaretAssociationHint::Before; 8233 return offsets; 8234 } 8235 8236 bool nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext, 8237 nsRect& aRect) { 8238 if (aRect.IsEmpty()) { 8239 return false; 8240 } 8241 8242 nsRect givenRect = aRect; 8243 8244 gfxFontGroup* fontGroup = GetInflatedFontGroupForFrame(this); 8245 RefPtr<gfxFont> firstFont = fontGroup->GetFirstValidFont(); 8246 WritingMode wm = GetWritingMode(); 8247 bool verticalRun = wm.IsVertical(); 8248 bool useVerticalMetrics = verticalRun && !wm.IsSideways(); 8249 const gfxFont::Metrics& metrics = 8250 firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical 8251 : nsFontMetrics::eHorizontal); 8252 8253 nsCSSRendering::DecorationRectParams params; 8254 params.ascent = aPresContext->AppUnitsToGfxUnits(mAscent); 8255 8256 params.offset = fontGroup->GetUnderlineOffset(); 8257 8258 TextDecorations textDecs; 8259 GetTextDecorations(aPresContext, eResolvedColors, textDecs); 8260 8261 params.descentLimit = 8262 ComputeDescentLimitForSelectionUnderline(aPresContext, metrics); 8263 params.vertical = verticalRun; 8264 8265 if (verticalRun) { 8266 EnsureTextRun(nsTextFrame::eInflated); 8267 params.sidewaysLeft = mTextRun ? mTextRun->IsSidewaysLeft() : false; 8268 } else { 8269 params.sidewaysLeft = false; 8270 } 8271 8272 UniquePtr<SelectionDetails> details = GetSelectionDetails(); 8273 for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) { 8274 if (sd->mStart == sd->mEnd || 8275 sd->mSelectionType == SelectionType::eInvalid || 8276 !(ToSelectionTypeMask(sd->mSelectionType) & 8277 kSelectionTypesWithDecorations) || 8278 // URL strikeout does not use underline. 8279 sd->mSelectionType == SelectionType::eURLStrikeout) { 8280 continue; 8281 } 8282 float relativeSize = 1.f; 8283 RefPtr<ComputedStyle> style = Style(); 8284 8285 if (sd->mSelectionType == SelectionType::eNormal || 8286 sd->mSelectionType == SelectionType::eTargetText || 8287 sd->mSelectionType == SelectionType::eHighlight) { 8288 style = [&]() { 8289 if (sd->mSelectionType == SelectionType::eHighlight) { 8290 return ComputeHighlightSelectionStyle( 8291 sd->mHighlightData.mHighlightName); 8292 } 8293 if (sd->mSelectionType == SelectionType::eTargetText) { 8294 return ComputeTargetTextStyle(); 8295 } 8296 int16_t unusedFlags = 0; 8297 const int16_t selectionStatus = GetSelectionStatus(&unusedFlags); 8298 return ComputeSelectionStyle(selectionStatus); 8299 }(); 8300 if (!style || !style->HasTextDecorationLines()) { 8301 continue; 8302 } 8303 params.style = style->StyleTextReset()->mTextDecorationStyle; 8304 } else { 8305 auto index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType( 8306 sd->mSelectionType); 8307 if (sd->mSelectionType == SelectionType::eSpellCheck) { 8308 if (!nsTextPaintStyle::GetSelectionUnderline( 8309 this, index, nullptr, &relativeSize, ¶ms.style)) { 8310 continue; 8311 } 8312 } else { 8313 // IME selections 8314 TextRangeStyle& rangeStyle = sd->mTextRangeStyle; 8315 if (rangeStyle.IsDefined()) { 8316 if (!rangeStyle.IsLineStyleDefined() || 8317 rangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) { 8318 continue; 8319 } 8320 params.style = ToStyleLineStyle(rangeStyle); 8321 relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f; 8322 } else if (!nsTextPaintStyle::GetSelectionUnderline( 8323 this, index, nullptr, &relativeSize, ¶ms.style)) { 8324 continue; 8325 } 8326 } 8327 } 8328 nsRect decorationArea; 8329 8330 const auto& decThickness = 8331 style->StyleTextReset()->mTextDecorationThickness; 8332 params.lineSize.width = aPresContext->AppUnitsToGfxUnits(aRect.width); 8333 params.defaultLineThickness = ComputeSelectionUnderlineHeight( 8334 aPresContext, metrics, sd->mSelectionType); 8335 8336 params.lineSize.height = ComputeDecorationLineThickness( 8337 decThickness, params.defaultLineThickness, metrics, 8338 aPresContext->AppUnitsPerDevPixel(), this); 8339 8340 bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*style); 8341 const auto* styleText = style->StyleText(); 8342 params.offset = ComputeDecorationLineOffset( 8343 textDecs.HasUnderline() ? StyleTextDecorationLine::UNDERLINE 8344 : StyleTextDecorationLine::OVERLINE, 8345 styleText->mTextUnderlinePosition, styleText->mTextUnderlineOffset, 8346 metrics, aPresContext->AppUnitsPerDevPixel(), this, 8347 wm.IsCentralBaseline(), swapUnderline); 8348 8349 relativeSize = std::max(relativeSize, 1.0f); 8350 params.lineSize.height *= relativeSize; 8351 params.defaultLineThickness *= relativeSize; 8352 decorationArea = 8353 nsCSSRendering::GetTextDecorationRect(aPresContext, params); 8354 aRect.UnionRect(aRect, decorationArea); 8355 } 8356 8357 return !aRect.IsEmpty() && !givenRect.Contains(aRect); 8358 } 8359 8360 bool nsTextFrame::IsFrameSelected() const { 8361 NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(), 8362 "use the public IsSelected() instead"); 8363 if (mIsSelected == nsTextFrame::SelectionState::Unknown) { 8364 const bool isSelected = 8365 GetContent()->IsSelected(GetContentOffset(), GetContentEnd(), 8366 PresShell()->GetSelectionNodeCache()); 8367 mIsSelected = isSelected ? nsTextFrame::SelectionState::Selected 8368 : nsTextFrame::SelectionState::NotSelected; 8369 } else { 8370 #ifdef DEBUG 8371 // Assert that the selection caching works. 8372 const bool isReallySelected = 8373 GetContent()->IsSelected(GetContentOffset(), GetContentEnd()); 8374 MOZ_ASSERT((mIsSelected == nsTextFrame::SelectionState::Selected) == 8375 isReallySelected, 8376 "Should have called InvalidateSelectionState()"); 8377 #endif 8378 } 8379 8380 return mIsSelected == nsTextFrame::SelectionState::Selected; 8381 } 8382 8383 nsTextFrame* nsTextFrame::FindContinuationForOffset(int32_t aOffset) { 8384 // Use a continuations array to accelerate finding the first continuation 8385 // of interest, if possible. 8386 MOZ_ASSERT(!GetPrevContinuation(), "should be called on the primary frame"); 8387 auto* continuations = GetContinuations(); 8388 nsTextFrame* f = this; 8389 if (continuations) { 8390 size_t index; 8391 if (BinarySearchIf( 8392 *continuations, 0, continuations->Length(), 8393 [=](nsTextFrame* aFrame) -> int { 8394 return aOffset - aFrame->GetContentOffset(); 8395 }, 8396 &index)) { 8397 f = (*continuations)[index]; 8398 } else { 8399 f = (*continuations)[index ? index - 1 : 0]; 8400 } 8401 } 8402 8403 while (f && f->GetContentEnd() <= aOffset) { 8404 f = f->GetNextContinuation(); 8405 } 8406 8407 return f; 8408 } 8409 8410 void nsTextFrame::SelectionStateChanged(uint32_t aStart, uint32_t aEnd, 8411 bool aSelected, 8412 SelectionType aSelectionType) { 8413 NS_ASSERTION(!GetPrevContinuation(), 8414 "Should only be called for primary frame"); 8415 DEBUG_VERIFY_NOT_DIRTY(GetStateBits()); 8416 8417 InvalidateSelectionState(); 8418 8419 // Selection is collapsed, which can't affect text frame rendering 8420 if (aStart == aEnd) { 8421 return; 8422 } 8423 8424 nsTextFrame* f = FindContinuationForOffset(aStart); 8425 8426 nsPresContext* presContext = PresContext(); 8427 while (f && f->GetContentOffset() < int32_t(aEnd)) { 8428 // We may need to reflow to recompute the overflow area for 8429 // spellchecking or IME underline if their underline is thicker than 8430 // the normal decoration line. 8431 if (ToSelectionTypeMask(aSelectionType) & kSelectionTypesWithDecorations) { 8432 bool didHaveOverflowingSelection = 8433 f->HasAnyStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED); 8434 nsRect r(nsPoint(0, 0), GetSize()); 8435 if (didHaveOverflowingSelection || 8436 (aSelected && f->CombineSelectionUnderlineRect(presContext, r))) { 8437 presContext->PresShell()->FrameNeedsReflow( 8438 f, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); 8439 } 8440 } 8441 // Selection might change anything. Invalidate the overflow area. 8442 f->InvalidateFrame(); 8443 8444 f = f->GetNextContinuation(); 8445 } 8446 } 8447 8448 void nsTextFrame::UpdateIteratorFromOffset(const PropertyProvider& aProperties, 8449 int32_t& aInOffset, 8450 gfxSkipCharsIterator& aIter) { 8451 if (aInOffset < GetContentOffset()) { 8452 NS_WARNING("offset before this frame's content"); 8453 aInOffset = GetContentOffset(); 8454 } else if (aInOffset > GetContentEnd()) { 8455 NS_WARNING("offset after this frame's content"); 8456 aInOffset = GetContentEnd(); 8457 } 8458 8459 int32_t trimmedOffset = aProperties.GetStart().GetOriginalOffset(); 8460 int32_t trimmedEnd = trimmedOffset + aProperties.GetOriginalLength(); 8461 aInOffset = std::max(aInOffset, trimmedOffset); 8462 aInOffset = std::min(aInOffset, trimmedEnd); 8463 8464 aIter.SetOriginalOffset(aInOffset); 8465 8466 if (aInOffset < trimmedEnd && !aIter.IsOriginalCharSkipped() && 8467 !mTextRun->IsClusterStart(aIter.GetSkippedOffset())) { 8468 // Called for non-cluster boundary 8469 FindClusterStart(mTextRun, trimmedOffset, &aIter); 8470 } 8471 } 8472 8473 nsPoint nsTextFrame::GetPointFromIterator(const gfxSkipCharsIterator& aIter, 8474 PropertyProvider& aProperties) { 8475 Range range(aProperties.GetStart().GetSkippedOffset(), 8476 aIter.GetSkippedOffset()); 8477 gfxFloat advance = mTextRun->GetAdvanceWidth(range, &aProperties); 8478 nscoord iSize = NSToCoordCeilClamped(advance); 8479 nsPoint point; 8480 8481 if (mTextRun->IsVertical()) { 8482 point.x = 0; 8483 if (mTextRun->IsInlineReversed()) { 8484 point.y = mRect.height - iSize; 8485 } else { 8486 point.y = iSize; 8487 } 8488 } else { 8489 point.y = 0; 8490 if (Style()->IsTextCombined()) { 8491 iSize *= GetTextCombineScale(); 8492 } 8493 if (mTextRun->IsInlineReversed()) { 8494 point.x = mRect.width - iSize; 8495 } else { 8496 point.x = iSize; 8497 } 8498 } 8499 return point; 8500 } 8501 8502 nsresult nsTextFrame::GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) { 8503 if (!outPoint) { 8504 return NS_ERROR_NULL_POINTER; 8505 } 8506 8507 DEBUG_VERIFY_NOT_DIRTY(GetStateBits()); 8508 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 8509 return NS_ERROR_UNEXPECTED; 8510 } 8511 8512 if (GetContentLength() <= 0) { 8513 outPoint->x = 0; 8514 outPoint->y = 0; 8515 return NS_OK; 8516 } 8517 8518 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 8519 if (!mTextRun) { 8520 return NS_ERROR_FAILURE; 8521 } 8522 8523 PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics); 8524 // Don't trim trailing whitespace, we want the caret to appear in the right 8525 // place if it's positioned there 8526 properties.InitializeForDisplay(false); 8527 8528 UpdateIteratorFromOffset(properties, inOffset, iter); 8529 8530 *outPoint = GetPointFromIterator(iter, properties); 8531 8532 return NS_OK; 8533 } 8534 8535 nsresult nsTextFrame::GetCharacterRectsInRange(int32_t aInOffset, 8536 int32_t aLength, 8537 nsTArray<nsRect>& aRects) { 8538 DEBUG_VERIFY_NOT_DIRTY(GetStateBits()); 8539 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 8540 return NS_ERROR_UNEXPECTED; 8541 } 8542 8543 if (GetContentLength() <= 0) { 8544 return NS_OK; 8545 } 8546 8547 if (!mTextRun) { 8548 return NS_ERROR_FAILURE; 8549 } 8550 8551 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 8552 PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics); 8553 // Don't trim trailing whitespace, we want the caret to appear in the right 8554 // place if it's positioned there 8555 properties.InitializeForDisplay(false); 8556 8557 // Initialize iter; this will call FindClusterStart if necessary to align 8558 // iter to a cluster boundary. 8559 UpdateIteratorFromOffset(properties, aInOffset, iter); 8560 nsPoint point = GetPointFromIterator(iter, properties); 8561 8562 const int32_t kContentEnd = GetContentEnd(); 8563 const int32_t kEndOffset = std::min(aInOffset + aLength, kContentEnd); 8564 8565 if (aInOffset >= kEndOffset) { 8566 return NS_OK; 8567 } 8568 8569 if (!aRects.SetCapacity(aRects.Length() + kEndOffset - aInOffset, 8570 mozilla::fallible)) { 8571 return NS_ERROR_OUT_OF_MEMORY; 8572 } 8573 8574 do { 8575 // We'd like to assert here that |point| matches 8576 // |GetPointFromIterator(iter, properties)|, which in principle should be 8577 // true; however, testcases with vast dimensions can lead to coordinate 8578 // overflow and disrupt the calculations. So we've dropped the assertion 8579 // to avoid tripping the fuzzer unnecessarily. 8580 8581 // Measure to the end of the cluster. 8582 nscoord iSize = 0; 8583 gfxSkipCharsIterator nextIter(iter); 8584 if (aInOffset < kContentEnd) { 8585 nextIter.AdvanceOriginal(1); 8586 if (!nextIter.IsOriginalCharSkipped() && 8587 !mTextRun->IsClusterStart(nextIter.GetSkippedOffset()) && 8588 nextIter.GetOriginalOffset() < kContentEnd) { 8589 FindClusterEnd(mTextRun, kContentEnd, &nextIter); 8590 } 8591 8592 gfxFloat advance = mTextRun->GetAdvanceWidth( 8593 Range(iter.GetSkippedOffset(), nextIter.GetSkippedOffset()), 8594 &properties); 8595 iSize = NSToCoordCeilClamped(advance); 8596 } 8597 8598 // Compute the cluster rect, depending on directionality, and update 8599 // point to the origin we'll need for the next cluster. 8600 nsRect rect; 8601 rect.x = point.x; 8602 rect.y = point.y; 8603 8604 if (mTextRun->IsVertical()) { 8605 rect.width = mRect.width; 8606 rect.height = iSize; 8607 if (mTextRun->IsInlineReversed()) { 8608 // The iterator above returns a point with the origin at the 8609 // bottom left instead of the top left. Move the origin to the top left 8610 // by subtracting the character's height. 8611 rect.y -= rect.height; 8612 point.y -= iSize; 8613 } else { 8614 point.y += iSize; 8615 } 8616 } else { 8617 if (Style()->IsTextCombined()) { 8618 // The scale factor applies to the inline advance of the glyphs, so it 8619 // affects both the rect width and the origin point for the next glyph. 8620 iSize *= GetTextCombineScale(); 8621 } 8622 rect.width = iSize; 8623 rect.height = mRect.height; 8624 if (mTextRun->IsInlineReversed()) { 8625 // The iterator above returns a point with the origin at the 8626 // top right instead of the top left. Move the origin to the top left by 8627 // subtracting the character's width. 8628 rect.x -= iSize; 8629 point.x -= iSize; 8630 } else { 8631 point.x += iSize; 8632 } 8633 } 8634 8635 // Set the rect for all characters in the cluster. 8636 int32_t end = std::min(kEndOffset, nextIter.GetOriginalOffset()); 8637 while (aInOffset < end) { 8638 aRects.AppendElement(rect); 8639 aInOffset++; 8640 } 8641 8642 // Advance iter for the next cluster. 8643 iter = nextIter; 8644 } while (aInOffset < kEndOffset); 8645 8646 return NS_OK; 8647 } 8648 8649 nsresult nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset, 8650 bool aHint, 8651 int32_t* aOutOffset, 8652 nsIFrame** aOutFrame) { 8653 DEBUG_VERIFY_NOT_DIRTY(GetStateBits()); 8654 #if 0 // XXXrbs disable due to bug 310227 8655 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) 8656 return NS_ERROR_UNEXPECTED; 8657 #endif 8658 8659 NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters"); 8660 NS_ASSERTION(aContentOffset >= 0, 8661 "Negative content offset, existing code was very broken!"); 8662 nsIFrame* primaryFrame = mContent->GetPrimaryFrame(); 8663 if (this != primaryFrame) { 8664 // This call needs to happen on the primary frame 8665 return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint, 8666 aOutOffset, aOutFrame); 8667 } 8668 8669 nsTextFrame* f = this; 8670 int32_t offset = mContentOffset; 8671 8672 // Try to look up the offset to frame property 8673 nsTextFrame* cachedFrame = GetProperty(OffsetToFrameProperty()); 8674 8675 if (cachedFrame) { 8676 f = cachedFrame; 8677 offset = f->GetContentOffset(); 8678 8679 f->RemoveStateBits(TEXT_IN_OFFSET_CACHE); 8680 } 8681 8682 if ((aContentOffset >= offset) && (aHint || aContentOffset != offset)) { 8683 while (true) { 8684 nsTextFrame* next = f->GetNextContinuation(); 8685 if (!next || aContentOffset < next->GetContentOffset()) { 8686 break; 8687 } 8688 if (aContentOffset == next->GetContentOffset()) { 8689 if (aHint) { 8690 f = next; 8691 if (f->GetContentLength() == 0) { 8692 continue; // use the last of the empty frames with this offset 8693 } 8694 } 8695 break; 8696 } 8697 f = next; 8698 } 8699 } else { 8700 while (true) { 8701 nsTextFrame* prev = f->GetPrevContinuation(); 8702 if (!prev || aContentOffset > f->GetContentOffset()) { 8703 break; 8704 } 8705 if (aContentOffset == f->GetContentOffset()) { 8706 if (!aHint) { 8707 f = prev; 8708 if (f->GetContentLength() == 0) { 8709 continue; // use the first of the empty frames with this offset 8710 } 8711 } 8712 break; 8713 } 8714 f = prev; 8715 } 8716 } 8717 8718 *aOutOffset = aContentOffset - f->GetContentOffset(); 8719 *aOutFrame = f; 8720 8721 // cache the frame we found 8722 SetProperty(OffsetToFrameProperty(), f); 8723 f->AddStateBits(TEXT_IN_OFFSET_CACHE); 8724 8725 return NS_OK; 8726 } 8727 8728 nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetNoAmount(bool aForward, 8729 int32_t* aOffset) { 8730 NS_ASSERTION(aOffset && *aOffset <= GetContentLength(), 8731 "aOffset out of range"); 8732 8733 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 8734 if (!mTextRun) { 8735 return CONTINUE_EMPTY; 8736 } 8737 8738 TrimmedOffsets trimmed = GetTrimmedOffsets(CharacterDataBuffer()); 8739 // Check whether there are nonskipped characters in the trimmmed range 8740 return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) > 8741 iter.ConvertOriginalToSkipped(trimmed.mStart)) 8742 ? FOUND 8743 : CONTINUE; 8744 } 8745 8746 /** 8747 * This class iterates through the clusters before or after the given 8748 * aPosition (which is a content offset). You can test each cluster 8749 * to see if it's whitespace (as far as selection/caret movement is concerned), 8750 * or punctuation, or if there is a word break before the cluster. ("Before" 8751 * is interpreted according to aDirection, so if aDirection is -1, "before" 8752 * means actually *after* the cluster content.) 8753 */ 8754 class MOZ_STACK_CLASS ClusterIterator { 8755 public: 8756 ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, 8757 int32_t aDirection, nsString& aContext, 8758 bool aTrimSpaces = true); 8759 8760 bool NextCluster(); 8761 bool IsInlineWhitespace() const; 8762 bool IsNewline() const; 8763 bool IsPunctuation() const; 8764 intl::Script ScriptCode() const; 8765 bool HaveWordBreakBefore() const { return mHaveWordBreak; } 8766 8767 // Get the charIndex that corresponds to the "before" side of the current 8768 // character, according to the direction of iteration: so for a forward 8769 // iterator, this is simply mCharIndex, while for a reverse iterator it will 8770 // be mCharIndex + <number of code units in the character>. 8771 int32_t GetBeforeOffset() const { 8772 MOZ_ASSERT(mCharIndex >= 0); 8773 return mDirection < 0 ? GetAfterInternal() : mCharIndex; 8774 } 8775 // Get the charIndex that corresponds to the "before" side of the current 8776 // character, according to the direction of iteration: the opposite side 8777 // to what GetBeforeOffset returns. 8778 int32_t GetAfterOffset() const { 8779 MOZ_ASSERT(mCharIndex >= 0); 8780 return mDirection > 0 ? GetAfterInternal() : mCharIndex; 8781 } 8782 8783 private: 8784 // Helper for Get{After,Before}Offset; returns the charIndex after the 8785 // current position in the text, accounting for surrogate pairs. 8786 int32_t GetAfterInternal() const; 8787 8788 gfxSkipCharsIterator mIterator; 8789 // Usually, mCharacterDataBuffer is pointer to `dom::CharacterData::mText`. 8790 // However, if we're in a password field, this points to `mMaskedBuffer`. 8791 const CharacterDataBuffer* mCharacterDataBuffer; 8792 // If we're in a password field, this is initialized with mask characters. 8793 CharacterDataBuffer mMaskedBuffer; 8794 nsTextFrame* mTextFrame; 8795 int32_t mDirection; // +1 or -1, or 0 to indicate failure 8796 int32_t mCharIndex; 8797 nsTextFrame::TrimmedOffsets mTrimmed; 8798 nsTArray<bool> mWordBreaks; 8799 bool mHaveWordBreak; 8800 }; 8801 8802 static bool IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter, 8803 bool aRespectClusters, 8804 const gfxTextRun* aTextRun, 8805 nsTextFrame* aFrame) { 8806 if (aIter.IsOriginalCharSkipped()) { 8807 return false; 8808 } 8809 uint32_t index = aIter.GetSkippedOffset(); 8810 if (aRespectClusters && !aTextRun->IsClusterStart(index)) { 8811 return false; 8812 } 8813 if (index > 0) { 8814 // Check whether the proposed position is in between the two halves of a 8815 // surrogate pair, before a Variation Selector character, or within a 8816 // ligated emoji sequence; if so, this is not a valid character boundary. 8817 // (In the case where we are respecting clusters, we won't actually get 8818 // this far because the low surrogate is also marked as non-clusterStart 8819 // so we'll return FALSE above.) 8820 const uint32_t offs = AssertedCast<uint32_t>(aIter.GetOriginalOffset()); 8821 const CharacterDataBuffer& characterDataBuffer = 8822 aFrame->CharacterDataBuffer(); 8823 const char16_t ch = characterDataBuffer.CharAt(offs); 8824 8825 if (gfxFontUtils::IsVarSelector(ch) || 8826 characterDataBuffer.IsLowSurrogateFollowingHighSurrogateAt(offs) || 8827 (!aTextRun->IsLigatureGroupStart(index) && 8828 (unicode::GetEmojiPresentation(ch) == unicode::EmojiDefault || 8829 (unicode::GetEmojiPresentation(ch) == unicode::TextDefault && 8830 offs + 1 < characterDataBuffer.GetLength() && 8831 characterDataBuffer.CharAt(offs + 1) == 8832 gfxFontUtils::kUnicodeVS16)))) { 8833 return false; 8834 } 8835 8836 // If the proposed position is before a high surrogate, we need to decode 8837 // the surrogate pair (if valid) and check the resulting character. 8838 if (NS_IS_HIGH_SURROGATE(ch)) { 8839 if (const char32_t ucs4 = characterDataBuffer.ScalarValueAt(offs)) { 8840 // If the character is a (Plane-14) variation selector, 8841 // or an emoji character that is ligated with the previous 8842 // character (i.e. part of a Regional-Indicator flag pair, 8843 // or an emoji-ZWJ sequence), this is not a valid boundary. 8844 if (gfxFontUtils::IsVarSelector(ucs4) || 8845 (!aTextRun->IsLigatureGroupStart(index) && 8846 unicode::GetEmojiPresentation(ucs4) == unicode::EmojiDefault)) { 8847 return false; 8848 } 8849 } 8850 } 8851 } 8852 return true; 8853 } 8854 8855 nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetCharacter( 8856 bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) { 8857 const int32_t contentLengthInFrame = GetContentLength(); 8858 NS_ASSERTION(aOffset && *aOffset <= contentLengthInFrame, 8859 "aOffset out of range"); 8860 8861 if (!aOptions.mIgnoreUserStyleAll) { 8862 StyleUserSelect selectStyle; 8863 (void)IsSelectable(&selectStyle); 8864 if (selectStyle == StyleUserSelect::All) { 8865 return CONTINUE_UNSELECTABLE; 8866 } 8867 } 8868 8869 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 8870 if (!mTextRun) { 8871 return CONTINUE_EMPTY; 8872 } 8873 const TrimmedOffsets trimmed = 8874 GetTrimmedOffsets(CharacterDataBuffer(), TrimmedOffsetFlags::NoTrimAfter); 8875 8876 // A negative offset means "end of frame". 8877 const int32_t offset = 8878 GetContentOffset() + (*aOffset < 0 ? contentLengthInFrame : *aOffset); 8879 8880 if (!aForward) { 8881 const int32_t endOffset = [&]() -> int32_t { 8882 const int32_t minEndOffset = std::min(trimmed.GetEnd(), offset); 8883 if (minEndOffset <= trimmed.mStart || 8884 minEndOffset + 1 >= trimmed.GetEnd()) { 8885 return minEndOffset; 8886 } 8887 // If the end offset points a character in this frame and it's skipped 8888 // character, we want to scan previous character of the prceding first 8889 // non-skipped character. 8890 for (const int32_t i : 8891 Reversed(IntegerRange(trimmed.mStart, minEndOffset + 1))) { 8892 iter.SetOriginalOffset(i); 8893 if (!iter.IsOriginalCharSkipped()) { 8894 return i; 8895 } 8896 } 8897 return trimmed.mStart; 8898 }(); 8899 // If we're at the start of a line, look at the next continuation 8900 if (endOffset <= trimmed.mStart) { 8901 *aOffset = 0; 8902 return CONTINUE; 8903 } 8904 // If at the beginning of the line, look at the previous continuation 8905 for (const int32_t i : Reversed(IntegerRange(trimmed.mStart, endOffset))) { 8906 iter.SetOriginalOffset(i); 8907 // If we entered into a skipped char range again, we should skip all 8908 // of them. However, we cannot know the number of preceding skipped 8909 // chars. Therefore, we cannot skip all of them once. 8910 if (iter.IsOriginalCharSkipped()) { 8911 continue; 8912 } 8913 if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun, 8914 this)) { 8915 *aOffset = i - mContentOffset; 8916 return FOUND; 8917 } 8918 } 8919 *aOffset = 0; 8920 return CONTINUE; 8921 } 8922 8923 // If we're at the end of a line, look at the next continuation 8924 if (offset + 1 > trimmed.GetEnd()) { 8925 *aOffset = contentLengthInFrame; 8926 return CONTINUE; 8927 } 8928 8929 iter.SetOriginalOffset(offset); 8930 8931 // If we're at a preformatted linefeed, look at the next continutation 8932 if (offset < trimmed.GetEnd() && StyleText()->NewlineIsSignificant(this) && 8933 iter.GetSkippedOffset() < mTextRun->GetLength() && 8934 mTextRun->CharIsNewline(iter.GetSkippedOffset())) { 8935 *aOffset = contentLengthInFrame; 8936 return CONTINUE; 8937 } 8938 8939 const int32_t scanStartOffset = [&]() -> int32_t { 8940 // If current char is skipped, scan starting from the following 8941 // non-skipped char. 8942 int32_t skippedLength = 0; 8943 if (iter.IsOriginalCharSkipped(&skippedLength)) { 8944 const int32_t skippedLengthInFrame = 8945 std::min(skippedLength, trimmed.GetEnd() - iter.GetOriginalOffset()); 8946 return iter.GetOriginalOffset() + skippedLengthInFrame + 1; 8947 } 8948 return iter.GetOriginalOffset() + 1; 8949 }(); 8950 8951 for (int32_t i = scanStartOffset; i < trimmed.GetEnd(); i++) { 8952 iter.SetOriginalOffset(i); 8953 // If we entered into a skipped char range again, we should skip all 8954 // of them. 8955 int32_t skippedLength = 0; 8956 if (iter.IsOriginalCharSkipped(&skippedLength)) { 8957 const int32_t skippedLengthInFrame = 8958 std::min(skippedLength, trimmed.GetEnd() - iter.GetOriginalOffset()); 8959 if (skippedLengthInFrame) { 8960 i += skippedLengthInFrame - 1; 8961 } 8962 continue; 8963 } 8964 if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun, 8965 this)) { 8966 *aOffset = i - mContentOffset; 8967 return FOUND; 8968 } 8969 } 8970 8971 *aOffset = trimmed.GetEnd() - mContentOffset; 8972 return FOUND; 8973 } 8974 8975 bool ClusterIterator::IsInlineWhitespace() const { 8976 NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); 8977 return IsSelectionInlineWhitespace(*mCharacterDataBuffer, mCharIndex); 8978 } 8979 8980 bool ClusterIterator::IsNewline() const { 8981 NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); 8982 return IsSelectionNewline(*mCharacterDataBuffer, mCharIndex); 8983 } 8984 8985 bool ClusterIterator::IsPunctuation() const { 8986 NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); 8987 const char16_t ch = 8988 mCharacterDataBuffer->CharAt(AssertedCast<uint32_t>(mCharIndex)); 8989 return mozilla::IsPunctuationForWordSelect(ch); 8990 } 8991 8992 intl::Script ClusterIterator::ScriptCode() const { 8993 NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); 8994 const char16_t ch = 8995 mCharacterDataBuffer->CharAt(AssertedCast<uint32_t>(mCharIndex)); 8996 return intl::UnicodeProperties::GetScriptCode(ch); 8997 } 8998 8999 static inline bool IsKorean(intl::Script aScript) { 9000 // We only need to check for HANGUL script code; there is a script code 9001 // KOREAN but this is not assigned to any codepoints. (If that ever changes, 9002 // we could check for both codes here.) 9003 MOZ_ASSERT(aScript != intl::Script::KOREAN, "unexpected script code"); 9004 return aScript == intl::Script::HANGUL; 9005 } 9006 9007 int32_t ClusterIterator::GetAfterInternal() const { 9008 if (mCharacterDataBuffer->IsHighSurrogateFollowedByLowSurrogateAt( 9009 AssertedCast<uint32_t>(mCharIndex))) { 9010 return mCharIndex + 2; 9011 } 9012 return mCharIndex + 1; 9013 } 9014 9015 bool ClusterIterator::NextCluster() { 9016 if (!mDirection) { 9017 return false; 9018 } 9019 const gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated); 9020 9021 mHaveWordBreak = false; 9022 while (true) { 9023 bool keepGoing = false; 9024 if (mDirection > 0) { 9025 if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd()) { 9026 return false; 9027 } 9028 keepGoing = mIterator.IsOriginalCharSkipped() || 9029 mIterator.GetOriginalOffset() < mTrimmed.mStart || 9030 !textRun->IsClusterStart(mIterator.GetSkippedOffset()); 9031 mCharIndex = mIterator.GetOriginalOffset(); 9032 mIterator.AdvanceOriginal(1); 9033 } else { 9034 if (mIterator.GetOriginalOffset() <= mTrimmed.mStart) { 9035 // Trimming can skip backward word breakers, see bug 1667138 9036 return mHaveWordBreak; 9037 } 9038 mIterator.AdvanceOriginal(-1); 9039 keepGoing = mIterator.IsOriginalCharSkipped() || 9040 mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() || 9041 !textRun->IsClusterStart(mIterator.GetSkippedOffset()); 9042 mCharIndex = mIterator.GetOriginalOffset(); 9043 } 9044 9045 if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) { 9046 mHaveWordBreak = true; 9047 } 9048 if (!keepGoing) { 9049 return true; 9050 } 9051 } 9052 } 9053 9054 ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, 9055 int32_t aDirection, nsString& aContext, 9056 bool aTrimSpaces) 9057 : mIterator(aTextFrame->EnsureTextRun(nsTextFrame::eInflated)), 9058 mTextFrame(aTextFrame), 9059 mDirection(aDirection), 9060 mCharIndex(-1), 9061 mHaveWordBreak(false) { 9062 gfxTextRun* textRun = aTextFrame->GetTextRun(nsTextFrame::eInflated); 9063 if (!textRun) { 9064 mDirection = 0; // signal failure 9065 return; 9066 } 9067 9068 mCharacterDataBuffer = &aTextFrame->CharacterDataBuffer(); 9069 9070 const uint32_t textOffset = 9071 AssertedCast<uint32_t>(aTextFrame->GetContentOffset()); 9072 const uint32_t textLen = 9073 AssertedCast<uint32_t>(aTextFrame->GetContentLength()); 9074 9075 // If we're in a password field, some characters may be masked. In such 9076 // case, we need to treat each masked character as a mask character since 9077 // we shouldn't expose word boundary which is hidden by the masking. 9078 if (aTextFrame->GetContent() && mCharacterDataBuffer->GetLength() > 0 && 9079 aTextFrame->GetContent()->HasFlag(NS_MAYBE_MASKED) && 9080 (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed)) { 9081 const char16_t kPasswordMask = TextEditor::PasswordMask(); 9082 const nsTransformedTextRun* transformedTextRun = 9083 static_cast<const nsTransformedTextRun*>(textRun); 9084 // Use nsString and not nsAutoString so that we get a nsStringBuffer which 9085 // can be just AddRefed in `mMaskedFrag`. 9086 nsString maskedText; 9087 maskedText.SetCapacity(mCharacterDataBuffer->GetLength()); 9088 // Note that aTextFrame may not cover the whole of mFrag (in cases with 9089 // bidi continuations), so we cannot rely on its textrun (and associated 9090 // styles) being available for the entire fragment. 9091 uint32_t i = 0; 9092 // Just copy any text that precedes what aTextFrame covers. 9093 while (i < textOffset) { 9094 maskedText.Append(mCharacterDataBuffer->CharAt(i++)); 9095 } 9096 // For the range covered by aTextFrame, mask chars if appropriate. 9097 while (i < textOffset + textLen) { 9098 uint32_t skippedOffset = mIterator.ConvertOriginalToSkipped(i); 9099 bool mask = 9100 skippedOffset < transformedTextRun->GetLength() 9101 ? transformedTextRun->mStyles[skippedOffset]->mMaskPassword 9102 : false; 9103 if (mCharacterDataBuffer->IsHighSurrogateFollowedByLowSurrogateAt(i)) { 9104 if (mask) { 9105 maskedText.Append(kPasswordMask); 9106 maskedText.Append(kPasswordMask); 9107 } else { 9108 maskedText.Append(mCharacterDataBuffer->CharAt(i)); 9109 maskedText.Append(mCharacterDataBuffer->CharAt(i + 1)); 9110 } 9111 i += 2; 9112 } else { 9113 maskedText.Append(mask ? kPasswordMask 9114 : mCharacterDataBuffer->CharAt(i)); 9115 ++i; 9116 } 9117 } 9118 // Copy any trailing text from the fragment. 9119 while (i < mCharacterDataBuffer->GetLength()) { 9120 maskedText.Append(mCharacterDataBuffer->CharAt(i++)); 9121 } 9122 mMaskedBuffer.SetTo(maskedText, mCharacterDataBuffer->IsBidi(), true); 9123 mCharacterDataBuffer = &mMaskedBuffer; 9124 } 9125 9126 mIterator.SetOriginalOffset(aPosition); 9127 mTrimmed = aTextFrame->GetTrimmedOffsets( 9128 *mCharacterDataBuffer, 9129 aTrimSpaces ? nsTextFrame::TrimmedOffsetFlags::Default 9130 : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter | 9131 nsTextFrame::TrimmedOffsetFlags::NoTrimBefore); 9132 9133 // Allocate an extra element to record the word break at the end of the line 9134 // or text run in mWordBreak[textLen]. 9135 mWordBreaks.AppendElements(textLen + 1); 9136 PodZero(mWordBreaks.Elements(), textLen + 1); 9137 uint32_t textStart; 9138 if (aDirection > 0) { 9139 if (aContext.IsEmpty()) { 9140 // No previous context, so it must be the start of a line or text run 9141 mWordBreaks[0] = true; 9142 } 9143 textStart = aContext.Length(); 9144 mCharacterDataBuffer->AppendTo(aContext, textOffset, textLen); 9145 } else { 9146 if (aContext.IsEmpty()) { 9147 // No following context, so it must be the end of a line or text run 9148 mWordBreaks[textLen] = true; 9149 } 9150 textStart = 0; 9151 nsAutoString str; 9152 mCharacterDataBuffer->AppendTo(str, textOffset, textLen); 9153 aContext.Insert(str, 0); 9154 } 9155 9156 const uint32_t textEnd = textStart + textLen; 9157 intl::WordBreakIteratorUtf16 wordBreakIter(aContext); 9158 Maybe<uint32_t> nextBreak = 9159 wordBreakIter.Seek(textStart > 0 ? textStart - 1 : textStart); 9160 while (nextBreak && *nextBreak <= textEnd) { 9161 mWordBreaks[*nextBreak - textStart] = true; 9162 nextBreak = wordBreakIter.Next(); 9163 } 9164 9165 MOZ_ASSERT(textEnd != aContext.Length() || mWordBreaks[textLen], 9166 "There should be a word break at the end of a line or text run!"); 9167 } 9168 9169 nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetWord( 9170 bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect, 9171 int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) { 9172 int32_t contentLength = GetContentLength(); 9173 NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range"); 9174 9175 StyleUserSelect selectStyle; 9176 (void)IsSelectable(&selectStyle); 9177 if (selectStyle == StyleUserSelect::All) { 9178 return CONTINUE_UNSELECTABLE; 9179 } 9180 9181 int32_t offset = 9182 GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset); 9183 ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext, 9184 aTrimSpaces); 9185 9186 if (!cIter.NextCluster()) { 9187 return CONTINUE_EMPTY; 9188 } 9189 9190 // Do we need to check for Korean characters? 9191 bool is2b = CharacterDataBuffer().Is2b(); 9192 do { 9193 bool isPunctuation = cIter.IsPunctuation(); 9194 bool isInlineWhitespace = cIter.IsInlineWhitespace(); 9195 bool isWhitespace = isInlineWhitespace || cIter.IsNewline(); 9196 bool isWordBreakBefore = cIter.HaveWordBreakBefore(); 9197 // If the text is one-byte, we don't actually care about script code as 9198 // there cannot be any Korean in the frame. 9199 intl::Script scriptCode = is2b ? cIter.ScriptCode() : intl::Script::COMMON; 9200 if (!isWhitespace || isInlineWhitespace) { 9201 aState->SetSawInlineCharacter(); 9202 } 9203 if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) { 9204 aState->SetSawBeforeType(); 9205 aState->Update(isPunctuation, isWhitespace, scriptCode); 9206 continue; 9207 } 9208 // See if we can break before the current cluster 9209 if (!aState->mAtStart) { 9210 bool canBreak; 9211 if (isPunctuation != aState->mLastCharWasPunctuation) { 9212 canBreak = BreakWordBetweenPunctuation(aState, aForward, isPunctuation, 9213 isWhitespace, aIsKeyboardSelect); 9214 } else if (!aState->mLastCharWasWhitespace && !isWhitespace && 9215 !isPunctuation && isWordBreakBefore) { 9216 // if both the previous and the current character are not white 9217 // space but this can be word break before, we don't need to eat 9218 // a white space in this case. This case happens in some languages 9219 // that their words are not separated by white spaces. E.g., 9220 // Japanese and Chinese. 9221 canBreak = true; 9222 } else { 9223 canBreak = isWordBreakBefore && aState->mSawBeforeType && 9224 (aWordSelectEatSpace != isWhitespace); 9225 } 9226 // Special-case for Korean: treat a boundary between Hangul & non-Hangul 9227 // characters as a word boundary (see bug 1973393 and UAX#29). 9228 if (!canBreak && is2b && aState->mLastScript != intl::Script::INVALID && 9229 IsKorean(aState->mLastScript) != IsKorean(scriptCode)) { 9230 canBreak = true; 9231 } 9232 if (canBreak) { 9233 *aOffset = cIter.GetBeforeOffset() - mContentOffset; 9234 return FOUND; 9235 } 9236 } 9237 aState->Update(isPunctuation, isWhitespace, scriptCode); 9238 } while (cIter.NextCluster()); 9239 9240 *aOffset = cIter.GetAfterOffset() - mContentOffset; 9241 return CONTINUE; 9242 } 9243 9244 bool nsTextFrame::HasVisibleText() { 9245 // Text in the range is visible if there is at least one character in the 9246 // range that is not skipped and is mapped by this frame (which is the primary 9247 // frame) or one of its continuations. 9248 for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) { 9249 int32_t dummyOffset = 0; 9250 if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) { 9251 return true; 9252 } 9253 } 9254 return false; 9255 } 9256 9257 std::pair<int32_t, int32_t> nsTextFrame::GetOffsets() const { 9258 return std::make_pair(GetContentOffset(), GetContentEnd()); 9259 } 9260 9261 static bool IsFirstLetterPrefixPunctuation(uint32_t aChar) { 9262 switch (mozilla::unicode::GetGeneralCategory(aChar)) { 9263 case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */ 9264 case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION: /* Pd */ 9265 case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: /* Pe */ 9266 case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: /* Pf */ 9267 case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */ 9268 case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: /* Po */ 9269 case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION: /* Ps */ 9270 return true; 9271 default: 9272 return false; 9273 } 9274 } 9275 9276 static bool IsFirstLetterSuffixPunctuation(uint32_t aChar) { 9277 switch (mozilla::unicode::GetGeneralCategory(aChar)) { 9278 case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */ 9279 case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: /* Pe */ 9280 case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: /* Pf */ 9281 case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */ 9282 case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: /* Po */ 9283 return true; 9284 default: 9285 return false; 9286 } 9287 } 9288 9289 static int32_t FindEndOfPrefixPunctuationRun(const CharacterDataBuffer& aBuffer, 9290 const gfxTextRun* aTextRun, 9291 gfxSkipCharsIterator* aIter, 9292 int32_t aOffset, int32_t aStart, 9293 int32_t aEnd) { 9294 int32_t i; 9295 for (i = aStart; i < aEnd - aOffset; ++i) { 9296 if (IsFirstLetterPrefixPunctuation( 9297 aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)))) { 9298 aIter->SetOriginalOffset(aOffset + i); 9299 FindClusterEnd(aTextRun, aEnd, aIter); 9300 i = aIter->GetOriginalOffset() - aOffset; 9301 } else { 9302 break; 9303 } 9304 } 9305 return i; 9306 } 9307 9308 static int32_t FindEndOfSuffixPunctuationRun(const CharacterDataBuffer& aBuffer, 9309 const gfxTextRun* aTextRun, 9310 gfxSkipCharsIterator* aIter, 9311 int32_t aOffset, int32_t aStart, 9312 int32_t aEnd) { 9313 int32_t i; 9314 for (i = aStart; i < aEnd - aOffset; ++i) { 9315 if (IsFirstLetterSuffixPunctuation( 9316 aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)))) { 9317 aIter->SetOriginalOffset(aOffset + i); 9318 FindClusterEnd(aTextRun, aEnd, aIter); 9319 i = aIter->GetOriginalOffset() - aOffset; 9320 } else { 9321 break; 9322 } 9323 } 9324 return i; 9325 } 9326 9327 /** 9328 * Returns true if this text frame completes the first-letter, false 9329 * if it does not contain a true "letter". 9330 * If returns true, then it also updates aLength to cover just the first-letter 9331 * text. 9332 * 9333 * XXX :first-letter should be handled during frame construction 9334 * (and it has a good bit in common with nextBidi) 9335 * 9336 * @param aLength an in/out parameter: on entry contains the maximum length to 9337 * return, on exit returns length of the first-letter fragment (which may 9338 * include leading and trailing punctuation, for example) 9339 */ 9340 static bool FindFirstLetterRange(const CharacterDataBuffer& aBuffer, 9341 const nsAtom* aLang, 9342 const gfxTextRun* aTextRun, int32_t aOffset, 9343 const gfxSkipCharsIterator& aIter, 9344 int32_t* aLength) { 9345 int32_t length = *aLength; 9346 int32_t endOffset = aOffset + length; 9347 gfxSkipCharsIterator iter(aIter); 9348 9349 // Currently the only language-specific special case we handle here is the 9350 // Dutch "IJ" digraph. 9351 auto LangTagIsDutch = [](const nsAtom* aLang) -> bool { 9352 if (!aLang) { 9353 return false; 9354 } 9355 if (aLang == nsGkAtoms::nl) { 9356 return true; 9357 } 9358 // We don't need to fully parse as a Locale; just check the initial subtag. 9359 nsDependentAtomString langStr(aLang); 9360 int32_t index = langStr.FindChar('-'); 9361 if (index > 0) { 9362 langStr.Truncate(index); 9363 return langStr.EqualsLiteral("nl"); 9364 } 9365 return false; 9366 }; 9367 9368 // Skip any trimmable leading whitespace. 9369 int32_t i = GetTrimmableWhitespaceCount(aBuffer, aOffset, length, 1); 9370 while (true) { 9371 // Scan past any leading punctuation. This leaves `j` at the first 9372 // non-punctuation character. 9373 int32_t j = FindEndOfPrefixPunctuationRun(aBuffer, aTextRun, &iter, aOffset, 9374 i, endOffset); 9375 if (j == length) { 9376 return false; 9377 } 9378 9379 // Scan past any Unicode whitespace characters after punctuation. 9380 while (j < length) { 9381 char16_t ch = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + j)); 9382 // The spec says to allow "characters that belong to the `Zs` Unicode 9383 // general category _other than_ U+3000" here. 9384 if (unicode::GetGeneralCategory(ch) == 9385 HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR && 9386 ch != 0x3000) { 9387 ++j; 9388 } else { 9389 break; 9390 } 9391 } 9392 if (j == length) { 9393 return false; 9394 } 9395 if (j == i) { 9396 // If no whitespace was found, we've finished the first-letter prefix; 9397 // if there was some, then go back to check for more punctuation. 9398 break; 9399 } 9400 i = j; 9401 } 9402 9403 // If the next character is not a letter, number or symbol, there is no 9404 // first-letter. 9405 // Return true so that we don't go on looking, but set aLength to 0. 9406 const char32_t usv = 9407 aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)); 9408 if (!nsContentUtils::IsAlphanumericOrSymbol(usv)) { 9409 *aLength = 0; 9410 return true; 9411 } 9412 9413 // Consume another cluster (the actual first letter): 9414 9415 // For complex scripts such as Indic and SEAsian, where first-letter 9416 // should extend to entire orthographic "syllable" clusters, we don't 9417 // want to allow this to split a ligature. 9418 bool allowSplitLigature; 9419 bool usesIndicHalfForms = false; 9420 9421 typedef intl::Script Script; 9422 Script script = intl::UnicodeProperties::GetScriptCode(usv); 9423 switch (script) { 9424 default: 9425 allowSplitLigature = true; 9426 break; 9427 9428 // Don't break regional-indicator ligatures. 9429 case Script::COMMON: 9430 allowSplitLigature = !gfxFontUtils::IsRegionalIndicator(usv); 9431 break; 9432 9433 // For now, lacking any definitive specification of when to apply this 9434 // behavior, we'll base the decision on the HarfBuzz shaping engine 9435 // used for each script: those that are handled by the Indic, Tibetan, 9436 // Myanmar and SEAsian shapers will apply the "don't split ligatures" 9437 // rule. 9438 9439 // Indic 9440 case Script::BENGALI: 9441 case Script::DEVANAGARI: 9442 case Script::GUJARATI: 9443 usesIndicHalfForms = true; 9444 [[fallthrough]]; 9445 9446 case Script::GURMUKHI: 9447 case Script::KANNADA: 9448 case Script::MALAYALAM: 9449 case Script::ORIYA: 9450 case Script::TAMIL: 9451 case Script::TELUGU: 9452 case Script::SINHALA: 9453 case Script::BALINESE: 9454 case Script::LEPCHA: 9455 case Script::REJANG: 9456 case Script::SUNDANESE: 9457 case Script::JAVANESE: 9458 case Script::KAITHI: 9459 case Script::MEETEI_MAYEK: 9460 case Script::CHAKMA: 9461 case Script::SHARADA: 9462 case Script::TAKRI: 9463 case Script::KHMER: 9464 9465 // Tibetan 9466 case Script::TIBETAN: 9467 9468 // Myanmar 9469 case Script::MYANMAR: 9470 9471 // Other SEAsian 9472 case Script::BUGINESE: 9473 case Script::NEW_TAI_LUE: 9474 case Script::CHAM: 9475 case Script::TAI_THAM: 9476 9477 // What about Thai/Lao - any special handling needed? 9478 // Should we special-case Arabic lam-alef? 9479 9480 allowSplitLigature = false; 9481 break; 9482 } 9483 9484 // NOTE that FindClusterEnd sets the iterator to the last character that is 9485 // part of the cluster, NOT to the first character beyond it. 9486 iter.SetOriginalOffset(aOffset + i); 9487 FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature); 9488 9489 // Index of the last character included in the first-letter cluster. 9490 i = iter.GetOriginalOffset() - aOffset; 9491 9492 // Heuristic for Indic scripts that like to form conjuncts: 9493 // If we ended at a virama that is ligated with the preceding character 9494 // (e.g. creating a half-form), then don't stop here; include the next 9495 // cluster as well so that we don't break a conjunct. 9496 // 9497 // Unfortunately this cannot distinguish between a letter+virama that ligate 9498 // to create a half-form (in which case we have a conjunct that should not 9499 // be broken) and a letter+virama that ligate purely for presentational 9500 // reasons to position the (visible) virama component (in which case breaking 9501 // after the virama would be acceptable). So results may be imperfect, 9502 // depending how the font has chosen to implement visible viramas. 9503 if (usesIndicHalfForms) { 9504 while (i + 1 < length && 9505 !aTextRun->IsLigatureGroupStart(iter.GetSkippedOffset())) { 9506 char32_t c = aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)); 9507 if (intl::UnicodeProperties::GetCombiningClass(c) == 9508 HB_UNICODE_COMBINING_CLASS_VIRAMA) { 9509 iter.AdvanceOriginal(1); 9510 FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature); 9511 i = iter.GetOriginalOffset() - aOffset; 9512 } else { 9513 break; 9514 } 9515 } 9516 } 9517 9518 if (i + 1 == length) { 9519 return true; 9520 } 9521 9522 // Check for Dutch "ij" digraph special case, but only if both letters have 9523 // the same case. 9524 if (script == Script::LATIN && LangTagIsDutch(aLang)) { 9525 char16_t ch1 = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + i)); 9526 char16_t ch2 = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + i + 1)); 9527 if ((ch1 == 'i' && ch2 == 'j') || (ch1 == 'I' && ch2 == 'J')) { 9528 iter.SetOriginalOffset(aOffset + i + 1); 9529 FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature); 9530 i = iter.GetOriginalOffset() - aOffset; 9531 if (i + 1 == length) { 9532 return true; 9533 } 9534 } 9535 } 9536 9537 // When we reach here, `i` points to the last character of the first-letter 9538 // cluster, NOT to the first character beyond it. Advance to the next char, 9539 // ready to check for following whitespace/punctuation: 9540 ++i; 9541 9542 while (i < length) { 9543 // Skip over whitespace, except for word separator characters, before the 9544 // check for following punctuation. But remember the position before the 9545 // whitespace, in case we need to reset. 9546 const int32_t preWS = i; 9547 while (i < length) { 9548 char16_t ch = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + i)); 9549 // The spec says the first-letter suffix includes "any intervening 9550 // typographic space -- characters belonging to the Zs Unicode general 9551 // category other than U+3000 IDEOGRAPHIC SPACE or a word separator", 9552 // where "word separator" includes U+0020 and U+00A0. 9553 if (ch == 0x0020 || ch == 0x00A0 || ch == 0x3000 || 9554 unicode::GetGeneralCategory(ch) != 9555 HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR) { 9556 break; 9557 } else { 9558 ++i; 9559 } 9560 } 9561 9562 // Consume clusters that start with punctuation. 9563 const int32_t prePunct = i; 9564 i = FindEndOfSuffixPunctuationRun(aBuffer, aTextRun, &iter, aOffset, i, 9565 endOffset); 9566 9567 // If we didn't find punctuation here, then we also don't want to include 9568 // any preceding whitespace, so reset our index. 9569 if (i == prePunct) { 9570 i = preWS; 9571 break; 9572 } 9573 } 9574 9575 if (i < length) { 9576 *aLength = i; 9577 } 9578 return true; 9579 } 9580 9581 static uint32_t FindStartAfterSkippingWhitespace( 9582 nsTextFrame::PropertyProvider* aProvider, 9583 nsIFrame::InlineIntrinsicISizeData* aData, const nsStyleText* aTextStyle, 9584 gfxSkipCharsIterator* aIterator, uint32_t aFlowEndInTextRun) { 9585 if (aData->mSkipWhitespace) { 9586 while (aIterator->GetSkippedOffset() < aFlowEndInTextRun && 9587 IsTrimmableSpace(aProvider->GetCharacterDataBuffer(), 9588 aIterator->GetOriginalOffset(), aTextStyle)) { 9589 aIterator->AdvanceOriginal(1); 9590 } 9591 } 9592 return aIterator->GetSkippedOffset(); 9593 } 9594 9595 float nsTextFrame::GetFontSizeInflation() const { 9596 if (!HasFontSizeInflation()) { 9597 return 1.0f; 9598 } 9599 return GetProperty(FontSizeInflationProperty()); 9600 } 9601 9602 void nsTextFrame::SetFontSizeInflation(float aInflation) { 9603 if (aInflation == 1.0f) { 9604 if (HasFontSizeInflation()) { 9605 RemoveStateBits(TEXT_HAS_FONT_INFLATION); 9606 RemoveProperty(FontSizeInflationProperty()); 9607 } 9608 return; 9609 } 9610 9611 AddStateBits(TEXT_HAS_FONT_INFLATION); 9612 SetProperty(FontSizeInflationProperty(), aInflation); 9613 } 9614 9615 void nsTextFrame::SetHangableISize(nscoord aISize) { 9616 MOZ_ASSERT(aISize >= 0, "unexpected negative hangable advance"); 9617 if (aISize <= 0) { 9618 ClearHangableISize(); 9619 return; 9620 } 9621 SetProperty(HangableWhitespaceProperty(), aISize); 9622 mPropertyFlags |= PropertyFlags::HangableWS; 9623 } 9624 9625 nscoord nsTextFrame::GetHangableISize() const { 9626 MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::HangableWS) == 9627 HasProperty(HangableWhitespaceProperty()), 9628 "flag/property mismatch!"); 9629 return (mPropertyFlags & PropertyFlags::HangableWS) 9630 ? GetProperty(HangableWhitespaceProperty()) 9631 : 0; 9632 } 9633 9634 void nsTextFrame::ClearHangableISize() { 9635 if (mPropertyFlags & PropertyFlags::HangableWS) { 9636 RemoveProperty(HangableWhitespaceProperty()); 9637 mPropertyFlags &= ~PropertyFlags::HangableWS; 9638 } 9639 } 9640 9641 void nsTextFrame::SetTrimmableWS(gfxTextRun::TrimmableWS aTrimmableWS) { 9642 MOZ_ASSERT(aTrimmableWS.mAdvance >= 0, "negative trimmable size"); 9643 if (aTrimmableWS.mAdvance <= 0) { 9644 ClearTrimmableWS(); 9645 return; 9646 } 9647 SetProperty(TrimmableWhitespaceProperty(), aTrimmableWS); 9648 mPropertyFlags |= PropertyFlags::TrimmableWS; 9649 } 9650 9651 gfxTextRun::TrimmableWS nsTextFrame::GetTrimmableWS() const { 9652 MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::TrimmableWS) == 9653 HasProperty(TrimmableWhitespaceProperty()), 9654 "flag/property mismatch!"); 9655 return (mPropertyFlags & PropertyFlags::TrimmableWS) 9656 ? GetProperty(TrimmableWhitespaceProperty()) 9657 : gfxTextRun::TrimmableWS{}; 9658 } 9659 9660 void nsTextFrame::ClearTrimmableWS() { 9661 if (mPropertyFlags & PropertyFlags::TrimmableWS) { 9662 RemoveProperty(TrimmableWhitespaceProperty()); 9663 mPropertyFlags &= ~PropertyFlags::TrimmableWS; 9664 } 9665 } 9666 9667 /* virtual */ 9668 void nsTextFrame::MarkIntrinsicISizesDirty() { 9669 ClearTextRuns(); 9670 nsIFrame::MarkIntrinsicISizesDirty(); 9671 } 9672 9673 // XXX this doesn't handle characters shaped by line endings. We need to 9674 // temporarily override the "current line ending" settings. 9675 void nsTextFrame::AddInlineMinISizeForFlow(gfxContext* aRenderingContext, 9676 InlineMinISizeData* aData, 9677 TextRunType aTextRunType) { 9678 uint32_t flowEndInTextRun; 9679 gfxSkipCharsIterator iter = 9680 EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(), 9681 aData->LineContainer(), aData->mLine, &flowEndInTextRun); 9682 gfxTextRun* textRun = GetTextRun(aTextRunType); 9683 if (!textRun) { 9684 return; 9685 } 9686 9687 // Pass null for the line container. This will disable tab spacing, but that's 9688 // OK since we can't really handle tabs for intrinsic sizing anyway. 9689 const nsStyleText* textStyle = StyleText(); 9690 const auto& characterDataBuffer = CharacterDataBuffer(); 9691 9692 // If we're hyphenating, the PropertyProvider needs the actual length; 9693 // otherwise we can just pass INT32_MAX to mean "all the text" 9694 int32_t len = INT32_MAX; 9695 bool hyphenating = characterDataBuffer.GetLength() > 0 && 9696 (textStyle->mHyphens == StyleHyphens::Auto || 9697 (textStyle->mHyphens == StyleHyphens::Manual && 9698 !!(textRun->GetFlags() & 9699 gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS))); 9700 if (hyphenating) { 9701 gfxSkipCharsIterator tmp(iter); 9702 len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(), 9703 tmp.ConvertSkippedToOriginal(flowEndInTextRun)) - 9704 iter.GetOriginalOffset(); 9705 } 9706 PropertyProvider provider(textRun, textStyle, characterDataBuffer, this, iter, 9707 len, nullptr, 0, aTextRunType, 9708 aData->mAtStartOfLine); 9709 9710 bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant(); 9711 bool preformatNewlines = textStyle->NewlineIsSignificant(this); 9712 bool preformatTabs = textStyle->WhiteSpaceIsSignificant(); 9713 bool whitespaceCanHang = textStyle->WhiteSpaceCanHangOrVisuallyCollapse(); 9714 gfxFloat tabWidth = -1; 9715 uint32_t start = FindStartAfterSkippingWhitespace(&provider, aData, textStyle, 9716 &iter, flowEndInTextRun); 9717 9718 // text-combine-upright frame is constantly 1em on inline-axis; but if it has 9719 // a following sibling with the same style, it contributes nothing. 9720 if (Style()->IsTextCombined()) { 9721 if (start < flowEndInTextRun && textRun->CanBreakLineBefore(start)) { 9722 aData->OptionallyBreak(); 9723 } 9724 if (!GetNextSibling() || GetNextSibling()->Style() != Style()) { 9725 aData->mCurrentLine += provider.GetFontMetrics()->EmHeight(); 9726 } 9727 aData->mTrailingWhitespace = 0; 9728 return; 9729 } 9730 9731 if (textStyle->EffectiveOverflowWrap() == StyleOverflowWrap::Anywhere && 9732 textStyle->WordCanWrap(this)) { 9733 aData->OptionallyBreak(); 9734 aData->mCurrentLine += 9735 textRun->GetMinAdvanceWidth(Range(start, flowEndInTextRun)); 9736 aData->mTrailingWhitespace = 0; 9737 aData->mAtStartOfLine = false; 9738 aData->OptionallyBreak(); 9739 return; 9740 } 9741 9742 AutoTArray<gfxTextRun::HyphenType, BIG_TEXT_NODE_SIZE> hyphBuffer; 9743 if (hyphenating) { 9744 if (hyphBuffer.AppendElements(flowEndInTextRun - start, fallible)) { 9745 provider.GetHyphenationBreaks(Range(start, flowEndInTextRun), 9746 hyphBuffer.Elements()); 9747 } else { 9748 hyphenating = false; 9749 } 9750 } 9751 9752 for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) { 9753 bool preformattedNewline = false; 9754 bool preformattedTab = false; 9755 if (i < flowEndInTextRun) { 9756 // XXXldb Shouldn't we be including the newline as part of the 9757 // segment that it ends rather than part of the segment that it 9758 // starts? 9759 preformattedNewline = preformatNewlines && textRun->CharIsNewline(i); 9760 preformattedTab = preformatTabs && textRun->CharIsTab(i); 9761 if (!textRun->CanBreakLineBefore(i) && !preformattedNewline && 9762 !preformattedTab && 9763 (!hyphenating || 9764 !gfxTextRun::IsOptionalHyphenBreak(hyphBuffer[i - start]))) { 9765 // we can't break here (and it's not the end of the flow) 9766 continue; 9767 } 9768 } 9769 9770 if (i > wordStart) { 9771 nscoord width = NSToCoordCeilClamped( 9772 textRun->GetAdvanceWidth(Range(wordStart, i), &provider)); 9773 width = std::max(0, width); 9774 aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width); 9775 aData->mAtStartOfLine = false; 9776 9777 if (collapseWhitespace || whitespaceCanHang) { 9778 uint32_t trimStart = 9779 GetEndOfTrimmedText(characterDataBuffer, textStyle, wordStart, i, 9780 &iter, whitespaceCanHang); 9781 if (trimStart == start) { 9782 // This is *all* trimmable whitespace, so whatever trailingWhitespace 9783 // we saw previously is still trailing... 9784 aData->mTrailingWhitespace += width; 9785 } else { 9786 // Some non-whitespace so the old trailingWhitespace is no longer 9787 // trailing 9788 nscoord wsWidth = NSToCoordCeilClamped( 9789 textRun->GetAdvanceWidth(Range(trimStart, i), &provider)); 9790 aData->mTrailingWhitespace = std::max(0, wsWidth); 9791 } 9792 } else { 9793 aData->mTrailingWhitespace = 0; 9794 } 9795 } 9796 9797 if (preformattedTab) { 9798 PropertyProvider::Spacing spacing; 9799 provider.GetSpacing(Range(i, i + 1), &spacing); 9800 aData->mCurrentLine += nscoord(spacing.mBefore); 9801 if (tabWidth < 0) { 9802 tabWidth = ComputeTabWidthAppUnits(this); 9803 } 9804 gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth, 9805 provider.MinTabAdvance()); 9806 aData->mCurrentLine = nscoord(afterTab + spacing.mAfter); 9807 wordStart = i + 1; 9808 } else if (i < flowEndInTextRun || 9809 (i == textRun->GetLength() && 9810 (textRun->GetFlags2() & 9811 nsTextFrameUtils::Flags::HasTrailingBreak))) { 9812 if (preformattedNewline) { 9813 aData->ForceBreak(); 9814 } else if (i < flowEndInTextRun && hyphenating && 9815 gfxTextRun::IsOptionalHyphenBreak(hyphBuffer[i - start])) { 9816 aData->OptionallyBreak(NSToCoordRound(provider.GetHyphenWidth())); 9817 } else { 9818 aData->OptionallyBreak(); 9819 } 9820 if (aData->mSkipWhitespace) { 9821 iter.SetSkippedOffset(i); 9822 wordStart = FindStartAfterSkippingWhitespace( 9823 &provider, aData, textStyle, &iter, flowEndInTextRun); 9824 } else { 9825 wordStart = i; 9826 } 9827 provider.SetStartOfLine(iter); 9828 } 9829 } 9830 9831 if (start < flowEndInTextRun) { 9832 // Check if we have collapsible whitespace at the end 9833 aData->mSkipWhitespace = IsTrimmableSpace( 9834 provider.GetCharacterDataBuffer(), 9835 iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle); 9836 } 9837 } 9838 9839 bool nsTextFrame::IsCurrentFontInflation(float aInflation) const { 9840 return fabsf(aInflation - GetFontSizeInflation()) < 1e-6; 9841 } 9842 9843 void nsTextFrame::MaybeSplitFramesForFirstLetter() { 9844 if (!StaticPrefs::layout_css_intrinsic_size_first_letter_enabled()) { 9845 return; 9846 } 9847 9848 if (GetParent()->IsFloating() && GetContentLength() > 0) { 9849 // We've already claimed our first-letter content, don't try again. 9850 return; 9851 } 9852 if (GetPrevContinuation()) { 9853 // This isn't the first part of the first-letter. 9854 return; 9855 } 9856 9857 // Find the length of the first-letter. We need a textrun for this; just bail 9858 // out if we fail to create it. 9859 // But in the floating first-letter case, the text is initially all in our 9860 // next-in-flow, and the float itself is empty. So we need to look at that 9861 // textrun instead of our own during FindFirstLetterRange. 9862 nsTextFrame* f = GetParent()->IsFloating() ? GetNextInFlow() : this; 9863 gfxSkipCharsIterator iter = f->EnsureTextRun(nsTextFrame::eInflated); 9864 const gfxTextRun* textRun = f->GetTextRun(nsTextFrame::eInflated); 9865 9866 const auto& characterDataBuffer = CharacterDataBuffer(); 9867 const int32_t length = GetInFlowContentLength(); 9868 const int32_t offset = GetContentOffset(); 9869 int32_t firstLetterLength = length; 9870 NewlineProperty* cachedNewlineOffset = nullptr; 9871 int32_t newLineOffset = -1; // this will be -1 or a content offset 9872 // This will just return -1 if newlines are not significant. 9873 int32_t contentNewLineOffset = 9874 GetContentNewLineOffset(offset, cachedNewlineOffset); 9875 if (contentNewLineOffset < offset + length) { 9876 // The new line offset could be outside this frame if the frame has been 9877 // split by bidi resolution. In that case we won't use it in this reflow 9878 // (newLineOffset will remain -1), but we will still cache it in mContent 9879 newLineOffset = contentNewLineOffset; 9880 if (newLineOffset >= 0) { 9881 firstLetterLength = newLineOffset - offset; 9882 } 9883 } 9884 9885 if (contentNewLineOffset >= 0 && contentNewLineOffset < offset) { 9886 // We're in a first-letter frame's first in flow, so if there 9887 // was a first-letter, we'd be it. However, for one reason 9888 // or another (e.g., preformatted line break before this text), 9889 // we're not actually supposed to have first-letter style. So 9890 // just make a zero-length first-letter. 9891 firstLetterLength = 0; 9892 } else { 9893 // We only pass a language code to FindFirstLetterRange if it was 9894 // explicit in the content. 9895 const nsStyleFont* styleFont = StyleFont(); 9896 const nsAtom* lang = 9897 styleFont->mExplicitLanguage ? styleFont->mLanguage.get() : nullptr; 9898 FindFirstLetterRange(characterDataBuffer, lang, textRun, offset, iter, 9899 &firstLetterLength); 9900 if (newLineOffset >= 0) { 9901 // Don't allow a preformatted newline to be part of a first-letter. 9902 firstLetterLength = std::min(firstLetterLength, length - 1); 9903 } 9904 } 9905 if (firstLetterLength) { 9906 AddStateBits(TEXT_FIRST_LETTER); 9907 } 9908 9909 // Change this frame's length to the first-letter length right now 9910 // so that when we rebuild the textrun it will be built with the 9911 // right first-letter boundary. 9912 SetFirstLetterLength(firstLetterLength); 9913 } 9914 9915 static bool IsUnreflowedLetterFrame(nsIFrame* aFrame) { 9916 return aFrame->IsLetterFrame() && 9917 aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); 9918 } 9919 9920 // XXX Need to do something here to avoid incremental reflow bugs due to 9921 // first-line changing min-width 9922 /* virtual */ 9923 void nsTextFrame::AddInlineMinISize(const IntrinsicSizeInput& aInput, 9924 InlineMinISizeData* aData) { 9925 // Check if this textframe belongs to a first-letter frame that has not yet 9926 // been reflowed; if so, we need to deal with splitting off a continuation 9927 // before we can measure the advance correctly. 9928 if (IsUnreflowedLetterFrame(GetParent())) { 9929 MaybeSplitFramesForFirstLetter(); 9930 } 9931 9932 float inflation = nsLayoutUtils::FontSizeInflationFor(this); 9933 TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated; 9934 9935 if (trtype == eInflated && !IsCurrentFontInflation(inflation)) { 9936 // FIXME: Ideally, if we already have a text run, we'd move it to be 9937 // the uninflated text run. 9938 ClearTextRun(nullptr, nsTextFrame::eInflated); 9939 mFontMetrics = nullptr; 9940 } 9941 9942 nsTextFrame* f; 9943 const gfxTextRun* lastTextRun = nullptr; 9944 // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames 9945 // in the flow are handled right here. 9946 for (f = this; f; f = f->GetNextContinuation()) { 9947 // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we 9948 // haven't set up textruns yet for f. Except in OOM situations, 9949 // lastTextRun will only be null for the first text frame. 9950 if (f == this || f->GetTextRun(trtype) != lastTextRun) { 9951 nsIFrame* lc; 9952 if (aData->LineContainer() && 9953 aData->LineContainer() != (lc = f->FindLineContainer())) { 9954 NS_ASSERTION(f != this, 9955 "wrong InlineMinISizeData container" 9956 " for first continuation"); 9957 aData->mLine = nullptr; 9958 aData->SetLineContainer(lc); 9959 } 9960 9961 // This will process all the text frames that share the same textrun as f. 9962 f->AddInlineMinISizeForFlow(aInput.mContext, aData, trtype); 9963 lastTextRun = f->GetTextRun(trtype); 9964 } 9965 } 9966 } 9967 9968 // XXX this doesn't handle characters shaped by line endings. We need to 9969 // temporarily override the "current line ending" settings. 9970 void nsTextFrame::AddInlinePrefISizeForFlow(gfxContext* aRenderingContext, 9971 InlinePrefISizeData* aData, 9972 TextRunType aTextRunType) { 9973 if (IsUnreflowedLetterFrame(GetParent())) { 9974 MaybeSplitFramesForFirstLetter(); 9975 } 9976 9977 uint32_t flowEndInTextRun; 9978 gfxSkipCharsIterator iter = 9979 EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(), 9980 aData->LineContainer(), aData->mLine, &flowEndInTextRun); 9981 gfxTextRun* textRun = GetTextRun(aTextRunType); 9982 if (!textRun) { 9983 return; 9984 } 9985 9986 // Pass null for the line container. This will disable tab spacing, but that's 9987 // OK since we can't really handle tabs for intrinsic sizing anyway. 9988 9989 const nsStyleText* textStyle = StyleText(); 9990 const auto& characterDataBuffer = CharacterDataBuffer(); 9991 PropertyProvider provider(textRun, textStyle, characterDataBuffer, this, iter, 9992 INT32_MAX, nullptr, 0, aTextRunType, 9993 aData->mLineIsEmpty); 9994 9995 // text-combine-upright frame is constantly 1em on inline-axis, or zero if 9996 // there is a following sibling with the same style. 9997 if (Style()->IsTextCombined()) { 9998 if (!GetNextSibling() || GetNextSibling()->Style() != Style()) { 9999 aData->mCurrentLine += provider.GetFontMetrics()->EmHeight(); 10000 } 10001 aData->mTrailingWhitespace = 0; 10002 aData->mLineIsEmpty = false; 10003 return; 10004 } 10005 10006 bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant(); 10007 bool preformatNewlines = textStyle->NewlineIsSignificant(this); 10008 bool preformatTabs = textStyle->TabIsSignificant(); 10009 gfxFloat tabWidth = -1; 10010 uint32_t start = FindStartAfterSkippingWhitespace(&provider, aData, textStyle, 10011 &iter, flowEndInTextRun); 10012 if (aData->mLineIsEmpty) { 10013 provider.SetStartOfLine(iter); 10014 } 10015 10016 // XXX Should we consider hyphenation here? 10017 // If newlines and tabs aren't preformatted, nothing to do inside 10018 // the loop so make i skip to the end 10019 uint32_t loopStart = 10020 (preformatNewlines || preformatTabs) ? start : flowEndInTextRun; 10021 for (uint32_t i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) { 10022 bool preformattedNewline = false; 10023 bool preformattedTab = false; 10024 if (i < flowEndInTextRun) { 10025 // XXXldb Shouldn't we be including the newline as part of the 10026 // segment that it ends rather than part of the segment that it 10027 // starts? 10028 NS_ASSERTION(preformatNewlines || preformatTabs, 10029 "We can't be here unless newlines are " 10030 "hard breaks or there are tabs"); 10031 preformattedNewline = preformatNewlines && textRun->CharIsNewline(i); 10032 preformattedTab = preformatTabs && textRun->CharIsTab(i); 10033 if (!preformattedNewline && !preformattedTab) { 10034 // we needn't break here (and it's not the end of the flow) 10035 continue; 10036 } 10037 } 10038 10039 if (i > lineStart) { 10040 nscoord width = NSToCoordCeilClamped( 10041 textRun->GetAdvanceWidth(Range(lineStart, i), &provider)); 10042 width = std::max(0, width); 10043 aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width); 10044 aData->mLineIsEmpty = false; 10045 10046 if (collapseWhitespace) { 10047 uint32_t trimStart = GetEndOfTrimmedText(characterDataBuffer, textStyle, 10048 lineStart, i, &iter); 10049 if (trimStart == start) { 10050 // This is *all* trimmable whitespace, so whatever trailingWhitespace 10051 // we saw previously is still trailing... 10052 aData->mTrailingWhitespace += width; 10053 } else { 10054 // Some non-whitespace so the old trailingWhitespace is no longer 10055 // trailing 10056 nscoord wsWidth = NSToCoordCeilClamped( 10057 textRun->GetAdvanceWidth(Range(trimStart, i), &provider)); 10058 aData->mTrailingWhitespace = std::max(0, wsWidth); 10059 } 10060 } else { 10061 aData->mTrailingWhitespace = 0; 10062 } 10063 } 10064 10065 if (preformattedTab) { 10066 PropertyProvider::Spacing spacing; 10067 provider.GetSpacing(Range(i, i + 1), &spacing); 10068 aData->mCurrentLine += nscoord(spacing.mBefore); 10069 if (tabWidth < 0) { 10070 tabWidth = ComputeTabWidthAppUnits(this); 10071 } 10072 gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth, 10073 provider.MinTabAdvance()); 10074 aData->mCurrentLine = nscoord(afterTab + spacing.mAfter); 10075 aData->mLineIsEmpty = false; 10076 lineStart = i + 1; 10077 } else if (preformattedNewline) { 10078 aData->ForceBreak(); 10079 lineStart = i; 10080 } 10081 } 10082 10083 // Check if we have collapsible whitespace at the end 10084 if (start < flowEndInTextRun) { 10085 aData->mSkipWhitespace = IsTrimmableSpace( 10086 provider.GetCharacterDataBuffer(), 10087 iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle); 10088 } 10089 } 10090 10091 // XXX Need to do something here to avoid incremental reflow bugs due to 10092 // first-line and first-letter changing pref-width 10093 /* virtual */ 10094 void nsTextFrame::AddInlinePrefISize(const IntrinsicSizeInput& aInput, 10095 InlinePrefISizeData* aData) { 10096 float inflation = nsLayoutUtils::FontSizeInflationFor(this); 10097 TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated; 10098 10099 if (trtype == eInflated && !IsCurrentFontInflation(inflation)) { 10100 // FIXME: Ideally, if we already have a text run, we'd move it to be 10101 // the uninflated text run. 10102 ClearTextRun(nullptr, nsTextFrame::eInflated); 10103 mFontMetrics = nullptr; 10104 } 10105 10106 nsTextFrame* f; 10107 const gfxTextRun* lastTextRun = nullptr; 10108 // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames 10109 // in the flow are handled right here. 10110 for (f = this; f; f = f->GetNextContinuation()) { 10111 // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we 10112 // haven't set up textruns yet for f. Except in OOM situations, 10113 // lastTextRun will only be null for the first text frame. 10114 if (f == this || f->GetTextRun(trtype) != lastTextRun) { 10115 nsIFrame* lc; 10116 if (aData->LineContainer() && 10117 aData->LineContainer() != (lc = f->FindLineContainer())) { 10118 NS_ASSERTION(f != this, 10119 "wrong InlinePrefISizeData container" 10120 " for first continuation"); 10121 aData->mLine = nullptr; 10122 aData->SetLineContainer(lc); 10123 } 10124 10125 // This will process all the text frames that share the same textrun as f. 10126 f->AddInlinePrefISizeForFlow(aInput.mContext, aData, trtype); 10127 lastTextRun = f->GetTextRun(trtype); 10128 } 10129 } 10130 } 10131 10132 /* virtual */ 10133 nsIFrame::SizeComputationResult nsTextFrame::ComputeSize( 10134 const SizeComputationInput& aSizingInput, WritingMode aWM, 10135 const LogicalSize& aCBSize, nscoord aAvailableISize, 10136 const LogicalSize& aMargin, const LogicalSize& aBorderPadding, 10137 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { 10138 // Inlines and text don't compute size before reflow. 10139 return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE), 10140 AspectRatioUsage::None}; 10141 } 10142 10143 static nsRect RoundOut(const gfxRect& aRect) { 10144 nsRect r; 10145 r.x = NSToCoordFloor(aRect.X()); 10146 r.y = NSToCoordFloor(aRect.Y()); 10147 r.width = NSToCoordCeil(aRect.XMost()) - r.x; 10148 r.height = NSToCoordCeil(aRect.YMost()) - r.y; 10149 return r; 10150 } 10151 10152 nsRect nsTextFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const { 10153 if (Style()->HasTextDecorationLines() || HasAnyStateBits(TEXT_HYPHEN_BREAK)) { 10154 // This is conservative, but OK. 10155 return InkOverflowRect(); 10156 } 10157 10158 gfxSkipCharsIterator iter = 10159 const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated); 10160 if (!mTextRun) { 10161 return nsRect(); 10162 } 10163 10164 PropertyProvider provider(const_cast<nsTextFrame*>(this), iter, 10165 nsTextFrame::eInflated, mFontMetrics); 10166 // Trim trailing whitespace 10167 provider.InitializeForDisplay(true); 10168 10169 gfxTextRun::Metrics metrics = mTextRun->MeasureText( 10170 ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, 10171 aDrawTarget, &provider); 10172 if (GetWritingMode().IsLineInverted()) { 10173 metrics.mBoundingBox.y = -metrics.mBoundingBox.YMost(); 10174 } 10175 // mAscent should be the same as metrics.mAscent, but it's what we use to 10176 // paint so that's the one we'll use. 10177 nsRect boundingBox = RoundOut(metrics.mBoundingBox); 10178 boundingBox += nsPoint(0, mAscent); 10179 if (mTextRun->IsVertical()) { 10180 // Swap line-relative textMetrics dimensions to physical coordinates. 10181 std::swap(boundingBox.x, boundingBox.y); 10182 std::swap(boundingBox.width, boundingBox.height); 10183 } 10184 return boundingBox; 10185 } 10186 10187 /* virtual */ 10188 nsresult nsTextFrame::GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX, 10189 nscoord* aXMost) { 10190 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 10191 if (!mTextRun) { 10192 return NS_ERROR_FAILURE; 10193 } 10194 10195 PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics); 10196 provider.InitializeForMeasure(); 10197 10198 gfxTextRun::Metrics metrics = mTextRun->MeasureText( 10199 ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, 10200 aContext->GetDrawTarget(), &provider); 10201 // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency. 10202 *aX = NSToCoordFloor(metrics.mBoundingBox.x); 10203 *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost()); 10204 10205 return NS_OK; 10206 } 10207 10208 static bool HasSoftHyphenBefore(const CharacterDataBuffer& aBuffer, 10209 const gfxTextRun* aTextRun, 10210 int32_t aStartOffset, 10211 const gfxSkipCharsIterator& aIter) { 10212 if (aIter.GetSkippedOffset() < aTextRun->GetLength() && 10213 aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) { 10214 return true; 10215 } 10216 if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasShy)) { 10217 return false; 10218 } 10219 gfxSkipCharsIterator iter = aIter; 10220 while (iter.GetOriginalOffset() > aStartOffset) { 10221 iter.AdvanceOriginal(-1); 10222 if (!iter.IsOriginalCharSkipped()) { 10223 break; 10224 } 10225 if (aBuffer.CharAt(AssertedCast<uint32_t>(iter.GetOriginalOffset())) == 10226 CH_SHY) { 10227 return true; 10228 } 10229 } 10230 return false; 10231 } 10232 10233 /** 10234 * Removes all frames from aFrame up to (but not including) aFirstToNotRemove, 10235 * because their text has all been taken and reflowed by earlier frames. 10236 */ 10237 static void RemoveEmptyInFlows(nsTextFrame* aFrame, 10238 nsTextFrame* aFirstToNotRemove) { 10239 MOZ_ASSERT(aFrame != aFirstToNotRemove, "This will go very badly"); 10240 // We have to be careful here, because some RemoveFrame implementations 10241 // remove and destroy not only the passed-in frame but also all its following 10242 // in-flows (and sometimes all its following continuations in general). So 10243 // we remove |f| and everything up to but not including firstToNotRemove from 10244 // the flow first, to make sure that only the things we want destroyed are 10245 // destroyed. 10246 10247 // This sadly duplicates some of the logic from 10248 // nsSplittableFrame::RemoveFromFlow. We can get away with not duplicating 10249 // all of it, because we know that the prev-continuation links of 10250 // firstToNotRemove and f are fluid, and non-null. 10251 NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() == 10252 aFirstToNotRemove->GetPrevInFlow() && 10253 aFirstToNotRemove->GetPrevInFlow() != nullptr, 10254 "aFirstToNotRemove should have a fluid prev continuation"); 10255 NS_ASSERTION(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() && 10256 aFrame->GetPrevInFlow() != nullptr, 10257 "aFrame should have a fluid prev continuation"); 10258 10259 nsTextFrame* prevContinuation = aFrame->GetPrevContinuation(); 10260 nsTextFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation(); 10261 10262 for (nsTextFrame* f = aFrame; f != aFirstToNotRemove; 10263 f = f->GetNextContinuation()) { 10264 // f is going to be destroyed soon, after it is unlinked from the 10265 // continuation chain. If its textrun is going to be destroyed we need to 10266 // do it now, before we unlink the frames to remove from the flow, 10267 // because Destroy calls ClearTextRuns() and that will start at the 10268 // first frame with the text run and walk the continuations. 10269 if (f->IsInTextRunUserData()) { 10270 f->ClearTextRuns(); 10271 } else { 10272 f->DisconnectTextRuns(); 10273 } 10274 } 10275 10276 prevContinuation->SetNextInFlow(aFirstToNotRemove); 10277 aFirstToNotRemove->SetPrevInFlow(prevContinuation); 10278 10279 // **Note: it is important here that we clear the Next link from lastRemoved 10280 // BEFORE clearing the Prev link from aFrame, because SetPrevInFlow() will 10281 // follow the Next pointers, wiping out the cached mFirstContinuation field 10282 // from each following frame in the list. We need this to stop when it 10283 // reaches lastRemoved! 10284 lastRemoved->SetNextInFlow(nullptr); 10285 aFrame->SetPrevInFlow(nullptr); 10286 10287 nsContainerFrame* parent = aFrame->GetParent(); 10288 nsIFrame::DestroyContext context(aFrame->PresShell()); 10289 nsBlockFrame* parentBlock = do_QueryFrame(parent); 10290 if (parentBlock) { 10291 // Manually call DoRemoveFrame so we can tell it that we're 10292 // removing empty frames; this will keep it from blowing away 10293 // text runs. 10294 parentBlock->DoRemoveFrame(context, aFrame, nsBlockFrame::FRAMES_ARE_EMPTY); 10295 } else { 10296 // Just remove it normally; use FrameChildListID::NoReflowPrincipal to avoid 10297 // posting new reflows. 10298 parent->RemoveFrame(context, FrameChildListID::NoReflowPrincipal, aFrame); 10299 } 10300 } 10301 10302 void nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout, 10303 uint32_t aSetLengthFlags) { 10304 mContentLengthHint = aLength; 10305 int32_t end = GetContentOffset() + aLength; 10306 nsTextFrame* f = GetNextInFlow(); 10307 if (!f) { 10308 return; 10309 } 10310 10311 // If our end offset is moving, then even if frames are not being pushed or 10312 // pulled, content is moving to or from the next line and the next line 10313 // must be reflowed. 10314 // If the next-continuation is dirty, then we should dirty the next line now 10315 // because we may have skipped doing it if we dirtied it in 10316 // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow 10317 // and ChildIsDirty to handle a range of frames would be worse. 10318 if (aLineLayout && 10319 (end != f->mContentOffset || f->HasAnyStateBits(NS_FRAME_IS_DIRTY))) { 10320 aLineLayout->SetDirtyNextLine(); 10321 } 10322 10323 if (end < f->mContentOffset) { 10324 // Our frame is shrinking. Give the text to our next in flow. 10325 if (aLineLayout && HasSignificantTerminalNewline() && 10326 !GetParent()->IsLetterFrame() && 10327 (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) { 10328 // Whatever text we hand to our next-in-flow will end up in a frame all of 10329 // its own, since it ends in a forced linebreak. Might as well just put 10330 // it in a separate frame now. This is important to prevent text run 10331 // churn; if we did not do that, then we'd likely end up rebuilding 10332 // textruns for all our following continuations. 10333 // We skip this optimization when the parent is a first-letter frame 10334 // because it doesn't deal well with more than one child frame. 10335 // We also skip this optimization if we were called during bidi 10336 // resolution, so as not to create a new frame which doesn't appear in 10337 // the bidi resolver's list of frames 10338 nsIFrame* newFrame = 10339 PresShell()->FrameConstructor()->CreateContinuingFrame(this, 10340 GetParent()); 10341 nsTextFrame* next = static_cast<nsTextFrame*>(newFrame); 10342 GetParent()->InsertFrames(FrameChildListID::NoReflowPrincipal, this, 10343 aLineLayout->GetLine(), 10344 nsFrameList(next, next)); 10345 f = next; 10346 } 10347 10348 f->mContentOffset = end; 10349 if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) { 10350 ClearTextRuns(); 10351 f->ClearTextRuns(); 10352 } 10353 return; 10354 } 10355 // Our frame is growing. Take text from our in-flow(s). 10356 // We can take text from frames in lines beyond just the next line. 10357 // We don't dirty those lines. That's OK, because when we reflow 10358 // our empty next-in-flow, it will take text from its next-in-flow and 10359 // dirty that line. 10360 10361 // Note that in the process we may end up removing some frames from 10362 // the flow if they end up empty. 10363 nsTextFrame* framesToRemove = nullptr; 10364 while (f && f->mContentOffset < end) { 10365 f->mContentOffset = end; 10366 if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) { 10367 ClearTextRuns(); 10368 f->ClearTextRuns(); 10369 } 10370 nsTextFrame* next = f->GetNextInFlow(); 10371 // Note: the "f->GetNextSibling() == next" check below is to restrict 10372 // this optimization to the case where they are on the same child list. 10373 // Otherwise we might remove the only child of a nsFirstLetterFrame 10374 // for example and it can't handle that. See bug 597627 for details. 10375 if (next && next->mContentOffset <= end && f->GetNextSibling() == next && 10376 (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) { 10377 // |f| is now empty. We may as well remove it, instead of copying all 10378 // the text from |next| into it instead; the latter leads to use 10379 // rebuilding textruns for all following continuations. 10380 // We skip this optimization if we were called during bidi resolution, 10381 // since the bidi resolver may try to handle the destroyed frame later 10382 // and crash 10383 if (!framesToRemove) { 10384 // Remember that we have to remove this frame. 10385 framesToRemove = f; 10386 } 10387 } else if (framesToRemove) { 10388 RemoveEmptyInFlows(framesToRemove, f); 10389 framesToRemove = nullptr; 10390 } 10391 f = next; 10392 } 10393 10394 MOZ_ASSERT(!framesToRemove || (f && f->mContentOffset == end), 10395 "How did we exit the loop if we null out framesToRemove if " 10396 "!next || next->mContentOffset > end ?"); 10397 10398 if (framesToRemove) { 10399 // We are guaranteed that we exited the loop with f not null, per the 10400 // postcondition above 10401 RemoveEmptyInFlows(framesToRemove, f); 10402 } 10403 10404 #ifdef DEBUG 10405 f = this; 10406 int32_t iterations = 0; 10407 while (f && iterations < 10) { 10408 f->GetContentLength(); // Assert if negative length 10409 f = f->GetNextContinuation(); 10410 ++iterations; 10411 } 10412 f = this; 10413 iterations = 0; 10414 while (f && iterations < 10) { 10415 f->GetContentLength(); // Assert if negative length 10416 f = f->GetPrevContinuation(); 10417 ++iterations; 10418 } 10419 #endif 10420 } 10421 10422 void nsTextFrame::SetFirstLetterLength(int32_t aLength) { 10423 if (aLength == GetContentLength()) { 10424 return; 10425 } 10426 10427 mContentLengthHint = aLength; 10428 nsTextFrame* next = static_cast<nsTextFrame*>(GetNextInFlow()); 10429 if (!aLength && !next) { 10430 return; 10431 } 10432 10433 if (aLength > GetContentLength()) { 10434 // Stealing some text from our next-in-flow; this happens with floating 10435 // first-letter, which is initially given a zero-length range, with all 10436 // the text being in its continuation. 10437 if (!next) { 10438 MOZ_ASSERT_UNREACHABLE("Expected a next-in-flow; first-letter broken?"); 10439 return; 10440 } 10441 } else if (!next) { 10442 // We need to create a continuation for the parent first-letter frame, 10443 // and move any kids after this frame to the new one; if there are none, 10444 // a new continuing text frame will be created there. 10445 MOZ_ASSERT(GetParent()->IsLetterFrame()); 10446 auto* letterFrame = static_cast<nsFirstLetterFrame*>(GetParent()); 10447 next = letterFrame->CreateContinuationForFramesAfter(this); 10448 } 10449 10450 next->mContentOffset = GetContentOffset() + aLength; 10451 10452 ClearTextRuns(); 10453 } 10454 10455 bool nsTextFrame::IsFloatingFirstLetterChild() const { 10456 nsIFrame* frame = GetParent(); 10457 return frame && frame->IsFloating() && frame->IsLetterFrame(); 10458 } 10459 10460 bool nsTextFrame::IsInitialLetterChild() const { 10461 nsIFrame* frame = GetParent(); 10462 return frame && frame->StyleTextReset()->mInitialLetter.size != 0.0f && 10463 frame->IsLetterFrame(); 10464 } 10465 10466 struct nsTextFrame::NewlineProperty { 10467 int32_t mStartOffset; 10468 // The offset of the first \n after mStartOffset, or -1 if there is none 10469 int32_t mNewlineOffset; 10470 }; 10471 10472 int32_t nsTextFrame::GetContentNewLineOffset( 10473 int32_t aOffset, NewlineProperty*& aCachedNewlineOffset) { 10474 int32_t contentNewLineOffset = -1; // this will be -1 or a content offset 10475 if (StyleText()->NewlineIsSignificant(this)) { 10476 // Pointer to the nsGkAtoms::newline set on this frame's element 10477 aCachedNewlineOffset = mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY) 10478 ? static_cast<NewlineProperty*>( 10479 mContent->GetProperty(nsGkAtoms::newline)) 10480 : nullptr; 10481 if (aCachedNewlineOffset && aCachedNewlineOffset->mStartOffset <= aOffset && 10482 (aCachedNewlineOffset->mNewlineOffset == -1 || 10483 aCachedNewlineOffset->mNewlineOffset >= aOffset)) { 10484 contentNewLineOffset = aCachedNewlineOffset->mNewlineOffset; 10485 } else { 10486 contentNewLineOffset = 10487 FindChar(CharacterDataBuffer(), aOffset, 10488 GetContent()->TextLength() - aOffset, '\n'); 10489 } 10490 } 10491 10492 return contentNewLineOffset; 10493 } 10494 10495 void nsTextFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, 10496 const ReflowInput& aReflowInput, 10497 nsReflowStatus& aStatus) { 10498 MarkInReflow(); 10499 DO_GLOBAL_REFLOW_COUNT("nsTextFrame"); 10500 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 10501 10502 InvalidateSelectionState(); 10503 10504 // XXX If there's no line layout, we shouldn't even have created this 10505 // frame. This may happen if, for example, this is text inside a table 10506 // but not inside a cell. For now, just don't reflow. 10507 if (!aReflowInput.mLineLayout) { 10508 ClearMetrics(aMetrics); 10509 return; 10510 } 10511 10512 ReflowText(*aReflowInput.mLineLayout, aReflowInput.AvailableWidth(), 10513 aReflowInput.mRenderingContext->GetDrawTarget(), aMetrics, 10514 aStatus); 10515 } 10516 10517 #ifdef ACCESSIBILITY 10518 /** 10519 * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText. 10520 */ 10521 class MOZ_STACK_CLASS ReflowTextA11yNotifier { 10522 public: 10523 ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent) 10524 : mContent(aContent), mPresContext(aPresContext) {} 10525 ~ReflowTextA11yNotifier() { 10526 if (nsAccessibilityService* accService = GetAccService()) { 10527 accService->UpdateText(mPresContext->PresShell(), mContent); 10528 } 10529 } 10530 10531 private: 10532 ReflowTextA11yNotifier(); 10533 ReflowTextA11yNotifier(const ReflowTextA11yNotifier&); 10534 ReflowTextA11yNotifier& operator=(const ReflowTextA11yNotifier&); 10535 10536 nsIContent* mContent; 10537 nsPresContext* mPresContext; 10538 }; 10539 #endif 10540 10541 void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, 10542 DrawTarget* aDrawTarget, ReflowOutput& aMetrics, 10543 nsReflowStatus& aStatus) { 10544 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 10545 10546 #ifdef NOISY_REFLOW 10547 ListTag(stdout); 10548 printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth); 10549 #endif 10550 10551 nsPresContext* presContext = PresContext(); 10552 10553 #ifdef ACCESSIBILITY 10554 // Schedule the update of accessible tree since rendered text might be 10555 // changed. 10556 if (StyleVisibility()->IsVisible()) { 10557 ReflowTextA11yNotifier(presContext, mContent); 10558 } 10559 #endif 10560 10561 ///////////////////////////////////////////////////////////////////// 10562 // Set up flags and clear out state 10563 ///////////////////////////////////////////////////////////////////// 10564 10565 // Clear out the reflow input flags in mState. We also clear the whitespace 10566 // flags because this can change whether the frame maps whitespace-only text 10567 // or not. We also clear the flag that tracks whether we had a pending 10568 // reflow request from CharacterDataChanged (since we're reflowing now). 10569 RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS); 10570 mReflowRequestedForCharDataChange = false; 10571 RemoveProperty(WebRenderTextBounds()); 10572 10573 // Discard cached continuations array that will be invalidated by the reflow. 10574 if (nsTextFrame* first = FirstContinuation()) { 10575 first->ClearCachedContinuations(); 10576 } 10577 10578 // Temporarily map all possible content while we construct our new textrun. 10579 // so that when doing reflow our styles prevail over any part of the 10580 // textrun we look at. Note that next-in-flows may be mapping the same 10581 // content; gfxTextRun construction logic will ensure that we take priority. 10582 int32_t maxContentLength = GetInFlowContentLength(); 10583 10584 InvalidateSelectionState(); 10585 10586 // We don't need to reflow if there is no content. 10587 if (!maxContentLength) { 10588 ClearMetrics(aMetrics); 10589 return; 10590 } 10591 10592 #ifdef NOISY_BIDI 10593 printf("Reflowed textframe\n"); 10594 #endif 10595 10596 const nsStyleText* textStyle = StyleText(); 10597 10598 bool atStartOfLine = aLineLayout.LineAtStart(); 10599 if (atStartOfLine) { 10600 AddStateBits(TEXT_START_OF_LINE); 10601 } 10602 10603 uint32_t flowEndInTextRun; 10604 nsIFrame* lineContainer = aLineLayout.LineContainerFrame(); 10605 const auto& characterDataBuffer = CharacterDataBuffer(); 10606 10607 // DOM offsets of the text range we need to measure, after trimming 10608 // whitespace, restricting to first-letter, and restricting preformatted text 10609 // to nearest newline 10610 int32_t length = maxContentLength; 10611 int32_t offset = GetContentOffset(); 10612 10613 // Restrict preformatted text to the nearest newline 10614 NewlineProperty* cachedNewlineOffset = nullptr; 10615 int32_t newLineOffset = -1; // this will be -1 or a content offset 10616 // This will just return -1 if newlines are not significant. 10617 int32_t contentNewLineOffset = 10618 GetContentNewLineOffset(offset, cachedNewlineOffset); 10619 if (contentNewLineOffset < offset + length) { 10620 // The new line offset could be outside this frame if the frame has been 10621 // split by bidi resolution. In that case we won't use it in this reflow 10622 // (newLineOffset will remain -1), but we will still cache it in mContent 10623 newLineOffset = contentNewLineOffset; 10624 } 10625 if (newLineOffset >= 0) { 10626 length = newLineOffset + 1 - offset; 10627 } 10628 10629 if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) || 10630 HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) { 10631 // Skip leading whitespace. Make sure we don't skip a 'pre-line' 10632 // newline if there is one. 10633 int32_t skipLength = newLineOffset >= 0 ? length - 1 : length; 10634 int32_t whitespaceCount = 10635 GetTrimmableWhitespaceCount(characterDataBuffer, offset, skipLength, 1); 10636 if (whitespaceCount) { 10637 offset += whitespaceCount; 10638 length -= whitespaceCount; 10639 // Make sure this frame maps the trimmable whitespace. 10640 if (MOZ_UNLIKELY(offset > GetContentEnd())) { 10641 SetLength(offset - GetContentOffset(), &aLineLayout, 10642 ALLOW_FRAME_CREATION_AND_DESTRUCTION); 10643 } 10644 } 10645 } 10646 10647 // If trimming whitespace left us with nothing to do, return early. 10648 if (length == 0) { 10649 ClearMetrics(aMetrics); 10650 return; 10651 } 10652 10653 bool completedFirstLetter = false; 10654 // Layout dependent styles are a problem because we need to reconstruct 10655 // the gfxTextRun based on our layout. 10656 if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) { 10657 SetLength(maxContentLength, &aLineLayout, 10658 ALLOW_FRAME_CREATION_AND_DESTRUCTION); 10659 10660 if (aLineLayout.GetInFirstLetter()) { 10661 // floating first-letter boundaries are significant in textrun 10662 // construction, so clear the textrun out every time we hit a first-letter 10663 // and have changed our length (which controls the first-letter boundary) 10664 ClearTextRuns(); 10665 // Find the length of the first-letter. We need a textrun for this. 10666 // REVIEW: maybe-bogus inflation should be ok (fixed below) 10667 gfxSkipCharsIterator iter = 10668 EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer, 10669 aLineLayout.GetLine(), &flowEndInTextRun); 10670 10671 if (mTextRun) { 10672 int32_t firstLetterLength = length; 10673 if (aLineLayout.GetFirstLetterStyleOK()) { 10674 // We only pass a language code to FindFirstLetterRange if it was 10675 // explicit in the content. 10676 const nsStyleFont* styleFont = StyleFont(); 10677 const nsAtom* lang = styleFont->mExplicitLanguage 10678 ? styleFont->mLanguage.get() 10679 : nullptr; 10680 completedFirstLetter = 10681 FindFirstLetterRange(characterDataBuffer, lang, mTextRun, offset, 10682 iter, &firstLetterLength); 10683 if (newLineOffset >= 0) { 10684 // Don't allow a preformatted newline to be part of a first-letter. 10685 firstLetterLength = std::min(firstLetterLength, length - 1); 10686 if (length == 1) { 10687 // There is no text to be consumed by the first-letter before the 10688 // preformatted newline. Note that the first letter is therefore 10689 // complete (FindFirstLetterRange will have returned false). 10690 completedFirstLetter = true; 10691 } 10692 } 10693 } else { 10694 // We're in a first-letter frame's first in flow, so if there 10695 // was a first-letter, we'd be it. However, for one reason 10696 // or another (e.g., preformatted line break before this text), 10697 // we're not actually supposed to have first-letter style. So 10698 // just make a zero-length first-letter. 10699 firstLetterLength = 0; 10700 completedFirstLetter = true; 10701 } 10702 length = firstLetterLength; 10703 if (length) { 10704 AddStateBits(TEXT_FIRST_LETTER); 10705 } 10706 // Change this frame's length to the first-letter length right now 10707 // so that when we rebuild the textrun it will be built with the 10708 // right first-letter boundary 10709 SetLength(offset + length - GetContentOffset(), &aLineLayout, 10710 ALLOW_FRAME_CREATION_AND_DESTRUCTION); 10711 // Ensure that the textrun will be rebuilt 10712 ClearTextRuns(); 10713 } 10714 } 10715 } 10716 10717 float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this); 10718 10719 if (!IsCurrentFontInflation(fontSizeInflation)) { 10720 // FIXME: Ideally, if we already have a text run, we'd move it to be 10721 // the uninflated text run. 10722 ClearTextRun(nullptr, nsTextFrame::eInflated); 10723 mFontMetrics = nullptr; 10724 } 10725 10726 gfxSkipCharsIterator iter = 10727 EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer, 10728 aLineLayout.GetLine(), &flowEndInTextRun); 10729 10730 NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation), 10731 "EnsureTextRun should have set font size inflation"); 10732 10733 if (mTextRun && iter.GetOriginalEnd() < offset + length) { 10734 // The textrun does not map enough text for this frame. This can happen 10735 // when the textrun was ended in the middle of a text node because a 10736 // preformatted newline was encountered, and prev-in-flow frames have 10737 // consumed all the text of the textrun. We need a new textrun. 10738 ClearTextRuns(); 10739 iter = EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer, 10740 aLineLayout.GetLine(), &flowEndInTextRun); 10741 } 10742 10743 if (!mTextRun) { 10744 ClearMetrics(aMetrics); 10745 return; 10746 } 10747 10748 NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped( 10749 offset + length) <= mTextRun->GetLength(), 10750 "Text run does not map enough text for our reflow"); 10751 10752 ///////////////////////////////////////////////////////////////////// 10753 // See how much text should belong to this text frame, and measure it 10754 ///////////////////////////////////////////////////////////////////// 10755 10756 iter.SetOriginalOffset(offset); 10757 nscoord xOffsetForTabs = 10758 (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab) 10759 ? (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() - 10760 lineContainer->GetUsedBorderAndPadding().left) 10761 : -1; 10762 PropertyProvider provider(mTextRun, textStyle, characterDataBuffer, this, 10763 iter, length, lineContainer, xOffsetForTabs, 10764 nsTextFrame::eInflated, 10765 HasAnyStateBits(TEXT_START_OF_LINE)); 10766 10767 uint32_t transformedOffset = provider.GetStart().GetSkippedOffset(); 10768 10769 gfxFont::BoundingBoxType boundingBoxType = gfxFont::LOOSE_INK_EXTENTS; 10770 if (IsFloatingFirstLetterChild() || IsInitialLetterChild()) { 10771 if (nsFirstLetterFrame* firstLetter = do_QueryFrame(GetParent())) { 10772 if (firstLetter->UseTightBounds()) { 10773 boundingBoxType = gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS; 10774 } 10775 } 10776 } 10777 10778 int32_t limitLength = length; 10779 int32_t forceBreak = aLineLayout.GetForcedBreakPosition(this); 10780 bool forceBreakAfter = false; 10781 if (forceBreak >= length) { 10782 forceBreakAfter = forceBreak == length; 10783 // The break is not within the text considered for this textframe. 10784 forceBreak = -1; 10785 } 10786 if (forceBreak >= 0) { 10787 limitLength = forceBreak; 10788 } 10789 // This is the heart of text reflow right here! We don't know where 10790 // to break, so we need to see how much text fits in the available width. 10791 uint32_t transformedLength; 10792 if (offset + limitLength >= int32_t(characterDataBuffer.GetLength())) { 10793 NS_ASSERTION( 10794 offset + limitLength == int32_t(characterDataBuffer.GetLength()), 10795 "Content offset/length out of bounds"); 10796 NS_ASSERTION(flowEndInTextRun >= transformedOffset, 10797 "Negative flow length?"); 10798 transformedLength = flowEndInTextRun - transformedOffset; 10799 } else { 10800 // we're not looking at all the content, so we need to compute the 10801 // length of the transformed substring we're looking at 10802 gfxSkipCharsIterator iter(provider.GetStart()); 10803 iter.SetOriginalOffset(offset + limitLength); 10804 transformedLength = iter.GetSkippedOffset() - transformedOffset; 10805 } 10806 gfxTextRun::Metrics textMetrics; 10807 uint32_t transformedLastBreak = 0; 10808 bool usedHyphenation = false; 10809 gfxTextRun::TrimmableWS trimmableWS; 10810 gfxFloat availWidth = aAvailableWidth; 10811 if (Style()->IsTextCombined()) { 10812 // If text-combine-upright is 'all', we would compress whatever long 10813 // text into ~1em width, so there is no limited on the avail width. 10814 availWidth = std::numeric_limits<gfxFloat>::infinity(); 10815 } 10816 bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() || 10817 HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML); 10818 bool isBreakSpaces = 10819 textStyle->mWhiteSpaceCollapse == StyleWhiteSpaceCollapse::BreakSpaces; 10820 // allow whitespace to overflow the container 10821 bool whitespaceCanHang = textStyle->WhiteSpaceCanHangOrVisuallyCollapse(); 10822 gfxBreakPriority breakPriority = aLineLayout.LastOptionalBreakPriority(); 10823 gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak; 10824 bool shouldSuppressLineBreak = ShouldSuppressLineBreak(); 10825 if (shouldSuppressLineBreak) { 10826 suppressBreak = gfxTextRun::eSuppressAllBreaks; 10827 } else if (!aLineLayout.LineIsBreakable()) { 10828 suppressBreak = gfxTextRun::eSuppressInitialBreak; 10829 } 10830 uint32_t transformedCharsFit = mTextRun->BreakAndMeasureText( 10831 transformedOffset, transformedLength, HasAnyStateBits(TEXT_START_OF_LINE), 10832 availWidth, provider, suppressBreak, boundingBoxType, aDrawTarget, 10833 textStyle->WordCanWrap(this), textStyle->WhiteSpaceCanWrap(this), 10834 isBreakSpaces, 10835 // The following are output parameters: 10836 canTrimTrailingWhitespace || whitespaceCanHang ? &trimmableWS : nullptr, 10837 textMetrics, usedHyphenation, transformedLastBreak, 10838 // In/out 10839 breakPriority); 10840 if (!length && !textMetrics.mAscent && !textMetrics.mDescent) { 10841 // If we're measuring a zero-length piece of text, update 10842 // the height manually. 10843 nsFontMetrics* fm = provider.GetFontMetrics(); 10844 if (fm) { 10845 textMetrics.mAscent = gfxFloat(fm->MaxAscent()); 10846 textMetrics.mDescent = gfxFloat(fm->MaxDescent()); 10847 } 10848 } 10849 if (GetWritingMode().IsLineInverted()) { 10850 std::swap(textMetrics.mAscent, textMetrics.mDescent); 10851 textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost(); 10852 } 10853 // The "end" iterator points to the first character after the string mapped 10854 // by this frame. Basically, its original-string offset is offset+charsFit 10855 // after we've computed charsFit. 10856 gfxSkipCharsIterator end(provider.GetEndHint()); 10857 end.SetSkippedOffset(transformedOffset + transformedCharsFit); 10858 int32_t charsFit = end.GetOriginalOffset() - offset; 10859 if (offset + charsFit == newLineOffset) { 10860 // We broke before a trailing preformatted '\n'. The newline should 10861 // be assigned to this frame. Note that newLineOffset will be -1 if 10862 // there was no preformatted newline, so we wouldn't get here in that 10863 // case. 10864 ++charsFit; 10865 } 10866 // That might have taken us beyond our assigned content range (because 10867 // we might have advanced over some skipped chars that extend outside 10868 // this frame), so get back in. 10869 int32_t lastBreak = -1; 10870 if (charsFit >= limitLength) { 10871 charsFit = limitLength; 10872 if (transformedLastBreak != UINT32_MAX) { 10873 // lastBreak is needed. 10874 // This may set lastBreak greater than 'length', but that's OK 10875 lastBreak = end.ConvertSkippedToOriginal(transformedOffset + 10876 transformedLastBreak); 10877 } 10878 end.SetOriginalOffset(offset + charsFit); 10879 // If we were forced to fit, and the break position is after a soft hyphen, 10880 // note that this is a hyphenation break. 10881 if ((forceBreak >= 0 || forceBreakAfter) && 10882 HasSoftHyphenBefore(characterDataBuffer, mTextRun, offset, end)) { 10883 usedHyphenation = true; 10884 } 10885 } 10886 if (usedHyphenation) { 10887 // Fix up metrics to include hyphen 10888 AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &textMetrics, 10889 boundingBoxType, aDrawTarget); 10890 AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS); 10891 } 10892 if (textMetrics.mBoundingBox.IsEmpty()) { 10893 AddStateBits(TEXT_NO_RENDERED_GLYPHS); 10894 } 10895 10896 bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength; 10897 if (trimmableWS.mAdvance > 0.0) { 10898 if (canTrimTrailingWhitespace) { 10899 // Optimization: if we we can be sure this frame will be at end of line, 10900 // then trim the whitespace now. 10901 if (brokeText || HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) { 10902 // We're definitely going to break so our trailing whitespace should 10903 // definitely be trimmed. Record that we've already done it. 10904 AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE); 10905 textMetrics.mAdvanceWidth -= trimmableWS.mAdvance; 10906 trimmableWS.mAdvance = 0.0; 10907 } 10908 ClearHangableISize(); 10909 ClearTrimmableWS(); 10910 } else if (whitespaceCanHang) { 10911 // Figure out how much whitespace will hang if at end-of-line. 10912 gfxFloat hang = 10913 std::min(std::max(0.0, textMetrics.mAdvanceWidth - availWidth), 10914 gfxFloat(trimmableWS.mAdvance)); 10915 SetHangableISize(NSToCoordRound(trimmableWS.mAdvance - hang)); 10916 // nsLineLayout only needs the TrimmableWS property if justifying, so 10917 // check whether this is relevant. 10918 if (textStyle->mTextAlign == StyleTextAlign::Justify || 10919 textStyle->mTextAlignLast == StyleTextAlignLast::Justify) { 10920 SetTrimmableWS(trimmableWS); 10921 } 10922 textMetrics.mAdvanceWidth -= hang; 10923 trimmableWS.mAdvance = 0.0; 10924 } else { 10925 MOZ_ASSERT_UNREACHABLE("How did trimmableWS get set?!"); 10926 ClearHangableISize(); 10927 ClearTrimmableWS(); 10928 trimmableWS.mAdvance = 0.0; 10929 } 10930 } else { 10931 // Remove any stale frame properties. 10932 ClearHangableISize(); 10933 ClearTrimmableWS(); 10934 } 10935 10936 if (!brokeText && lastBreak >= 0) { 10937 // Since everything fit and no break was forced, 10938 // record the last break opportunity 10939 NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth, 10940 "If the text doesn't fit, and we have a break opportunity, " 10941 "why didn't MeasureText use it?"); 10942 MOZ_ASSERT(lastBreak >= offset, "Strange break position"); 10943 aLineLayout.NotifyOptionalBreakPosition(this, lastBreak - offset, true, 10944 breakPriority); 10945 } 10946 10947 int32_t contentLength = offset + charsFit - GetContentOffset(); 10948 10949 ///////////////////////////////////////////////////////////////////// 10950 // Compute output metrics 10951 ///////////////////////////////////////////////////////////////////// 10952 10953 // first-letter frames should use the tight bounding box metrics for 10954 // ascent/descent for good drop-cap effects 10955 if (HasAnyStateBits(TEXT_FIRST_LETTER)) { 10956 textMetrics.mAscent = 10957 std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y()); 10958 textMetrics.mDescent = 10959 std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost()); 10960 } 10961 10962 // Setup metrics for caller 10963 // Disallow negative widths 10964 WritingMode wm = GetWritingMode(); 10965 LogicalSize finalSize(wm); 10966 finalSize.ISize(wm) = 10967 NSToCoordCeilClamped(std::max(gfxFloat(0.0), textMetrics.mAdvanceWidth)); 10968 10969 nscoord fontBaseline; 10970 // Note(dshin): Baseline should tecnhically be halfway through the em box for 10971 // a central baseline. It is simply half of the text run block size so that it 10972 // can be easily calculated in `GetNaturalBaselineBOffset`. 10973 if (transformedCharsFit == 0 && !usedHyphenation) { 10974 aMetrics.SetBlockStartAscent(0); 10975 finalSize.BSize(wm) = 0; 10976 fontBaseline = 0; 10977 } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) { 10978 fontBaseline = NSToCoordCeil(textMetrics.mAscent); 10979 const auto size = fontBaseline + NSToCoordCeil(textMetrics.mDescent); 10980 // Use actual text metrics for floating first letter frame. 10981 aMetrics.SetBlockStartAscent(wm.IsAlphabeticalBaseline() ? fontBaseline 10982 : size / 2); 10983 finalSize.BSize(wm) = size; 10984 } else { 10985 // Otherwise, ascent should contain the overline drawable area. 10986 // And also descent should contain the underline drawable area. 10987 // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them. 10988 nsFontMetrics* fm = provider.GetFontMetrics(); 10989 nscoord fontAscent = 10990 wm.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent(); 10991 nscoord fontDescent = 10992 wm.IsLineInverted() ? fm->MaxAscent() : fm->MaxDescent(); 10993 fontBaseline = std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent); 10994 const auto size = 10995 fontBaseline + 10996 std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent); 10997 aMetrics.SetBlockStartAscent(wm.IsAlphabeticalBaseline() ? fontBaseline 10998 : size / 2); 10999 finalSize.BSize(wm) = size; 11000 } 11001 if (Style()->IsTextCombined()) { 11002 nsFontMetrics* fm = provider.GetFontMetrics(); 11003 nscoord width = finalSize.ISize(wm); 11004 nscoord em = fm->EmHeight(); 11005 // Compress the characters in horizontal axis if necessary. 11006 auto* data = GetProperty(TextCombineDataProperty()); 11007 if (!data) { 11008 data = new TextCombineData; 11009 SetProperty(TextCombineDataProperty(), data); 11010 } 11011 data->mNaturalWidth = width; 11012 finalSize.ISize(wm) = em; 11013 // If we're going to have to adjust the block-size to make it 1em, make an 11014 // appropriate adjustment to the font baseline (determined by the ascent 11015 // returned in aMetrics) 11016 if (finalSize.BSize(wm) != em) { 11017 fontBaseline = 11018 aMetrics.BlockStartAscent() + (em - finalSize.BSize(wm)) / 2; 11019 aMetrics.SetBlockStartAscent(fontBaseline); 11020 } 11021 // If we have a next-in-flow with the same style, remove our block-size 11022 // so that they end up beside each other; only the last in the series 11023 // gets to keep its block-axis size. 11024 if (GetNextSibling() && GetNextSibling()->Style() == Style()) { 11025 finalSize.BSize(wm) = 0; 11026 } else { 11027 // Make the characters be in an 1em square. 11028 finalSize.BSize(wm) = em; 11029 } 11030 // If there are earlier sibling frames with the same style, and we have 11031 // reached the end of the run of same-style siblings, recompute the scale 11032 // as necessary to make them all fit. (This is a bit inefficient, but is 11033 // such a rare case that we don't much care.) 11034 nsIFrame* f = GetPrevSibling(); 11035 if (f && f->Style() == Style() && 11036 (!GetNextSibling() || GetNextSibling()->Style() != Style())) { 11037 // Collect the total "natural" width of the text. 11038 while (f->Style() == Style()) { 11039 if (auto* fData = f->GetProperty(TextCombineDataProperty())) { 11040 width += fData->mNaturalWidth; 11041 } 11042 if (!f->GetPrevSibling()) { 11043 break; 11044 } 11045 f = f->GetPrevSibling(); 11046 } 11047 } else { 11048 // Not at the end of a run of frames; we're just going to handle `this`. 11049 f = this; 11050 } 11051 // Figure out scaling to apply to this frame (or to the run of same-style 11052 // sibling frames), and the starting offset within the em square. 11053 float scale; 11054 nscoord offset; 11055 if (width > em) { 11056 scale = static_cast<float>(em) / width; 11057 offset = 0; 11058 } else { 11059 scale = 1.0f; 11060 offset = (em - width) / 2; 11061 } 11062 while (true) { 11063 if (auto* fData = f->GetProperty(TextCombineDataProperty())) { 11064 fData->mScale = scale; 11065 fData->mOffset = offset; 11066 offset += fData->mNaturalWidth * scale; 11067 } 11068 if (f == this) { 11069 break; 11070 } 11071 f = f->GetNextSibling(); 11072 } 11073 } 11074 aMetrics.SetSize(wm, finalSize); 11075 11076 NS_ASSERTION(aMetrics.BlockStartAscent() >= 0, "Negative ascent???"); 11077 // The effective "descent" here will be negative in the case of a frame that 11078 // is participating in text-combine-upright, but is not the last frame within 11079 // the combined upright cell, because we zero out its block-size (see above). 11080 // So this creates an exception to the non-negative assertion here. 11081 DebugOnly<nscoord> descent = 11082 (Style()->IsTextCombined() ? aMetrics.ISize(aMetrics.GetWritingMode()) 11083 : aMetrics.BSize(aMetrics.GetWritingMode())) - 11084 aMetrics.BlockStartAscent(); 11085 NS_ASSERTION(descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() && 11086 GetNextSibling()->Style() == Style()), 11087 "Unexpected negative descent???"); 11088 11089 mAscent = fontBaseline; 11090 11091 // Handle text that runs outside its normal bounds. 11092 nsRect boundingBox = RoundOut(textMetrics.mBoundingBox); 11093 if (mTextRun->IsVertical()) { 11094 // Swap line-relative textMetrics dimensions to physical coordinates. 11095 std::swap(boundingBox.x, boundingBox.y); 11096 std::swap(boundingBox.width, boundingBox.height); 11097 if (GetWritingMode().IsVerticalRL()) { 11098 boundingBox.x = -boundingBox.XMost(); 11099 boundingBox.x += aMetrics.Width() - mAscent; 11100 } else { 11101 boundingBox.x += mAscent; 11102 } 11103 } else { 11104 boundingBox.y += mAscent; 11105 } 11106 aMetrics.SetOverflowAreasToDesiredBounds(); 11107 aMetrics.InkOverflow().UnionRect(aMetrics.InkOverflow(), boundingBox); 11108 11109 // When we have text decorations, we don't need to compute their overflow now 11110 // because we're guaranteed to do it later 11111 // (see nsLineLayout::RelativePositionFrames) 11112 UnionAdditionalOverflow(presContext, aLineLayout.LineContainerFrame(), 11113 provider, &aMetrics.InkOverflow(), false, true); 11114 11115 ///////////////////////////////////////////////////////////////////// 11116 // Clean up, update state 11117 ///////////////////////////////////////////////////////////////////// 11118 11119 // If all our characters are discarded or collapsed, then trimmable width 11120 // from the last textframe should be preserved. Otherwise the trimmable width 11121 // from this textframe overrides. (Currently in CSS trimmable width can be 11122 // at most one space so there's no way for trimmable width from a previous 11123 // frame to accumulate with trimmable width from this frame.) 11124 if (transformedCharsFit > 0) { 11125 aLineLayout.SetTrimmableISize(NSToCoordFloor(trimmableWS.mAdvance)); 11126 AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS); 11127 } 11128 bool breakAfter = forceBreakAfter; 11129 if (!shouldSuppressLineBreak) { 11130 if (charsFit > 0 && charsFit == length && 11131 textStyle->mHyphens != StyleHyphens::None && 11132 HasSoftHyphenBefore(characterDataBuffer, mTextRun, offset, end)) { 11133 bool fits = 11134 textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth; 11135 // Record a potential break after final soft hyphen 11136 aLineLayout.NotifyOptionalBreakPosition(this, length, fits, 11137 gfxBreakPriority::eNormalBreak); 11138 } 11139 // length == 0 means either the text is empty or it's all collapsed away 11140 bool emptyTextAtStartOfLine = atStartOfLine && length == 0; 11141 if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine && 11142 transformedOffset + transformedLength == mTextRun->GetLength() && 11143 (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTrailingBreak)) { 11144 // We placed all the text in the textrun and we have a break opportunity 11145 // at the end of the textrun. We need to record it because the following 11146 // content may not care about nsLineBreaker. 11147 11148 // Note that because we didn't break, we can be sure that (thanks to the 11149 // code up above) textMetrics.mAdvanceWidth includes the width of any 11150 // trailing whitespace. So we need to subtract trimmableWidth here 11151 // because if we did break at this point, that much width would be 11152 // trimmed. 11153 if (textMetrics.mAdvanceWidth - trimmableWS.mAdvance > availWidth) { 11154 breakAfter = true; 11155 } else { 11156 aLineLayout.NotifyOptionalBreakPosition(this, length, true, 11157 gfxBreakPriority::eNormalBreak); 11158 } 11159 } 11160 } 11161 11162 // Compute reflow status 11163 if (contentLength != maxContentLength) { 11164 aStatus.SetIncomplete(); 11165 } 11166 11167 if (charsFit == 0 && length > 0 && !usedHyphenation) { 11168 // Couldn't place any text 11169 aStatus.SetInlineLineBreakBeforeAndReset(); 11170 } else if (contentLength > 0 && 11171 mContentOffset + contentLength - 1 == newLineOffset) { 11172 // Ends in \n 11173 aStatus.SetInlineLineBreakAfter(); 11174 aLineLayout.SetLineEndsInBR(true); 11175 } else if (breakAfter) { 11176 aStatus.SetInlineLineBreakAfter(); 11177 } 11178 if (completedFirstLetter) { 11179 aLineLayout.SetFirstLetterStyleOK(false); 11180 aStatus.SetFirstLetterComplete(); 11181 } 11182 if (brokeText && breakPriority == gfxBreakPriority::eWordWrapBreak) { 11183 aLineLayout.SetUsedOverflowWrap(); 11184 } 11185 11186 // Updated the cached NewlineProperty, or delete it. 11187 if (contentLength < maxContentLength && 11188 textStyle->NewlineIsSignificant(this) && 11189 (contentNewLineOffset < 0 || 11190 mContentOffset + contentLength <= contentNewLineOffset)) { 11191 if (!cachedNewlineOffset) { 11192 cachedNewlineOffset = new NewlineProperty; 11193 if (NS_FAILED(mContent->SetProperty( 11194 nsGkAtoms::newline, cachedNewlineOffset, 11195 nsINode::DeleteProperty<NewlineProperty>))) { 11196 delete cachedNewlineOffset; 11197 cachedNewlineOffset = nullptr; 11198 } 11199 mContent->SetFlags(NS_HAS_NEWLINE_PROPERTY); 11200 } 11201 if (cachedNewlineOffset) { 11202 cachedNewlineOffset->mStartOffset = offset; 11203 cachedNewlineOffset->mNewlineOffset = contentNewLineOffset; 11204 } 11205 } else if (cachedNewlineOffset) { 11206 mContent->RemoveProperty(nsGkAtoms::newline); 11207 mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY); 11208 } 11209 11210 // Compute space and letter counts for justification, if required 11211 if ((lineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify || 11212 lineContainer->StyleText()->mTextAlignLast == 11213 StyleTextAlignLast::Justify || 11214 shouldSuppressLineBreak) && 11215 !lineContainer->IsInSVGTextSubtree()) { 11216 AddStateBits(TEXT_JUSTIFICATION_ENABLED); 11217 Range range(uint32_t(offset), uint32_t(offset + charsFit)); 11218 aLineLayout.SetJustificationInfo(provider.ComputeJustification(range)); 11219 } 11220 11221 SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION); 11222 11223 InvalidateFrame(); 11224 11225 #ifdef NOISY_REFLOW 11226 ListTag(stdout); 11227 printf(": desiredSize=%d,%d(b=%d) status=%x\n", aMetrics.Width(), 11228 aMetrics.Height(), aMetrics.BlockStartAscent(), aStatus); 11229 #endif 11230 } 11231 11232 /* virtual */ 11233 bool nsTextFrame::CanContinueTextRun() const { 11234 // We can continue a text run through a text frame 11235 return true; 11236 } 11237 11238 nsTextFrame::TrimOutput nsTextFrame::TrimTrailingWhiteSpace( 11239 DrawTarget* aDrawTarget) { 11240 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW), 11241 "frame should have been reflowed"); 11242 11243 TrimOutput result; 11244 result.mChanged = false; 11245 result.mDeltaWidth = 0; 11246 11247 AddStateBits(TEXT_END_OF_LINE); 11248 11249 if (!GetTextRun(nsTextFrame::eInflated)) { 11250 // If reflow didn't create a textrun, there must have been no content once 11251 // leading whitespace was trimmed, so nothing more to do here. 11252 return result; 11253 } 11254 11255 int32_t contentLength = GetContentLength(); 11256 if (!contentLength) { 11257 return result; 11258 } 11259 11260 gfxSkipCharsIterator start = 11261 EnsureTextRun(nsTextFrame::eInflated, aDrawTarget); 11262 NS_ENSURE_TRUE(mTextRun, result); 11263 11264 uint32_t trimmedStart = start.GetSkippedOffset(); 11265 11266 const auto& characterDataBuffer = CharacterDataBuffer(); 11267 TrimmedOffsets trimmed = GetTrimmedOffsets(characterDataBuffer); 11268 gfxSkipCharsIterator trimmedEndIter = start; 11269 const nsStyleText* textStyle = StyleText(); 11270 gfxFloat delta = 0; 11271 uint32_t trimmedEnd = 11272 trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd()); 11273 11274 if (!HasAnyStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE) && 11275 trimmed.GetEnd() < GetContentEnd()) { 11276 gfxSkipCharsIterator end = trimmedEndIter; 11277 uint32_t endOffset = 11278 end.ConvertOriginalToSkipped(GetContentOffset() + contentLength); 11279 if (trimmedEnd < endOffset) { 11280 // We can't be dealing with tabs here ... they wouldn't be trimmed. So 11281 // it's OK to pass null for the line container. 11282 PropertyProvider provider(mTextRun, textStyle, characterDataBuffer, this, 11283 start, contentLength, nullptr, 0, 11284 nsTextFrame::eInflated, 11285 HasAnyStateBits(TEXT_START_OF_LINE)); 11286 delta = 11287 mTextRun->GetAdvanceWidth(Range(trimmedEnd, endOffset), &provider); 11288 result.mChanged = true; 11289 } 11290 } 11291 11292 gfxFloat advanceDelta; 11293 mTextRun->SetLineBreaks(Range(trimmedStart, trimmedEnd), 11294 HasAnyStateBits(TEXT_START_OF_LINE), true, 11295 &advanceDelta); 11296 if (advanceDelta != 0) { 11297 result.mChanged = true; 11298 } 11299 11300 // aDeltaWidth is *subtracted* from our width. 11301 // If advanceDelta is positive then setting the line break made us longer, 11302 // so aDeltaWidth could go negative. 11303 result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta); 11304 // If aDeltaWidth goes negative, that means this frame might not actually fit 11305 // anymore!!! We need higher level line layout to recover somehow. 11306 // If it's because the frame has a soft hyphen that is now being displayed, 11307 // this should actually be OK, because our reflow recorded the break 11308 // opportunity that allowed the soft hyphen to be used, and we wouldn't 11309 // have recorded the opportunity unless the hyphen fit (or was the first 11310 // opportunity on the line). 11311 // Otherwise this can/ really only happen when we have glyphs with special 11312 // shapes at the end of lines, I think. Breaking inside a kerning pair won't 11313 // do it because that would mean we broke inside this textrun, and 11314 // BreakAndMeasureText should make sure the resulting shaped substring fits. 11315 // Maybe if we passed a maxTextLength? But that only happens at direction 11316 // changes (so we wouldn't kern across the boundary) or for first-letter 11317 // (which always fits because it starts the line!). 11318 NS_WARNING_ASSERTION(result.mDeltaWidth >= 0, 11319 "Negative deltawidth, something odd is happening"); 11320 11321 #ifdef NOISY_TRIM 11322 ListTag(stdout); 11323 printf(": trim => %d\n", result.mDeltaWidth); 11324 #endif 11325 return result; 11326 } 11327 11328 OverflowAreas nsTextFrame::RecomputeOverflow(nsIFrame* aBlockFrame, 11329 bool aIncludeShadows) { 11330 RemoveProperty(WebRenderTextBounds()); 11331 11332 nsRect bounds(nsPoint(0, 0), GetSize()); 11333 OverflowAreas result(bounds, bounds); 11334 11335 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 11336 if (!mTextRun) { 11337 return result; 11338 } 11339 11340 PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics); 11341 // Don't trim trailing space, in case we need to paint it as selected. 11342 provider.InitializeForDisplay(false); 11343 11344 gfxTextRun::Metrics textMetrics = 11345 mTextRun->MeasureText(ComputeTransformedRange(provider), 11346 gfxFont::LOOSE_INK_EXTENTS, nullptr, &provider); 11347 if (GetWritingMode().IsLineInverted()) { 11348 textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost(); 11349 } 11350 nsRect boundingBox = RoundOut(textMetrics.mBoundingBox); 11351 boundingBox += nsPoint(0, mAscent); 11352 if (mTextRun->IsVertical()) { 11353 // Swap line-relative textMetrics dimensions to physical coordinates. 11354 std::swap(boundingBox.x, boundingBox.y); 11355 std::swap(boundingBox.width, boundingBox.height); 11356 } 11357 nsRect& vis = result.InkOverflow(); 11358 vis.UnionRect(vis, boundingBox); 11359 UnionAdditionalOverflow(PresContext(), aBlockFrame, provider, &vis, true, 11360 aIncludeShadows); 11361 return result; 11362 } 11363 11364 static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle, 11365 const gfxTextRun* aTextRun, uint32_t aSkippedOffset, 11366 const CharacterDataBuffer& aBuffer, 11367 int32_t aBufferOffset, int32_t aBufferLen, 11368 nsAString& aOut) { 11369 nsAutoString fragString; 11370 char16_t* out; 11371 bool needsToMaskPassword = NeedsToMaskPassword(aFrame); 11372 if (aStyle->mTextTransform.IsNone() && !needsToMaskPassword && 11373 aStyle->mWebkitTextSecurity == StyleTextSecurity::None) { 11374 // No text-transform, so we can copy directly to the output string. 11375 aOut.SetLength(aOut.Length() + aBufferLen); 11376 out = aOut.EndWriting() - aBufferLen; 11377 } else { 11378 // Use a temporary string as source for the transform. 11379 fragString.SetLength(aBufferLen); 11380 out = fragString.BeginWriting(); 11381 } 11382 11383 // Copy the text, with \n and \t replaced by <space> if appropriate. 11384 MOZ_ASSERT(aBufferOffset >= 0); 11385 for (uint32_t i = 0; i < static_cast<uint32_t>(aBufferLen); ++i) { 11386 char16_t ch = aBuffer.CharAt(static_cast<uint32_t>(aBufferOffset) + i); 11387 if ((ch == '\n' && !aStyle->NewlineIsSignificant(aFrame)) || 11388 (ch == '\t' && !aStyle->TabIsSignificant())) { 11389 ch = ' '; 11390 } 11391 out[i] = ch; 11392 } 11393 11394 if (!aStyle->mTextTransform.IsNone() || needsToMaskPassword || 11395 aStyle->mWebkitTextSecurity != StyleTextSecurity::None) { 11396 MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed); 11397 if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) { 11398 // Apply text-transform according to style in the transformed run. 11399 char16_t maskChar = 11400 needsToMaskPassword ? 0 : aStyle->TextSecurityMaskChar(); 11401 auto transformedTextRun = 11402 static_cast<const nsTransformedTextRun*>(aTextRun); 11403 nsAutoString convertedString; 11404 AutoTArray<bool, 50> charsToMergeArray; 11405 AutoTArray<bool, 50> deletedCharsArray; 11406 nsCaseTransformTextRunFactory::TransformString( 11407 fragString, convertedString, /* aGlobalTransform = */ Nothing(), 11408 maskChar, /* aCaseTransformsOnly = */ true, nullptr, 11409 charsToMergeArray, deletedCharsArray, transformedTextRun, 11410 aSkippedOffset); 11411 aOut.Append(convertedString); 11412 } else { 11413 // Should not happen (see assertion above), but as a fallback... 11414 aOut.Append(fragString); 11415 } 11416 } 11417 } 11418 11419 static void LineStartsOrEndsAtHardLineBreak(nsTextFrame* aFrame, 11420 nsBlockFrame* aLineContainer, 11421 bool* aStartsAtHardBreak, 11422 bool* aEndsAtHardBreak) { 11423 bool foundValidLine; 11424 nsBlockInFlowLineIterator iter(aLineContainer, aFrame, &foundValidLine); 11425 if (!foundValidLine) { 11426 NS_ERROR("Invalid line!"); 11427 *aStartsAtHardBreak = *aEndsAtHardBreak = true; 11428 return; 11429 } 11430 11431 *aEndsAtHardBreak = !iter.GetLine()->IsLineWrapped(); 11432 if (iter.Prev()) { 11433 *aStartsAtHardBreak = !iter.GetLine()->IsLineWrapped(); 11434 } else { 11435 // Hit block boundary 11436 *aStartsAtHardBreak = true; 11437 } 11438 } 11439 11440 bool nsTextFrame::AppendRenderedText(AppendRenderedTextState& aState, 11441 RenderedText& aResult) { 11442 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 11443 // We don't trust dirty frames, especially when computing rendered text. 11444 return false; 11445 } 11446 11447 // Ensure the text run and grab the gfxSkipCharsIterator for it 11448 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 11449 if (!mTextRun) { 11450 return false; 11451 } 11452 gfxSkipCharsIterator tmpIter = iter; 11453 11454 // Check if the frame starts/ends at a hard line break, to determine 11455 // whether whitespace should be trimmed. 11456 bool startsAtHardBreak, endsAtHardBreak; 11457 if (!HasAnyStateBits(TEXT_START_OF_LINE | TEXT_END_OF_LINE)) { 11458 startsAtHardBreak = endsAtHardBreak = false; 11459 } else if (nsBlockFrame* thisLc = do_QueryFrame(FindLineContainer())) { 11460 if (thisLc != aState.mLineContainer) { 11461 // Setup line cursor when needed. 11462 aState.mLineContainer = thisLc; 11463 aState.mLineContainer->SetupLineCursorForQuery(); 11464 } 11465 LineStartsOrEndsAtHardLineBreak(this, aState.mLineContainer, 11466 &startsAtHardBreak, &endsAtHardBreak); 11467 } else { 11468 // Weird situation where we have a line layout without a block. 11469 // No soft breaks occur in this situation. 11470 startsAtHardBreak = endsAtHardBreak = true; 11471 } 11472 11473 // Whether we need to trim whitespaces after the text frame. 11474 // TrimmedOffsetFlags::Default will allow trimming; we set NoTrim* flags 11475 // in the cases where this should not occur. 11476 TrimmedOffsetFlags trimFlags = TrimmedOffsetFlags::Default; 11477 if (!IsAtEndOfLine() || 11478 aState.mTrimTrailingWhitespace != TrailingWhitespace::Trim || 11479 !endsAtHardBreak) { 11480 trimFlags |= TrimmedOffsetFlags::NoTrimAfter; 11481 } 11482 11483 // Whether to trim whitespaces before the text frame. 11484 if (!startsAtHardBreak) { 11485 trimFlags |= TrimmedOffsetFlags::NoTrimBefore; 11486 } 11487 11488 TrimmedOffsets trimmedOffsets = 11489 GetTrimmedOffsets(aState.mCharacterDataBuffer, trimFlags); 11490 bool trimmedSignificantNewline = 11491 (trimmedOffsets.GetEnd() < GetContentEnd() || 11492 (aState.mTrimTrailingWhitespace == TrailingWhitespace::Trim && 11493 StyleText()->mWhiteSpaceCollapse == 11494 StyleWhiteSpaceCollapse::PreserveBreaks)) && 11495 HasSignificantTerminalNewline(); 11496 uint32_t skippedToRenderedStringOffset = 11497 aState.mOffsetInRenderedString - 11498 tmpIter.ConvertOriginalToSkipped(trimmedOffsets.mStart); 11499 uint32_t nextOffsetInRenderedString = 11500 tmpIter.ConvertOriginalToSkipped(trimmedOffsets.GetEnd()) + 11501 (trimmedSignificantNewline ? 1 : 0) + skippedToRenderedStringOffset; 11502 11503 if (aState.mOffsetType == TextOffsetType::OffsetsInRenderedText) { 11504 if (nextOffsetInRenderedString <= aState.mStartOffset) { 11505 aState.mOffsetInRenderedString = nextOffsetInRenderedString; 11506 return true; 11507 } 11508 if (!aState.mHaveOffsets) { 11509 aResult.mOffsetWithinNodeText = tmpIter.ConvertSkippedToOriginal( 11510 aState.mStartOffset - skippedToRenderedStringOffset); 11511 aResult.mOffsetWithinNodeRenderedText = aState.mStartOffset; 11512 aState.mHaveOffsets = true; 11513 } 11514 if (aState.mOffsetInRenderedString >= aState.mEndOffset) { 11515 return false; 11516 } 11517 } else { 11518 if (uint32_t(GetContentEnd()) <= aState.mStartOffset) { 11519 aState.mOffsetInRenderedString = nextOffsetInRenderedString; 11520 return true; 11521 } 11522 if (!aState.mHaveOffsets) { 11523 aResult.mOffsetWithinNodeText = aState.mStartOffset; 11524 // Skip trimmed space when computed the rendered text offset. 11525 int32_t clamped = 11526 std::max<int32_t>(aState.mStartOffset, trimmedOffsets.mStart); 11527 aResult.mOffsetWithinNodeRenderedText = 11528 tmpIter.ConvertOriginalToSkipped(clamped) + 11529 skippedToRenderedStringOffset; 11530 MOZ_ASSERT(aResult.mOffsetWithinNodeRenderedText >= 11531 aState.mOffsetInRenderedString && 11532 aResult.mOffsetWithinNodeRenderedText <= INT32_MAX, 11533 "Bad offset within rendered text"); 11534 aState.mHaveOffsets = true; 11535 } 11536 if (uint32_t(mContentOffset) >= aState.mEndOffset) { 11537 return false; 11538 } 11539 } 11540 11541 int32_t startOffset; 11542 int32_t endOffset; 11543 if (aState.mOffsetType == TextOffsetType::OffsetsInRenderedText) { 11544 startOffset = tmpIter.ConvertSkippedToOriginal( 11545 aState.mStartOffset - skippedToRenderedStringOffset); 11546 endOffset = tmpIter.ConvertSkippedToOriginal(aState.mEndOffset - 11547 skippedToRenderedStringOffset); 11548 } else { 11549 startOffset = aState.mStartOffset; 11550 endOffset = std::min<uint32_t>(INT32_MAX, aState.mEndOffset); 11551 } 11552 11553 // If startOffset and/or endOffset are inside of trimmedOffsets' range, 11554 // then clamp the edges of trimmedOffsets accordingly. 11555 int32_t origTrimmedOffsetsEnd = trimmedOffsets.GetEnd(); 11556 trimmedOffsets.mStart = 11557 std::max<uint32_t>(trimmedOffsets.mStart, startOffset); 11558 trimmedOffsets.mLength = 11559 std::min<uint32_t>(origTrimmedOffsetsEnd, endOffset) - 11560 trimmedOffsets.mStart; 11561 11562 if (trimmedOffsets.mLength > 0) { 11563 const nsStyleText* textStyle = StyleText(); 11564 iter.SetOriginalOffset(trimmedOffsets.mStart); 11565 while (iter.GetOriginalOffset() < trimmedOffsets.GetEnd()) { 11566 int32_t runLength; 11567 bool isSkipped = iter.IsOriginalCharSkipped(&runLength); 11568 runLength = std::min(runLength, 11569 trimmedOffsets.GetEnd() - iter.GetOriginalOffset()); 11570 if (isSkipped) { 11571 MOZ_ASSERT(runLength >= 0); 11572 for (uint32_t i = 0; i < static_cast<uint32_t>(runLength); ++i) { 11573 const char16_t ch = aState.mCharacterDataBuffer.CharAt( 11574 AssertedCast<uint32_t>(iter.GetOriginalOffset() + i)); 11575 if (ch == CH_SHY) { 11576 // We should preserve soft hyphens. They can't be transformed. 11577 aResult.mString.Append(ch); 11578 } 11579 } 11580 } else { 11581 TransformChars(this, textStyle, mTextRun, iter.GetSkippedOffset(), 11582 aState.mCharacterDataBuffer, iter.GetOriginalOffset(), 11583 runLength, aResult.mString); 11584 } 11585 iter.AdvanceOriginal(runLength); 11586 } 11587 } 11588 11589 if (trimmedSignificantNewline && GetContentEnd() <= endOffset) { 11590 // A significant newline was trimmed off (we must be 11591 // white-space:pre-line). Put it back. 11592 aResult.mString.Append('\n'); 11593 } 11594 aState.mOffsetInRenderedString = nextOffsetInRenderedString; 11595 11596 return true; 11597 } 11598 11599 nsIFrame::RenderedText nsTextFrame::GetRenderedText( 11600 uint32_t aStartOffset, uint32_t aEndOffset, TextOffsetType aOffsetType, 11601 TrailingWhitespace aTrimTrailingWhitespace) { 11602 MOZ_ASSERT(aStartOffset <= aEndOffset, "bogus offsets"); 11603 MOZ_ASSERT(!GetPrevContinuation() || 11604 (aOffsetType == TextOffsetType::OffsetsInContentText && 11605 aStartOffset >= (uint32_t)GetContentOffset() && 11606 aEndOffset <= (uint32_t)GetContentEnd()), 11607 "Must be called on first-in-flow, or content offsets must be " 11608 "given and be within this frame."); 11609 11610 // The handling of offsets could be more efficient... 11611 RenderedText result; 11612 AppendRenderedTextState state{aStartOffset, aEndOffset, aOffsetType, 11613 aTrimTrailingWhitespace, CharacterDataBuffer()}; 11614 11615 for (nsTextFrame* textFrame = this; textFrame; 11616 textFrame = textFrame->GetNextContinuation()) { 11617 if (!textFrame->AppendRenderedText(state, result)) { 11618 break; 11619 } 11620 } 11621 11622 if (!state.mHaveOffsets) { 11623 result.mOffsetWithinNodeText = state.mCharacterDataBuffer.GetLength(); 11624 result.mOffsetWithinNodeRenderedText = state.mOffsetInRenderedString; 11625 } 11626 11627 return result; 11628 } 11629 11630 /* virtual */ 11631 bool nsTextFrame::IsEmpty() { 11632 NS_ASSERTION( 11633 !HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE), 11634 "Invalid state"); 11635 11636 // XXXldb Should this check compatibility mode as well??? 11637 const nsStyleText* textStyle = StyleText(); 11638 if (textStyle->WhiteSpaceIsSignificant()) { 11639 // When WhiteSpaceIsSignificant styles are in effect, we only treat the 11640 // frame as empty if its content really is entirely *empty* (not just 11641 // whitespace). 11642 return !GetContentLength(); 11643 } 11644 11645 if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE)) { 11646 return false; 11647 } 11648 11649 if (HasAnyStateBits(TEXT_IS_ONLY_WHITESPACE)) { 11650 return true; 11651 } 11652 11653 bool isEmpty = IsAllWhitespace(CharacterDataBuffer(), 11654 textStyle->mWhiteSpaceCollapse != 11655 StyleWhiteSpaceCollapse::PreserveBreaks); 11656 AddStateBits(isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE); 11657 return isEmpty; 11658 } 11659 11660 #ifdef DEBUG_FRAME_DUMP 11661 // Translate the mapped content into a string that's printable 11662 void nsTextFrame::ToCString(nsCString& aBuf) const { 11663 // Get the frame's text content 11664 const auto& characterDataBuffer = CharacterDataBuffer(); 11665 11666 const int32_t length = GetContentEnd() - mContentOffset; 11667 if (length <= 0) { 11668 // Negative lengths are possible during invalidation. 11669 return; 11670 } 11671 11672 const uint32_t bufferLength = AssertedCast<uint32_t>(GetContentEnd()); 11673 uint32_t bufferOffset = AssertedCast<uint32_t>(GetContentOffset()); 11674 11675 while (bufferOffset < bufferLength) { 11676 char16_t ch = characterDataBuffer.CharAt(bufferOffset++); 11677 if (ch == '\r') { 11678 aBuf.AppendLiteral("\\r"); 11679 } else if (ch == '\n') { 11680 aBuf.AppendLiteral("\\n"); 11681 } else if (ch == '\t') { 11682 aBuf.AppendLiteral("\\t"); 11683 } else if ((ch < ' ') || (ch >= 127)) { 11684 aBuf.Append(nsPrintfCString("\\u%04x", ch)); 11685 } else { 11686 aBuf.Append(ch); 11687 } 11688 } 11689 } 11690 11691 nsresult nsTextFrame::GetFrameName(nsAString& aResult) const { 11692 MakeFrameName(u"Text"_ns, aResult); 11693 nsAutoCString tmp; 11694 ToCString(tmp); 11695 tmp.SetLength(std::min<size_t>(tmp.Length(), 50u)); 11696 aResult += u"\""_ns + NS_ConvertASCIItoUTF16(tmp) + u"\""_ns; 11697 return NS_OK; 11698 } 11699 11700 void nsTextFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const { 11701 nsCString str; 11702 ListGeneric(str, aPrefix, aFlags); 11703 11704 if (!aFlags.contains(ListFlag::OnlyListDeterministicInfo)) { 11705 str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun)); 11706 } 11707 11708 // Output the first/last content offset and prev/next in flow info 11709 bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength(); 11710 str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(), 11711 isComplete ? 'T' : 'F'); 11712 11713 if (IsSelected()) { 11714 str += " SELECTED"; 11715 } 11716 fprintf_stderr(out, "%s\n", str.get()); 11717 } 11718 11719 void nsTextFrame::ListTextRuns(FILE* out, 11720 nsTHashSet<const void*>& aSeen) const { 11721 if (!mTextRun || aSeen.Contains(mTextRun)) { 11722 return; 11723 } 11724 aSeen.Insert(mTextRun); 11725 mTextRun->Dump(out); 11726 } 11727 #endif 11728 11729 void nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd) { 11730 AddStateBits(NS_FRAME_IS_BIDI); 11731 if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) { 11732 mContent->RemoveProperty(nsGkAtoms::flowlength); 11733 mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY); 11734 } 11735 11736 /* 11737 * After Bidi resolution we may need to reassign text runs. 11738 * This is called during bidi resolution from the block container, so we 11739 * shouldn't be holding a local reference to a textrun anywhere. 11740 */ 11741 ClearTextRuns(); 11742 11743 nsTextFrame* prev = GetPrevContinuation(); 11744 if (prev) { 11745 // the bidi resolver can be very evil when columns/pages are involved. Don't 11746 // let it violate our invariants. 11747 int32_t prevOffset = prev->GetContentOffset(); 11748 aStart = std::max(aStart, prevOffset); 11749 aEnd = std::max(aEnd, prevOffset); 11750 prev->ClearTextRuns(); 11751 } 11752 11753 mContentOffset = aStart; 11754 SetLength(aEnd - aStart, nullptr, 0); 11755 } 11756 11757 /** 11758 * @return true if this text frame ends with a newline character. It should 11759 * return false if it is not a text frame. 11760 */ 11761 bool nsTextFrame::HasSignificantTerminalNewline() const { 11762 return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant(this); 11763 } 11764 11765 bool nsTextFrame::IsAtEndOfLine() const { 11766 return HasAnyStateBits(TEXT_END_OF_LINE); 11767 } 11768 11769 Maybe<nscoord> nsTextFrame::GetNaturalBaselineBOffset( 11770 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 11771 BaselineExportContext) const { 11772 if (aBaselineGroup == BaselineSharingGroup::Last) { 11773 return Nothing{}; 11774 } 11775 11776 if (!aWM.IsOrthogonalTo(GetWritingMode())) { 11777 if (aWM.IsCentralBaseline()) { 11778 return Some(GetLogicalUsedBorderAndPadding(aWM).BStart(aWM) + 11779 ContentBSize(aWM) / 2); 11780 } 11781 return Some(mAscent); 11782 } 11783 11784 // When the text frame has a writing mode orthogonal to the desired 11785 // writing mode, return a baseline coincides its parent frame. 11786 nsIFrame* parent = GetParent(); 11787 nsPoint position = GetNormalPosition(); 11788 nscoord parentAscent = parent->GetLogicalBaseline(aWM); 11789 if (aWM.IsVerticalRL()) { 11790 nscoord parentDescent = parent->GetSize().width - parentAscent; 11791 nscoord descent = parentDescent - position.x; 11792 return Some(GetSize().width - descent); 11793 } 11794 return Some(parentAscent - (aWM.IsVertical() ? position.x : position.y)); 11795 } 11796 11797 nscoord nsTextFrame::GetCaretBaseline() const { 11798 if (mAscent == 0 && HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS)) { 11799 nsBlockFrame* container = do_QueryFrame(FindLineContainer()); 11800 // TODO(emilio): Ideally we'd want to find out if only our line is empty, 11801 // but that's non-trivial to do, and realistically empty inlines and text 11802 // will get placed into a non-empty line unless all lines are empty, I 11803 // believe... 11804 if (container && container->LinesAreEmpty()) { 11805 nscoord blockSize = container->ContentBSize(GetWritingMode()); 11806 return GetFontMetricsDerivedCaretBaseline(blockSize); 11807 } 11808 } 11809 return nsIFrame::GetCaretBaseline(); 11810 } 11811 11812 bool nsTextFrame::HasAnyNoncollapsedCharacters() { 11813 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 11814 int32_t offset = GetContentOffset(), offsetEnd = GetContentEnd(); 11815 int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset); 11816 int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd); 11817 return skippedOffset != skippedOffsetEnd; 11818 } 11819 11820 bool nsTextFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) { 11821 return ComputeCustomOverflowInternal(aOverflowAreas, true); 11822 } 11823 11824 bool nsTextFrame::ComputeCustomOverflowInternal(OverflowAreas& aOverflowAreas, 11825 bool aIncludeShadows) { 11826 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 11827 return true; 11828 } 11829 11830 nsIFrame* decorationsBlock; 11831 if (IsFloatingFirstLetterChild()) { 11832 decorationsBlock = GetParent(); 11833 } else { 11834 nsIFrame* f = this; 11835 for (;;) { 11836 nsBlockFrame* fBlock = do_QueryFrame(f); 11837 if (fBlock) { 11838 decorationsBlock = fBlock; 11839 break; 11840 } 11841 11842 f = f->GetParent(); 11843 if (!f) { 11844 NS_ERROR("Couldn't find any block ancestor (for text decorations)"); 11845 return nsIFrame::ComputeCustomOverflow(aOverflowAreas); 11846 } 11847 } 11848 } 11849 11850 aOverflowAreas = RecomputeOverflow(decorationsBlock, aIncludeShadows); 11851 return nsIFrame::ComputeCustomOverflow(aOverflowAreas); 11852 } 11853 11854 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(JustificationAssignmentProperty, int32_t) 11855 11856 void nsTextFrame::AssignJustificationGaps( 11857 const mozilla::JustificationAssignment& aAssign) { 11858 int32_t encoded = (aAssign.mGapsAtStart << 8) | aAssign.mGapsAtEnd; 11859 static_assert(sizeof(aAssign) == 1, 11860 "The encoding might be broken if JustificationAssignment " 11861 "is larger than 1 byte"); 11862 SetProperty(JustificationAssignmentProperty(), encoded); 11863 } 11864 11865 mozilla::JustificationAssignment nsTextFrame::GetJustificationAssignment() 11866 const { 11867 int32_t encoded = GetProperty(JustificationAssignmentProperty()); 11868 mozilla::JustificationAssignment result; 11869 result.mGapsAtStart = encoded >> 8; 11870 result.mGapsAtEnd = encoded & 0xFF; 11871 return result; 11872 } 11873 11874 uint32_t nsTextFrame::CountGraphemeClusters() const { 11875 const auto& characterDataBuffer = CharacterDataBuffer(); 11876 nsAutoString content; 11877 characterDataBuffer.AppendTo(content, 11878 AssertedCast<uint32_t>(GetContentOffset()), 11879 AssertedCast<uint32_t>(GetContentLength())); 11880 return unicode::CountGraphemeClusters(content); 11881 } 11882 11883 bool nsTextFrame::HasNonSuppressedText() const { 11884 if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE | 11885 // If we haven't reflowed yet, or are currently doing so, 11886 // just return true because we can't be sure. 11887 NS_FRAME_FIRST_REFLOW | NS_FRAME_IN_REFLOW)) { 11888 return true; 11889 } 11890 11891 if (!GetTextRun(nsTextFrame::eInflated)) { 11892 return false; 11893 } 11894 11895 TrimmedOffsets offsets = 11896 GetTrimmedOffsets(CharacterDataBuffer(), TrimmedOffsetFlags::NoTrimAfter); 11897 return offsets.mLength != 0; 11898 }