| File: | root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/generic/./../../../layout/generic/nsTextFrame.cpp |
| Warning: | line 10791, column 5 Value stored to 'next' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
| 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
| 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| 4 | |
| 5 | /* rendering object for textual content of elements */ |
| 6 | |
| 7 | #include "nsTextFrame.h" |
| 8 | |
| 9 | #include <algorithm> |
| 10 | #include <limits> |
| 11 | |
| 12 | #include "MathMLTextRunFactory.h" |
| 13 | #include "PresShellInlines.h" |
| 14 | #include "PseudoStyleType.h" |
| 15 | #include "TextDrawTarget.h" |
| 16 | #include "gfx2DGlue.h" |
| 17 | #include "gfxContext.h" |
| 18 | #include "gfxUtils.h" |
| 19 | #include "mozilla/Attributes.h" |
| 20 | #include "mozilla/BinarySearch.h" |
| 21 | #include "mozilla/CaretAssociationHint.h" |
| 22 | #include "mozilla/ComputedStyle.h" |
| 23 | #include "mozilla/DebugOnly.h" |
| 24 | #include "mozilla/GeckoBindings.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/dom/Range.h" |
| 40 | #include "mozilla/gfx/2D.h" |
| 41 | #include "mozilla/intl/Bidi.h" |
| 42 | #include "mozilla/intl/Segmenter.h" |
| 43 | #include "mozilla/intl/UnicodeProperties.h" |
| 44 | #include "mozilla/widget/ThemeDrawing.h" |
| 45 | #include "nsBlockFrame.h" |
| 46 | #include "nsCOMPtr.h" |
| 47 | #include "nsCSSColorUtils.h" |
| 48 | #include "nsCSSFrameConstructor.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 "nsRubyFrame.h" |
| 68 | #include "nsSplittableFrame.h" |
| 69 | #include "nsString.h" |
| 70 | #include "nsStyleConsts.h" |
| 71 | #include "nsStyleStruct.h" |
| 72 | #include "nsStyleStructInlines.h" |
| 73 | #include "nsStyleUtil.h" |
| 74 | #include "nsTArray.h" |
| 75 | #include "nsTextFrameUtils.h" |
| 76 | #include "nsTextPaintStyle.h" |
| 77 | #include "nsTextRunTransformations.h" |
| 78 | #include "nsUnicharUtils.h" |
| 79 | #include "nsUnicodeProperties.h" |
| 80 | #ifdef ACCESSIBILITY1 |
| 81 | # include "nsAccessibilityService.h" |
| 82 | #endif |
| 83 | |
| 84 | #include "mozilla/LookAndFeel.h" |
| 85 | #include "mozilla/ProfilerLabels.h" |
| 86 | #include "mozilla/UniquePtr.h" |
| 87 | #include "mozilla/dom/Element.h" |
| 88 | #include "mozilla/gfx/DrawTargetRecording.h" |
| 89 | #include "nsPrintfCString.h" |
| 90 | |
| 91 | #ifdef DEBUG1 |
| 92 | # undef NOISY_REFLOW |
| 93 | # undef NOISY_TRIM |
| 94 | #else |
| 95 | # undef NOISY_REFLOW |
| 96 | # undef NOISY_TRIM |
| 97 | #endif |
| 98 | |
| 99 | #ifdef DrawText |
| 100 | # undef DrawText |
| 101 | #endif |
| 102 | |
| 103 | using namespace mozilla; |
| 104 | using namespace mozilla::dom; |
| 105 | using namespace mozilla::gfx; |
| 106 | |
| 107 | static bool NeedsToMaskPassword(const nsTextFrame* aFrame) { |
| 108 | MOZ_ASSERT(aFrame)do { static_assert( mozilla::detail::AssertionConditionType< decltype(aFrame)>::isValid, "invalid assertion condition") ; if ((__builtin_expect(!!(!(!!(aFrame))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aFrame", "./../../../layout/generic/nsTextFrame.cpp" , 108); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFrame" ")") ; do { MOZ_CrashSequence(__null, 108); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 109 | MOZ_ASSERT(aFrame->GetContent())do { static_assert( mozilla::detail::AssertionConditionType< decltype(aFrame->GetContent())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aFrame->GetContent()))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("aFrame->GetContent()" , "./../../../layout/generic/nsTextFrame.cpp", 109); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aFrame->GetContent()" ")"); do { MOZ_CrashSequence (__null, 109); __attribute__((nomerge)) ::abort(); } while (false ); } } while (false); |
| 110 | if (!aFrame->GetContent()->HasFlag(NS_MAYBE_MASKED)) { |
| 111 | return false; |
| 112 | } |
| 113 | const nsIFrame* frame = |
| 114 | nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::TextInput); |
| 115 | MOZ_ASSERT(frame, "How do we have a masked text node without a text input?")do { static_assert( mozilla::detail::AssertionConditionType< decltype(frame)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(frame))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("frame" " (" "How do we have a masked text node without a text input?" ")", "./../../../layout/generic/nsTextFrame.cpp", 115); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "frame" ") (" "How do we have a masked text node without a text input?" ")"); do { MOZ_CrashSequence(__null, 115); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 116 | return !frame || !frame->GetContent()->AsElement()->State().HasState( |
| 117 | ElementState::REVEALED); |
| 118 | } |
| 119 | |
| 120 | namespace mozilla { |
| 121 | |
| 122 | bool TextAutospace::ShouldSuppressLetterNumeralSpacing(const nsIFrame* aFrame) { |
| 123 | const auto wm = aFrame->GetWritingMode(); |
| 124 | if (wm.IsUpright()) { |
| 125 | // The characters are in vertical writing mode with forced upright glyph |
| 126 | // orientation. |
| 127 | return true; |
| 128 | } |
| 129 | if (aFrame->Style()->IsTextCombined()) { |
| 130 | // The characters have combined forced upright glyph orientation. |
| 131 | return true; |
| 132 | } |
| 133 | if (aFrame->StyleText()->mTextTransform & StyleTextTransform::FULL_WIDTH) { |
| 134 | // The characters are transformed to full-width, so non-ideographic |
| 135 | // letters/numerals look like ideograph letter/numerals. |
| 136 | return true; |
| 137 | } |
| 138 | return false; |
| 139 | } |
| 140 | |
| 141 | bool TextAutospace::Enabled(const StyleTextAutospace& aStyleTextAutospace, |
| 142 | const nsTextFrame* aFrame) { |
| 143 | if (aStyleTextAutospace == StyleTextAutospace::NO_AUTOSPACE) { |
| 144 | return false; |
| 145 | } |
| 146 | |
| 147 | if (aStyleTextAutospace == StyleTextAutospace::AUTO) { |
| 148 | // 'text-autospace: auto' is UA-defined. Currently, WebKit parses `auto` but |
| 149 | // does not add spacing; Blink does not parse 'auto' (treated as invalid). |
| 150 | // To align with other engines, we treat 'auto' the same as a no-op. |
| 151 | return false; |
| 152 | } |
| 153 | |
| 154 | if (ShouldSuppressLetterNumeralSpacing(aFrame)) { |
| 155 | // If we suppress the spacing for aFrame, ideograph-alpha or |
| 156 | // ideograph-numeric boundaries cannot occur. |
| 157 | return false; |
| 158 | } |
| 159 | |
| 160 | if (NeedsToMaskPassword(aFrame)) { |
| 161 | // Don't allow autospacing in masked password fields, as it could reveal |
| 162 | // hints about the types of characters present. |
| 163 | return false; |
| 164 | } |
| 165 | |
| 166 | return true; |
| 167 | } |
| 168 | |
| 169 | TextAutospace::TextAutospace(const StyleTextAutospace& aStyleTextAutospace, |
| 170 | nscoord aInterScriptSpacing) |
| 171 | : mBoundarySet(InitBoundarySet(aStyleTextAutospace)), |
| 172 | mInterScriptSpacing(aInterScriptSpacing) {} |
| 173 | |
| 174 | bool TextAutospace::ShouldApplySpacing(CharClass aPrevClass, |
| 175 | CharClass aCurrClass) const { |
| 176 | const EnumSet<CharClass> classes{aPrevClass, aCurrClass}; |
| 177 | if (mBoundarySet.contains(Boundary::IdeographAlpha)) { |
| 178 | constexpr EnumSet<CharClass> kIdeographAlphaMask{ |
| 179 | CharClass::Ideograph, CharClass::NonIdeographicLetter}; |
| 180 | if (classes == kIdeographAlphaMask) { |
| 181 | return true; |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | if (mBoundarySet.contains(Boundary::IdeographNumeric)) { |
| 186 | constexpr EnumSet<CharClass> kIdeographNumericMask{ |
| 187 | CharClass::Ideograph, CharClass::NonIdeographicNumeral}; |
| 188 | if (classes == kIdeographNumericMask) { |
| 189 | return true; |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | return false; |
| 194 | } |
| 195 | |
| 196 | bool TextAutospace::IsIdeograph(char32_t aChar) { |
| 197 | // All characters in the range of U+3041 to U+30FF, except those that belong |
| 198 | // to Unicode Punctuation [P*] general category. |
| 199 | if (0x3041 <= aChar && aChar <= 0x30FF) { |
| 200 | return !intl::UnicodeProperties::IsPunctuation(aChar); |
| 201 | } |
| 202 | |
| 203 | // CJK Strokes (U+31C0 to U+31EF) and Katakana Phonetic Extensions (U+31F0 to |
| 204 | // U+31FF). |
| 205 | if (0x31C0 <= aChar && aChar <= 0x31FF) { |
| 206 | return true; |
| 207 | } |
| 208 | |
| 209 | // All characters that have the Han script property. |
| 210 | if (intl::UnicodeProperties::GetScriptCode(aChar) == intl::Script::HAN) { |
| 211 | return true; |
| 212 | } |
| 213 | |
| 214 | return false; |
| 215 | } |
| 216 | |
| 217 | TextAutospace::CharClass TextAutospace::GetCharClass(char32_t aChar) { |
| 218 | if (IsAsciiAlpha(aChar)) { |
| 219 | return CharClass::NonIdeographicLetter; |
| 220 | } |
| 221 | |
| 222 | if (IsAsciiDigit(aChar)) { |
| 223 | return CharClass::NonIdeographicNumeral; |
| 224 | } |
| 225 | |
| 226 | if (IsIdeograph(aChar)) { |
| 227 | return CharClass::Ideograph; |
| 228 | } |
| 229 | |
| 230 | // From now on, aChar is *not* an ideograph. |
| 231 | if (intl::UnicodeProperties::IsCombiningMark(aChar)) { |
| 232 | // Do not treat combining mark as letter. |
| 233 | return CharClass::CombiningMark; |
| 234 | } |
| 235 | |
| 236 | if (intl::UnicodeProperties::IsLetter(aChar) && |
| 237 | !intl::UnicodeProperties::IsEastAsianFullWidth(aChar)) { |
| 238 | return CharClass::NonIdeographicLetter; |
| 239 | } |
| 240 | |
| 241 | if (intl::UnicodeProperties::CharType(aChar) == |
| 242 | intl::GeneralCategory::Decimal_Number) { |
| 243 | if (!intl::UnicodeProperties::IsEastAsianFullWidth(aChar)) { |
| 244 | return CharClass::NonIdeographicNumeral; |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | return CharClass::Other; |
| 249 | } |
| 250 | |
| 251 | TextAutospace::BoundarySet TextAutospace::InitBoundarySet( |
| 252 | const StyleTextAutospace& aStyleTextAutospace) const { |
| 253 | if (aStyleTextAutospace == StyleTextAutospace::NORMAL) { |
| 254 | return {Boundary::IdeographAlpha, Boundary::IdeographNumeric}; |
| 255 | } |
| 256 | |
| 257 | if (aStyleTextAutospace == StyleTextAutospace::IDEOGRAPH_ALPHA) { |
| 258 | return {Boundary::IdeographAlpha}; |
| 259 | } |
| 260 | |
| 261 | if (aStyleTextAutospace == StyleTextAutospace::IDEOGRAPH_NUMERIC) { |
| 262 | return {Boundary::IdeographNumeric}; |
| 263 | } |
| 264 | |
| 265 | return {}; |
| 266 | } |
| 267 | |
| 268 | } // namespace mozilla |
| 269 | |
| 270 | struct TabWidth { |
| 271 | TabWidth(uint32_t aOffset, uint32_t aWidth) |
| 272 | : mOffset(aOffset), mWidth(float(aWidth)) {} |
| 273 | |
| 274 | uint32_t mOffset; // DOM offset relative to the current frame's offset. |
| 275 | float mWidth; // extra space to be added at this position (in app units) |
| 276 | }; |
| 277 | |
| 278 | struct nsTextFrame::TabWidthStore { |
| 279 | explicit TabWidthStore(int32_t aValidForContentOffset) |
| 280 | : mLimit(0), mValidForContentOffset(aValidForContentOffset) {} |
| 281 | |
| 282 | // Apply tab widths to the aSpacing array, which corresponds to characters |
| 283 | // beginning at aOffset and has length aLength. (Width records outside this |
| 284 | // range will be ignored.) |
| 285 | void ApplySpacing(gfxTextRun::PropertyProvider::Spacing* aSpacing, |
| 286 | uint32_t aOffset, uint32_t aLength); |
| 287 | |
| 288 | // Offset up to which tabs have been measured; positions beyond this have not |
| 289 | // been calculated yet but may be appended if needed later. It's a DOM |
| 290 | // offset relative to the current frame's offset. |
| 291 | uint32_t mLimit; |
| 292 | |
| 293 | // Need to recalc tab offsets if frame content offset differs from this. |
| 294 | int32_t mValidForContentOffset; |
| 295 | |
| 296 | // A TabWidth record for each tab character measured so far. |
| 297 | nsTArray<TabWidth> mWidths; |
| 298 | }; |
| 299 | |
| 300 | namespace { |
| 301 | |
| 302 | struct TabwidthAdaptor { |
| 303 | const nsTArray<TabWidth>& mWidths; |
| 304 | explicit TabwidthAdaptor(const nsTArray<TabWidth>& aWidths) |
| 305 | : mWidths(aWidths) {} |
| 306 | uint32_t operator[](size_t aIdx) const { return mWidths[aIdx].mOffset; } |
| 307 | }; |
| 308 | |
| 309 | } // namespace |
| 310 | |
| 311 | void nsTextFrame::TabWidthStore::ApplySpacing( |
| 312 | gfxTextRun::PropertyProvider::Spacing* aSpacing, uint32_t aOffset, |
| 313 | uint32_t aLength) { |
| 314 | size_t i = 0; |
| 315 | const size_t len = mWidths.Length(); |
| 316 | |
| 317 | // If aOffset is non-zero, do a binary search to find where to start |
| 318 | // processing the tab widths, in case the list is really long. (See bug |
| 319 | // 953247.) |
| 320 | // We need to start from the first entry where mOffset >= aOffset. |
| 321 | if (aOffset > 0) { |
| 322 | mozilla::BinarySearch(TabwidthAdaptor(mWidths), 0, len, aOffset, &i); |
| 323 | } |
| 324 | |
| 325 | uint32_t limit = aOffset + aLength; |
| 326 | while (i < len) { |
| 327 | const TabWidth& tw = mWidths[i]; |
| 328 | if (tw.mOffset >= limit) { |
| 329 | break; |
| 330 | } |
| 331 | aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth; |
| 332 | i++; |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | NS_DECLARE_FRAME_PROPERTY_DELETABLE(TabWidthProperty,static const mozilla::FramePropertyDescriptor<nsTextFrame:: TabWidthStore>* TabWidthProperty() { static const auto descriptor = mozilla::FramePropertyDescriptor<nsTextFrame::TabWidthStore >::NewWithDestructor<DeleteValue>(); return &descriptor ; } |
| 337 | nsTextFrame::TabWidthStore)static const mozilla::FramePropertyDescriptor<nsTextFrame:: TabWidthStore>* TabWidthProperty() { static const auto descriptor = mozilla::FramePropertyDescriptor<nsTextFrame::TabWidthStore >::NewWithDestructor<DeleteValue>(); return &descriptor ; } |
| 338 | |
| 339 | NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(OffsetToFrameProperty, nsTextFrame)static const mozilla::FramePropertyDescriptor<nsTextFrame> * OffsetToFrameProperty() { static const auto descriptor = mozilla ::FramePropertyDescriptor<nsTextFrame>::NewWithoutDestructor (); return &descriptor; } |
| 340 | |
| 341 | NS_DECLARE_FRAME_PROPERTY_RELEASABLE(UninflatedTextRunProperty, gfxTextRun)static const mozilla::FramePropertyDescriptor<gfxTextRun> * UninflatedTextRunProperty() { static const auto descriptor = mozilla::FramePropertyDescriptor<gfxTextRun>::NewWithDestructor <ReleaseValue>(); return &descriptor; } |
| 342 | |
| 343 | NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float)static const mozilla::FramePropertyDescriptor<mozilla::SmallValueHolder <float> >* FontSizeInflationProperty() { static const auto descriptor = mozilla::FramePropertyDescriptor<mozilla ::SmallValueHolder<float> >::NewWithoutDestructor(); return &descriptor; } |
| 344 | |
| 345 | NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(HangableWhitespaceProperty, nscoord)static const mozilla::FramePropertyDescriptor<mozilla::SmallValueHolder <nscoord> >* HangableWhitespaceProperty() { static const auto descriptor = mozilla::FramePropertyDescriptor<mozilla ::SmallValueHolder<nscoord> >::NewWithoutDestructor( ); return &descriptor; } |
| 346 | NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TrimmableWhitespaceProperty,static const mozilla::FramePropertyDescriptor<mozilla::SmallValueHolder <gfxTextRun::TrimmableWS> >* TrimmableWhitespaceProperty () { static const auto descriptor = mozilla::FramePropertyDescriptor <mozilla::SmallValueHolder<gfxTextRun::TrimmableWS> > ::NewWithoutDestructor(); return &descriptor; } |
| 347 | gfxTextRun::TrimmableWS)static const mozilla::FramePropertyDescriptor<mozilla::SmallValueHolder <gfxTextRun::TrimmableWS> >* TrimmableWhitespaceProperty () { static const auto descriptor = mozilla::FramePropertyDescriptor <mozilla::SmallValueHolder<gfxTextRun::TrimmableWS> > ::NewWithoutDestructor(); return &descriptor; } |
| 348 | |
| 349 | struct nsTextFrame::PaintTextSelectionParams : nsTextFrame::PaintTextParams { |
| 350 | Point textBaselinePt; |
| 351 | PropertyProvider* provider = nullptr; |
| 352 | Range contentRange; |
| 353 | nsTextPaintStyle* textPaintStyle = nullptr; |
| 354 | Range glyphRange; |
| 355 | explicit PaintTextSelectionParams(const PaintTextParams& aParams) |
| 356 | : PaintTextParams(aParams) {} |
| 357 | }; |
| 358 | |
| 359 | struct nsTextFrame::DrawTextRunParams { |
| 360 | gfxContext* context; |
| 361 | mozilla::gfx::PaletteCache& paletteCache; |
| 362 | PropertyProvider* provider = nullptr; |
| 363 | gfxFloat* advanceWidth = nullptr; |
| 364 | mozilla::SVGContextPaint* contextPaint = nullptr; |
| 365 | DrawPathCallbacks* callbacks = nullptr; |
| 366 | nscolor textColor = NS_RGBA(0, 0, 0, 0)((nscolor)(((0) << 24) | ((0) << 16) | ((0) << 8) | (0))); |
| 367 | nscolor textStrokeColor = NS_RGBA(0, 0, 0, 0)((nscolor)(((0) << 24) | ((0) << 16) | ((0) << 8) | (0))); |
| 368 | nsAtom* fontPalette = nullptr; |
| 369 | float textStrokeWidth = 0.0f; |
| 370 | bool drawSoftHyphen = false; |
| 371 | bool hasTextShadow = false; |
| 372 | bool paintingShadows = false; |
| 373 | DrawTextRunParams(gfxContext* aContext, |
| 374 | mozilla::gfx::PaletteCache& aPaletteCache) |
| 375 | : context(aContext), paletteCache(aPaletteCache) {} |
| 376 | }; |
| 377 | |
| 378 | struct nsTextFrame::ClipEdges { |
| 379 | ClipEdges(const nsIFrame* aFrame, const nsPoint& aToReferenceFrame, |
| 380 | nscoord aVisIStartEdge, nscoord aVisIEndEdge) { |
| 381 | nsRect r = aFrame->ScrollableOverflowRect() + aToReferenceFrame; |
| 382 | if (aFrame->GetWritingMode().IsVertical()) { |
| 383 | mVisIStart = aVisIStartEdge > 0 ? r.y + aVisIStartEdge : nscoord_MIN; |
| 384 | mVisIEnd = aVisIEndEdge > 0 |
| 385 | ? std::max(r.YMost() - aVisIEndEdge, mVisIStart) |
| 386 | : nscoord_MAX; |
| 387 | } else { |
| 388 | mVisIStart = aVisIStartEdge > 0 ? r.x + aVisIStartEdge : nscoord_MIN; |
| 389 | mVisIEnd = aVisIEndEdge > 0 |
| 390 | ? std::max(r.XMost() - aVisIEndEdge, mVisIStart) |
| 391 | : nscoord_MAX; |
| 392 | } |
| 393 | } |
| 394 | |
| 395 | void Intersect(nscoord* aVisIStart, nscoord* aVisISize) const { |
| 396 | nscoord end = *aVisIStart + *aVisISize; |
| 397 | *aVisIStart = std::max(*aVisIStart, mVisIStart); |
| 398 | *aVisISize = std::max(std::min(end, mVisIEnd) - *aVisIStart, 0); |
| 399 | } |
| 400 | |
| 401 | nscoord mVisIStart; |
| 402 | nscoord mVisIEnd; |
| 403 | }; |
| 404 | |
| 405 | struct nsTextFrame::DrawTextParams : nsTextFrame::DrawTextRunParams { |
| 406 | Point framePt; |
| 407 | LayoutDeviceRect dirtyRect; |
| 408 | const nsTextPaintStyle* textStyle = nullptr; |
| 409 | const ClipEdges* clipEdges = nullptr; |
| 410 | const nscolor* decorationOverrideColor = nullptr; |
| 411 | Range glyphRange; |
| 412 | DrawTextParams(gfxContext* aContext, |
| 413 | mozilla::gfx::PaletteCache& aPaletteCache) |
| 414 | : DrawTextRunParams(aContext, aPaletteCache) {} |
| 415 | }; |
| 416 | |
| 417 | struct nsTextFrame::PaintShadowParams { |
| 418 | gfxTextRun::Range range; |
| 419 | LayoutDeviceRect dirtyRect; |
| 420 | Point framePt; |
| 421 | Point textBaselinePt; |
| 422 | gfxContext* context; |
| 423 | DrawPathCallbacks* callbacks = nullptr; |
| 424 | nscolor foregroundColor = NS_RGBA(0, 0, 0, 0)((nscolor)(((0) << 24) | ((0) << 16) | ((0) << 8) | (0))); |
| 425 | const ClipEdges* clipEdges = nullptr; |
| 426 | PropertyProvider* provider = nullptr; |
| 427 | nscoord leftSideOffset = 0; |
| 428 | explicit PaintShadowParams(const PaintTextParams& aParams) |
| 429 | : dirtyRect(aParams.dirtyRect), |
| 430 | framePt(aParams.framePt), |
| 431 | context(aParams.context) {} |
| 432 | }; |
| 433 | |
| 434 | /** |
| 435 | * A glyph observer for the change of a font glyph in a text run. |
| 436 | * |
| 437 | * This is stored in {Simple, Complex}TextRunUserData. |
| 438 | */ |
| 439 | class GlyphObserver final : public gfxFont::GlyphChangeObserver { |
| 440 | public: |
| 441 | GlyphObserver(gfxFont* aFont, gfxTextRun* aTextRun) |
| 442 | : gfxFont::GlyphChangeObserver(aFont), mTextRun(aTextRun) { |
| 443 | MOZ_ASSERT(aTextRun->GetUserData())do { static_assert( mozilla::detail::AssertionConditionType< decltype(aTextRun->GetUserData())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aTextRun->GetUserData())) ), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aTextRun->GetUserData()" , "./../../../layout/generic/nsTextFrame.cpp", 443); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aTextRun->GetUserData()" ")"); do { MOZ_CrashSequence (__null, 443); __attribute__((nomerge)) ::abort(); } while (false ); } } while (false); |
| 444 | } |
| 445 | void NotifyGlyphsChanged() override; |
| 446 | |
| 447 | private: |
| 448 | gfxTextRun* mTextRun; |
| 449 | }; |
| 450 | |
| 451 | static const nsFrameState TEXT_REFLOW_FLAGS = |
| 452 | TEXT_FIRST_LETTER | TEXT_START_OF_LINE | TEXT_END_OF_LINE | |
| 453 | TEXT_HYPHEN_BREAK | TEXT_TRIMMED_TRAILING_WHITESPACE | |
| 454 | TEXT_JUSTIFICATION_ENABLED | TEXT_HAS_NONCOLLAPSED_CHARACTERS | |
| 455 | TEXT_SELECTION_UNDERLINE_OVERFLOWED | TEXT_NO_RENDERED_GLYPHS; |
| 456 | |
| 457 | static const nsFrameState TEXT_WHITESPACE_FLAGS = |
| 458 | TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE; |
| 459 | |
| 460 | /* |
| 461 | * Some general notes |
| 462 | * |
| 463 | * Text frames delegate work to gfxTextRun objects. The gfxTextRun object |
| 464 | * transforms text to positioned glyphs. It can report the geometry of the |
| 465 | * glyphs and paint them. Text frames configure gfxTextRuns by providing text, |
| 466 | * spacing, language, and other information. |
| 467 | * |
| 468 | * A gfxTextRun can cover more than one DOM text node. This is necessary to |
| 469 | * get kerning, ligatures and shaping for text that spans multiple text nodes |
| 470 | * but is all the same font. |
| 471 | * |
| 472 | * The userdata for a gfxTextRun object can be: |
| 473 | * |
| 474 | * - A nsTextFrame* in the case a text run maps to only one flow. In this |
| 475 | * case, the textrun's user data pointer is a pointer to mStartFrame for that |
| 476 | * flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength is the |
| 477 | * length of the text node. |
| 478 | * |
| 479 | * - A SimpleTextRunUserData in the case a text run maps to one flow, but we |
| 480 | * still have to keep a list of glyph observers. |
| 481 | * |
| 482 | * - A ComplexTextRunUserData in the case a text run maps to multiple flows, |
| 483 | * but we need to keep a list of glyph observers. |
| 484 | * |
| 485 | * - A TextRunUserData in the case a text run maps multiple flows, but it |
| 486 | * doesn't have any glyph observer for changes in SVG fonts. |
| 487 | * |
| 488 | * You can differentiate between the four different cases with the |
| 489 | * IsSimpleFlow and MightHaveGlyphChanges flags. |
| 490 | * |
| 491 | * We go to considerable effort to make sure things work even if in-flow |
| 492 | * siblings have different ComputedStyles (i.e., first-letter and first-line). |
| 493 | * |
| 494 | * Our convention is that unsigned integer character offsets are offsets into |
| 495 | * the transformed string. Signed integer character offsets are offsets into |
| 496 | * the DOM string. |
| 497 | * |
| 498 | * XXX currently we don't handle hyphenated breaks between text frames where the |
| 499 | * hyphen occurs at the end of the first text frame, e.g. |
| 500 | * <b>Kit­</b>ty |
| 501 | */ |
| 502 | |
| 503 | /** |
| 504 | * This is our user data for the textrun, when textRun->GetFlags2() has |
| 505 | * IsSimpleFlow set, and also MightHaveGlyphChanges. |
| 506 | * |
| 507 | * This allows having an array of observers if there are fonts whose glyphs |
| 508 | * might change, but also avoid allocation in the simple case that there aren't. |
| 509 | */ |
| 510 | struct SimpleTextRunUserData { |
| 511 | nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers; |
| 512 | nsTextFrame* mFrame; |
| 513 | explicit SimpleTextRunUserData(nsTextFrame* aFrame) : mFrame(aFrame) {} |
| 514 | }; |
| 515 | |
| 516 | /** |
| 517 | * We use an array of these objects to record which text frames |
| 518 | * are associated with the textrun. mStartFrame is the start of a list of |
| 519 | * text frames. Some sequence of its continuations are covered by the textrun. |
| 520 | * A content textnode can have at most one TextRunMappedFlow associated with it |
| 521 | * for a given textrun. |
| 522 | * |
| 523 | * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to |
| 524 | * obtain the offset into the before-transformation text of the textrun. It can |
| 525 | * be positive (when a text node starts in the middle of a text run) or negative |
| 526 | * (when a text run starts in the middle of a text node). Of course it can also |
| 527 | * be zero. |
| 528 | */ |
| 529 | struct TextRunMappedFlow { |
| 530 | nsTextFrame* mStartFrame; |
| 531 | int32_t mDOMOffsetToBeforeTransformOffset; |
| 532 | // The text mapped starts at mStartFrame->GetContentOffset() and is this long |
| 533 | uint32_t mContentLength; |
| 534 | }; |
| 535 | |
| 536 | /** |
| 537 | * This is the type in the gfxTextRun's userdata field in the common case that |
| 538 | * the text run maps to multiple flows, but no fonts have been found with |
| 539 | * animatable glyphs. |
| 540 | * |
| 541 | * This way, we avoid allocating and constructing the extra nsTArray. |
| 542 | */ |
| 543 | struct TextRunUserData { |
| 544 | #ifdef DEBUG1 |
| 545 | TextRunMappedFlow* mMappedFlows; |
| 546 | #endif |
| 547 | uint32_t mMappedFlowCount; |
| 548 | uint32_t mLastFlowIndex; |
| 549 | }; |
| 550 | |
| 551 | /** |
| 552 | * This is our user data for the textrun, when textRun->GetFlags2() does not |
| 553 | * have IsSimpleFlow set and has the MightHaveGlyphChanges flag. |
| 554 | */ |
| 555 | struct ComplexTextRunUserData : public TextRunUserData { |
| 556 | nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers; |
| 557 | }; |
| 558 | |
| 559 | static TextRunUserData* CreateUserData(uint32_t aMappedFlowCount) { |
| 560 | TextRunUserData* data = static_cast<TextRunUserData*>(moz_xmalloc( |
| 561 | sizeof(TextRunUserData) + aMappedFlowCount * sizeof(TextRunMappedFlow))); |
| 562 | #ifdef DEBUG1 |
| 563 | data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1); |
| 564 | #endif |
| 565 | data->mMappedFlowCount = aMappedFlowCount; |
| 566 | data->mLastFlowIndex = 0; |
| 567 | return data; |
| 568 | } |
| 569 | |
| 570 | static void DestroyUserData(TextRunUserData* aUserData) { |
| 571 | if (aUserData) { |
| 572 | free(aUserData); |
| 573 | } |
| 574 | } |
| 575 | |
| 576 | static ComplexTextRunUserData* CreateComplexUserData( |
| 577 | uint32_t aMappedFlowCount) { |
| 578 | ComplexTextRunUserData* data = static_cast<ComplexTextRunUserData*>( |
| 579 | moz_xmalloc(sizeof(ComplexTextRunUserData) + |
| 580 | aMappedFlowCount * sizeof(TextRunMappedFlow))); |
| 581 | new (data) ComplexTextRunUserData(); |
| 582 | #ifdef DEBUG1 |
| 583 | data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1); |
| 584 | #endif |
| 585 | data->mMappedFlowCount = aMappedFlowCount; |
| 586 | data->mLastFlowIndex = 0; |
| 587 | return data; |
| 588 | } |
| 589 | |
| 590 | static void DestroyComplexUserData(ComplexTextRunUserData* aUserData) { |
| 591 | if (aUserData) { |
| 592 | aUserData->~ComplexTextRunUserData(); |
| 593 | free(aUserData); |
| 594 | } |
| 595 | } |
| 596 | |
| 597 | static void DestroyTextRunUserData(gfxTextRun* aTextRun) { |
| 598 | MOZ_ASSERT(aTextRun->GetUserData())do { static_assert( mozilla::detail::AssertionConditionType< decltype(aTextRun->GetUserData())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aTextRun->GetUserData())) ), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aTextRun->GetUserData()" , "./../../../layout/generic/nsTextFrame.cpp", 598); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aTextRun->GetUserData()" ")"); do { MOZ_CrashSequence (__null, 598); __attribute__((nomerge)) ::abort(); } while (false ); } } while (false); |
| 599 | if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { |
| 600 | if (aTextRun->GetFlags2() & |
| 601 | nsTextFrameUtils::Flags::MightHaveGlyphChanges) { |
| 602 | delete static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData()); |
| 603 | } |
| 604 | } else { |
| 605 | if (aTextRun->GetFlags2() & |
| 606 | nsTextFrameUtils::Flags::MightHaveGlyphChanges) { |
| 607 | DestroyComplexUserData( |
| 608 | static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData())); |
| 609 | } else { |
| 610 | DestroyUserData(static_cast<TextRunUserData*>(aTextRun->GetUserData())); |
| 611 | } |
| 612 | } |
| 613 | aTextRun->ClearFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges); |
| 614 | aTextRun->SetUserData(nullptr); |
| 615 | } |
| 616 | |
| 617 | static TextRunMappedFlow* GetMappedFlows(const gfxTextRun* aTextRun) { |
| 618 | MOZ_ASSERT(aTextRun->GetUserData(), "UserData must exist.")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aTextRun->GetUserData())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aTextRun->GetUserData())) ), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aTextRun->GetUserData()" " (" "UserData must exist." ")", "./../../../layout/generic/nsTextFrame.cpp" , 618); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aTextRun->GetUserData()" ") (" "UserData must exist." ")"); do { MOZ_CrashSequence(__null , 618); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 619 | MOZ_ASSERT(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow),do { static_assert( mozilla::detail::AssertionConditionType< decltype(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags ::IsSimpleFlow))>::isValid, "invalid assertion condition") ; if ((__builtin_expect(!!(!(!!(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow)" " (" "The method should not be called for simple flows." ")" , "./../../../layout/generic/nsTextFrame.cpp", 620); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow)" ") (" "The method should not be called for simple flows." ")" ); do { MOZ_CrashSequence(__null, 620); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
| 620 | "The method should not be called for simple flows.")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags ::IsSimpleFlow))>::isValid, "invalid assertion condition") ; if ((__builtin_expect(!!(!(!!(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow)" " (" "The method should not be called for simple flows." ")" , "./../../../layout/generic/nsTextFrame.cpp", 620); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow)" ") (" "The method should not be called for simple flows." ")" ); do { MOZ_CrashSequence(__null, 620); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 621 | TextRunMappedFlow* flows; |
| 622 | if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) { |
| 623 | flows = reinterpret_cast<TextRunMappedFlow*>( |
| 624 | static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) + 1); |
| 625 | } else { |
| 626 | flows = reinterpret_cast<TextRunMappedFlow*>( |
| 627 | static_cast<TextRunUserData*>(aTextRun->GetUserData()) + 1); |
| 628 | } |
| 629 | MOZ_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType< decltype(static_cast<TextRunUserData*>(aTextRun->GetUserData ())->mMappedFlows == flows)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(static_cast<TextRunUserData *>(aTextRun->GetUserData())->mMappedFlows == flows)) ), 0))) { do { } while (false); MOZ_ReportAssertionFailure("static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows" " (" "GetMappedFlows should return the same pointer as mMappedFlows." ")", "./../../../layout/generic/nsTextFrame.cpp", 632); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows" ") (" "GetMappedFlows should return the same pointer as mMappedFlows." ")"); do { MOZ_CrashSequence(__null, 632); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
| 630 | static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows ==do { static_assert( mozilla::detail::AssertionConditionType< decltype(static_cast<TextRunUserData*>(aTextRun->GetUserData ())->mMappedFlows == flows)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(static_cast<TextRunUserData *>(aTextRun->GetUserData())->mMappedFlows == flows)) ), 0))) { do { } while (false); MOZ_ReportAssertionFailure("static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows" " (" "GetMappedFlows should return the same pointer as mMappedFlows." ")", "./../../../layout/generic/nsTextFrame.cpp", 632); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows" ") (" "GetMappedFlows should return the same pointer as mMappedFlows." ")"); do { MOZ_CrashSequence(__null, 632); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
| 631 | flows,do { static_assert( mozilla::detail::AssertionConditionType< decltype(static_cast<TextRunUserData*>(aTextRun->GetUserData ())->mMappedFlows == flows)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(static_cast<TextRunUserData *>(aTextRun->GetUserData())->mMappedFlows == flows)) ), 0))) { do { } while (false); MOZ_ReportAssertionFailure("static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows" " (" "GetMappedFlows should return the same pointer as mMappedFlows." ")", "./../../../layout/generic/nsTextFrame.cpp", 632); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows" ") (" "GetMappedFlows should return the same pointer as mMappedFlows." ")"); do { MOZ_CrashSequence(__null, 632); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
| 632 | "GetMappedFlows should return the same pointer as mMappedFlows.")do { static_assert( mozilla::detail::AssertionConditionType< decltype(static_cast<TextRunUserData*>(aTextRun->GetUserData ())->mMappedFlows == flows)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(static_cast<TextRunUserData *>(aTextRun->GetUserData())->mMappedFlows == flows)) ), 0))) { do { } while (false); MOZ_ReportAssertionFailure("static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows" " (" "GetMappedFlows should return the same pointer as mMappedFlows." ")", "./../../../layout/generic/nsTextFrame.cpp", 632); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows == flows" ") (" "GetMappedFlows should return the same pointer as mMappedFlows." ")"); do { MOZ_CrashSequence(__null, 632); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 633 | return flows; |
| 634 | } |
| 635 | |
| 636 | /** |
| 637 | * These are utility functions just for helping with the complexity related with |
| 638 | * the text runs user data. |
| 639 | */ |
| 640 | static nsTextFrame* GetFrameForSimpleFlow(const gfxTextRun* aTextRun) { |
| 641 | MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow,do { static_assert( mozilla::detail::AssertionConditionType< decltype(aTextRun->GetFlags2() & nsTextFrameUtils::Flags ::IsSimpleFlow)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow" " (" "Not so simple flow?" ")", "./../../../layout/generic/nsTextFrame.cpp" , 642); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow" ") (" "Not so simple flow?" ")"); do { MOZ_CrashSequence(__null , 642); __attribute__((nomerge)) ::abort(); } while (false); } } while (false) |
| 642 | "Not so simple flow?")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aTextRun->GetFlags2() & nsTextFrameUtils::Flags ::IsSimpleFlow)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow" " (" "Not so simple flow?" ")", "./../../../layout/generic/nsTextFrame.cpp" , 642); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow" ") (" "Not so simple flow?" ")"); do { MOZ_CrashSequence(__null , 642); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 643 | if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) { |
| 644 | return static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())->mFrame; |
| 645 | } |
| 646 | |
| 647 | return static_cast<nsTextFrame*>(aTextRun->GetUserData()); |
| 648 | } |
| 649 | |
| 650 | /** |
| 651 | * Remove |aTextRun| from the frame continuation chain starting at |
| 652 | * |aStartContinuation| if non-null, otherwise starting at |aFrame|. |
| 653 | * Unmark |aFrame| as a text run owner if it's the frame we start at. |
| 654 | * Return true if |aStartContinuation| is non-null and was found |
| 655 | * in the next-continuation chain of |aFrame|. |
| 656 | */ |
| 657 | static bool ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun, |
| 658 | nsTextFrame* aStartContinuation, |
| 659 | nsFrameState aWhichTextRunState) { |
| 660 | MOZ_ASSERT(aFrame, "null frame")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aFrame)>::isValid, "invalid assertion condition") ; if ((__builtin_expect(!!(!(!!(aFrame))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aFrame" " (" "null frame" ")", "./../../../layout/generic/nsTextFrame.cpp", 660); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aFrame" ") (" "null frame" ")"); do { MOZ_CrashSequence (__null, 660); __attribute__((nomerge)) ::abort(); } while (false ); } } while (false); |
| 661 | MOZ_ASSERT(!aStartContinuation ||do { static_assert( mozilla::detail::AssertionConditionType< decltype(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>:: isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" " (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp" , 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" ") (" "wrong aStartContinuation for this text run" ")"); do { MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort (); } while (false); } } while (false) |
| 662 | (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||do { static_assert( mozilla::detail::AssertionConditionType< decltype(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>:: isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" " (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp" , 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" ") (" "wrong aStartContinuation for this text run" ")"); do { MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort (); } while (false); } } while (false) |
| 663 | aStartContinuation->GetTextRun(nsTextFrame::eInflated) ==do { static_assert( mozilla::detail::AssertionConditionType< decltype(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>:: isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" " (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp" , 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" ") (" "wrong aStartContinuation for this text run" ")"); do { MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort (); } while (false); } } while (false) |
| 664 | aTextRun) ||do { static_assert( mozilla::detail::AssertionConditionType< decltype(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>:: isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" " (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp" , 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" ") (" "wrong aStartContinuation for this text run" ")"); do { MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort (); } while (false); } } while (false) |
| 665 | (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||do { static_assert( mozilla::detail::AssertionConditionType< decltype(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>:: isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" " (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp" , 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" ") (" "wrong aStartContinuation for this text run" ")"); do { MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort (); } while (false); } } while (false) |
| 666 | aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ==do { static_assert( mozilla::detail::AssertionConditionType< decltype(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>:: isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" " (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp" , 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" ") (" "wrong aStartContinuation for this text run" ")"); do { MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort (); } while (false); } } while (false) |
| 667 | aTextRun),do { static_assert( mozilla::detail::AssertionConditionType< decltype(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>:: isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" " (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp" , 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" ") (" "wrong aStartContinuation for this text run" ")"); do { MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort (); } while (false); } } while (false) |
| 668 | "wrong aStartContinuation for this text run")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun))>:: isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!aStartContinuation || (!aStartContinuation->GetTextRun (nsTextFrame::eInflated) || aStartContinuation->GetTextRun (nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation ->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" " (" "wrong aStartContinuation for this text run" ")", "./../../../layout/generic/nsTextFrame.cpp" , 668); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aStartContinuation || (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun)" ") (" "wrong aStartContinuation for this text run" ")"); do { MOZ_CrashSequence(__null, 668); __attribute__((nomerge)) ::abort (); } while (false); } } while (false); |
| 669 | |
| 670 | if (!aStartContinuation || aStartContinuation == aFrame) { |
| 671 | aFrame->RemoveStateBits(aWhichTextRunState); |
| 672 | } else { |
| 673 | do { |
| 674 | NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame")do { if (!(aFrame->IsTextFrame())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Bad frame", "aFrame->IsTextFrame()", "./../../../layout/generic/nsTextFrame.cpp" , 674); MOZ_PretendNoReturn(); } } while (0); |
| 675 | aFrame = aFrame->GetNextContinuation(); |
| 676 | } while (aFrame && aFrame != aStartContinuation); |
| 677 | } |
| 678 | bool found = aStartContinuation == aFrame; |
| 679 | while (aFrame) { |
| 680 | NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame")do { if (!(aFrame->IsTextFrame())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Bad frame", "aFrame->IsTextFrame()", "./../../../layout/generic/nsTextFrame.cpp" , 680); MOZ_PretendNoReturn(); } } while (0); |
| 681 | if (!aFrame->RemoveTextRun(aTextRun)) { |
| 682 | break; |
| 683 | } |
| 684 | aFrame = aFrame->GetNextContinuation(); |
| 685 | } |
| 686 | |
| 687 | MOZ_ASSERT(!found || aStartContinuation, "how did we find null?")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!found || aStartContinuation)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!found || aStartContinuation ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "!found || aStartContinuation" " (" "how did we find null?" ")" , "./../../../layout/generic/nsTextFrame.cpp", 687); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "!found || aStartContinuation" ") (" "how did we find null?" ")"); do { MOZ_CrashSequence(__null, 687); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 688 | return found; |
| 689 | } |
| 690 | |
| 691 | /** |
| 692 | * Kill all references to |aTextRun| starting at |aStartContinuation|. |
| 693 | * It could be referenced by any of its owners, and all their in-flows. |
| 694 | * If |aStartContinuation| is null then process all userdata frames |
| 695 | * and their continuations. |
| 696 | * @note the caller is expected to take care of possibly destroying the |
| 697 | * text run if all userdata frames were reset (userdata is deallocated |
| 698 | * by this function though). The caller can detect this has occured by |
| 699 | * checking |aTextRun->GetUserData() == nullptr|. |
| 700 | */ |
| 701 | static void UnhookTextRunFromFrames(gfxTextRun* aTextRun, |
| 702 | nsTextFrame* aStartContinuation) { |
| 703 | if (!aTextRun->GetUserData()) { |
| 704 | return; |
| 705 | } |
| 706 | |
| 707 | if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { |
| 708 | nsTextFrame* userDataFrame = GetFrameForSimpleFlow(aTextRun); |
| 709 | nsFrameState whichTextRunState = |
| 710 | userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun |
| 711 | ? TEXT_IN_TEXTRUN_USER_DATA |
| 712 | : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA; |
| 713 | DebugOnly<bool> found = ClearAllTextRunReferences( |
| 714 | userDataFrame, aTextRun, aStartContinuation, whichTextRunState); |
| 715 | NS_ASSERTION(!aStartContinuation || found,do { if (!(!aStartContinuation || found)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "aStartContinuation wasn't found in simple flow text run", "!aStartContinuation || found" , "./../../../layout/generic/nsTextFrame.cpp", 716); MOZ_PretendNoReturn (); } } while (0) |
| 716 | "aStartContinuation wasn't found in simple flow text run")do { if (!(!aStartContinuation || found)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "aStartContinuation wasn't found in simple flow text run", "!aStartContinuation || found" , "./../../../layout/generic/nsTextFrame.cpp", 716); MOZ_PretendNoReturn (); } } while (0); |
| 717 | if (!userDataFrame->HasAnyStateBits(whichTextRunState)) { |
| 718 | DestroyTextRunUserData(aTextRun); |
| 719 | } |
| 720 | } else { |
| 721 | auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData()); |
| 722 | TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun); |
| 723 | int32_t destroyFromIndex = aStartContinuation ? -1 : 0; |
| 724 | for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) { |
| 725 | nsTextFrame* userDataFrame = userMappedFlows[i].mStartFrame; |
| 726 | nsFrameState whichTextRunState = |
| 727 | userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun |
| 728 | ? TEXT_IN_TEXTRUN_USER_DATA |
| 729 | : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA; |
| 730 | bool found = ClearAllTextRunReferences( |
| 731 | userDataFrame, aTextRun, aStartContinuation, whichTextRunState); |
| 732 | if (found) { |
| 733 | if (userDataFrame->HasAnyStateBits(whichTextRunState)) { |
| 734 | destroyFromIndex = i + 1; |
| 735 | } else { |
| 736 | destroyFromIndex = i; |
| 737 | } |
| 738 | aStartContinuation = nullptr; |
| 739 | } |
| 740 | } |
| 741 | NS_ASSERTION(destroyFromIndex >= 0,do { if (!(destroyFromIndex >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "aStartContinuation wasn't found in multi flow text run", "destroyFromIndex >= 0" , "./../../../layout/generic/nsTextFrame.cpp", 742); MOZ_PretendNoReturn (); } } while (0) |
| 742 | "aStartContinuation wasn't found in multi flow text run")do { if (!(destroyFromIndex >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "aStartContinuation wasn't found in multi flow text run", "destroyFromIndex >= 0" , "./../../../layout/generic/nsTextFrame.cpp", 742); MOZ_PretendNoReturn (); } } while (0); |
| 743 | if (destroyFromIndex == 0) { |
| 744 | DestroyTextRunUserData(aTextRun); |
| 745 | } else { |
| 746 | userData->mMappedFlowCount = uint32_t(destroyFromIndex); |
| 747 | if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) { |
| 748 | userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1; |
| 749 | } |
| 750 | } |
| 751 | } |
| 752 | } |
| 753 | |
| 754 | static void InvalidateFrameDueToGlyphsChanged(nsIFrame* aFrame) { |
| 755 | MOZ_ASSERT(aFrame)do { static_assert( mozilla::detail::AssertionConditionType< decltype(aFrame)>::isValid, "invalid assertion condition") ; if ((__builtin_expect(!!(!(!!(aFrame))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aFrame", "./../../../layout/generic/nsTextFrame.cpp" , 755); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFrame" ")") ; do { MOZ_CrashSequence(__null, 755); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 756 | |
| 757 | PresShell* presShell = aFrame->PresShell(); |
| 758 | for (nsIFrame* f = aFrame; f; |
| 759 | f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { |
| 760 | f->InvalidateFrame(); |
| 761 | |
| 762 | // If this is a non-display text frame within SVG <text>, we need |
| 763 | // to reflow the SVGTextFrame. (This is similar to reflowing the |
| 764 | // SVGTextFrame in response to style changes, in |
| 765 | // SVGTextFrame::DidSetComputedStyle.) |
| 766 | if (f->IsInSVGTextSubtree() && f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { |
| 767 | auto* svgTextFrame = static_cast<SVGTextFrame*>( |
| 768 | nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::SVGText)); |
| 769 | svgTextFrame->ScheduleReflowSVGNonDisplayText(IntrinsicDirty::None); |
| 770 | } else { |
| 771 | // Theoretically we could just update overflow areas, perhaps using |
| 772 | // OverflowChangedTracker, but that would do a bunch of work eagerly that |
| 773 | // we should probably do lazily here since there could be a lot |
| 774 | // of text frames affected and we'd like to coalesce the work. So that's |
| 775 | // not easy to do well. |
| 776 | // TODO(emilio): If we allow text to store changes or so, this could use |
| 777 | // PostRestyleEvent(..., UpdateOverflow); |
| 778 | presShell->FrameNeedsReflow(f, IntrinsicDirty::None, NS_FRAME_IS_DIRTY); |
| 779 | } |
| 780 | } |
| 781 | } |
| 782 | |
| 783 | void GlyphObserver::NotifyGlyphsChanged() { |
| 784 | if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { |
| 785 | InvalidateFrameDueToGlyphsChanged(GetFrameForSimpleFlow(mTextRun)); |
| 786 | return; |
| 787 | } |
| 788 | |
| 789 | auto data = static_cast<TextRunUserData*>(mTextRun->GetUserData()); |
| 790 | TextRunMappedFlow* userMappedFlows = GetMappedFlows(mTextRun); |
| 791 | for (uint32_t i = 0; i < data->mMappedFlowCount; ++i) { |
| 792 | InvalidateFrameDueToGlyphsChanged(userMappedFlows[i].mStartFrame); |
| 793 | } |
| 794 | } |
| 795 | |
| 796 | int32_t nsTextFrame::GetContentEnd() const { |
| 797 | nsTextFrame* next = GetNextContinuation(); |
| 798 | // In case of allocation failure when setting/modifying the text buffer, |
| 799 | // it's possible our text might be missing. So we check the buffer length, |
| 800 | // in addition to the offset of the next continuation (if any). |
| 801 | int32_t bufferLen = CharacterDataBuffer().GetLength(); |
| 802 | return next ? std::min(bufferLen, next->GetContentOffset()) : bufferLen; |
| 803 | } |
| 804 | |
| 805 | struct FlowLengthProperty { |
| 806 | int32_t mStartOffset; |
| 807 | // The offset of the next fixed continuation after mStartOffset, or |
| 808 | // of the end of the text if there is none |
| 809 | int32_t mEndFlowOffset; |
| 810 | }; |
| 811 | |
| 812 | int32_t nsTextFrame::GetInFlowContentLength() { |
| 813 | if (!HasAnyStateBits(NS_FRAME_IS_BIDI)) { |
| 814 | return mContent->TextLength() - mContentOffset; |
| 815 | } |
| 816 | |
| 817 | FlowLengthProperty* flowLength = |
| 818 | mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) |
| 819 | ? static_cast<FlowLengthProperty*>( |
| 820 | mContent->GetProperty(nsGkAtoms::flowlength)) |
| 821 | : nullptr; |
| 822 | MOZ_ASSERT(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength,do { static_assert( mozilla::detail::AssertionConditionType< decltype(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == ! !flowLength)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY ) == !!flowLength))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength" " (" "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag" ")", "./../../../layout/generic/nsTextFrame.cpp" , 823); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength" ") (" "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag" ")"); do { MOZ_CrashSequence(__null, 823); __attribute__((nomerge)) ::abort (); } while (false); } } while (false) |
| 823 | "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag")do { static_assert( mozilla::detail::AssertionConditionType< decltype(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == ! !flowLength)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY ) == !!flowLength))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength" " (" "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag" ")", "./../../../layout/generic/nsTextFrame.cpp" , 823); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength" ") (" "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag" ")"); do { MOZ_CrashSequence(__null, 823); __attribute__((nomerge)) ::abort (); } while (false); } } while (false); |
| 824 | /** |
| 825 | * This frame must start inside the cached flow. If the flow starts at |
| 826 | * mContentOffset but this frame is empty, logically it might be before the |
| 827 | * start of the cached flow. |
| 828 | */ |
| 829 | if (flowLength && |
| 830 | (flowLength->mStartOffset < mContentOffset || |
| 831 | (flowLength->mStartOffset == mContentOffset && |
| 832 | GetContentEnd() > mContentOffset)) && |
| 833 | flowLength->mEndFlowOffset > mContentOffset) { |
| 834 | #ifdef DEBUG1 |
| 835 | NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),do { if (!(flowLength->mEndFlowOffset >= GetContentEnd( ))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "frame crosses fixed continuation boundary" , "flowLength->mEndFlowOffset >= GetContentEnd()", "./../../../layout/generic/nsTextFrame.cpp" , 836); MOZ_PretendNoReturn(); } } while (0) |
| 836 | "frame crosses fixed continuation boundary")do { if (!(flowLength->mEndFlowOffset >= GetContentEnd( ))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "frame crosses fixed continuation boundary" , "flowLength->mEndFlowOffset >= GetContentEnd()", "./../../../layout/generic/nsTextFrame.cpp" , 836); MOZ_PretendNoReturn(); } } while (0); |
| 837 | #endif |
| 838 | return flowLength->mEndFlowOffset - mContentOffset; |
| 839 | } |
| 840 | |
| 841 | nsTextFrame* nextBidi = LastInFlow()->GetNextContinuation(); |
| 842 | int32_t endFlow = |
| 843 | nextBidi ? nextBidi->GetContentOffset() : GetContent()->TextLength(); |
| 844 | |
| 845 | if (!flowLength) { |
| 846 | flowLength = new FlowLengthProperty; |
| 847 | if (NS_FAILED(mContent->SetProperty(((bool)(__builtin_expect(!!(NS_FAILED_impl(mContent->SetProperty ( nsGkAtoms::flowlength, flowLength, nsINode::DeleteProperty< FlowLengthProperty>))), 0))) |
| 848 | nsGkAtoms::flowlength, flowLength,((bool)(__builtin_expect(!!(NS_FAILED_impl(mContent->SetProperty ( nsGkAtoms::flowlength, flowLength, nsINode::DeleteProperty< FlowLengthProperty>))), 0))) |
| 849 | nsINode::DeleteProperty<FlowLengthProperty>))((bool)(__builtin_expect(!!(NS_FAILED_impl(mContent->SetProperty ( nsGkAtoms::flowlength, flowLength, nsINode::DeleteProperty< FlowLengthProperty>))), 0)))) { |
| 850 | delete flowLength; |
| 851 | flowLength = nullptr; |
| 852 | } else { |
| 853 | mContent->SetFlags(NS_HAS_FLOWLENGTH_PROPERTY); |
| 854 | } |
| 855 | } |
| 856 | if (flowLength) { |
| 857 | flowLength->mStartOffset = mContentOffset; |
| 858 | flowLength->mEndFlowOffset = endFlow; |
| 859 | } |
| 860 | |
| 861 | return endFlow - mContentOffset; |
| 862 | } |
| 863 | |
| 864 | // Smarter versions of dom::IsSpaceCharacter. |
| 865 | // Unicode is really annoying; sometimes a space character isn't whitespace --- |
| 866 | // when it combines with another character |
| 867 | // So we have several versions of IsSpace for use in different contexts. |
| 868 | |
| 869 | static bool IsSpaceCombiningSequenceTail(const CharacterDataBuffer& aBuffer, |
| 870 | uint32_t aPos) { |
| 871 | NS_ASSERTION(aPos <= aBuffer.GetLength(), "Bad offset")do { if (!(aPos <= aBuffer.GetLength())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Bad offset", "aPos <= aBuffer.GetLength()", "./../../../layout/generic/nsTextFrame.cpp" , 871); MOZ_PretendNoReturn(); } } while (0); |
| 872 | if (!aBuffer.Is2b()) { |
| 873 | return false; |
| 874 | } |
| 875 | return nsTextFrameUtils::IsSpaceCombiningSequenceTail( |
| 876 | aBuffer.Get2b() + aPos, aBuffer.GetLength() - aPos); |
| 877 | } |
| 878 | |
| 879 | // Check whether aPos is a space for CSS 'word-spacing' purposes |
| 880 | static bool IsCSSWordSpacingSpace(const CharacterDataBuffer& aBuffer, |
| 881 | uint32_t aPos, const nsTextFrame* aFrame, |
| 882 | const nsStyleText* aStyleText) { |
| 883 | NS_ASSERTION(aPos < aBuffer.GetLength(), "No text for IsSpace!")do { if (!(aPos < aBuffer.GetLength())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "No text for IsSpace!", "aPos < aBuffer.GetLength()", "./../../../layout/generic/nsTextFrame.cpp" , 883); MOZ_PretendNoReturn(); } } while (0); |
| 884 | |
| 885 | char16_t ch = aBuffer.CharAt(aPos); |
| 886 | switch (ch) { |
| 887 | case ' ': |
| 888 | case CH_NBSP160: |
| 889 | return !IsSpaceCombiningSequenceTail(aBuffer, aPos + 1); |
| 890 | case '\r': |
| 891 | case '\t': |
| 892 | return !aStyleText->WhiteSpaceIsSignificant(); |
| 893 | case '\n': |
| 894 | return !aStyleText->NewlineIsSignificant(aFrame); |
| 895 | default: |
| 896 | return false; |
| 897 | } |
| 898 | } |
| 899 | |
| 900 | constexpr char16_t kOghamSpaceMark = 0x1680; |
| 901 | |
| 902 | // Check whether the string aChars/aLength starts with space that's |
| 903 | // trimmable according to CSS 'white-space:normal/nowrap'. |
| 904 | static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength) { |
| 905 | NS_ASSERTION(aLength > 0, "No text for IsSpace!")do { if (!(aLength > 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "No text for IsSpace!", "aLength > 0", "./../../../layout/generic/nsTextFrame.cpp" , 905); MOZ_PretendNoReturn(); } } while (0); |
| 906 | |
| 907 | char16_t ch = *aChars; |
| 908 | if (ch == ' ' || ch == kOghamSpaceMark) { |
| 909 | return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, |
| 910 | aLength - 1); |
| 911 | } |
| 912 | return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r'; |
| 913 | } |
| 914 | |
| 915 | // Check whether the character aCh is trimmable according to CSS |
| 916 | // 'white-space:normal/nowrap' |
| 917 | static bool IsTrimmableSpace(char aCh) { |
| 918 | return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r'; |
| 919 | } |
| 920 | |
| 921 | static bool IsTrimmableSpace(const CharacterDataBuffer& aBuffer, uint32_t aPos, |
| 922 | const nsStyleText* aStyleText, |
| 923 | bool aAllowHangingWS = false) { |
| 924 | NS_ASSERTION(aPos < aBuffer.GetLength(), "No text for IsSpace!")do { if (!(aPos < aBuffer.GetLength())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "No text for IsSpace!", "aPos < aBuffer.GetLength()", "./../../../layout/generic/nsTextFrame.cpp" , 924); MOZ_PretendNoReturn(); } } while (0); |
| 925 | |
| 926 | switch (aBuffer.CharAt(aPos)) { |
| 927 | case ' ': |
| 928 | case kOghamSpaceMark: |
| 929 | return (!aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS) && |
| 930 | !IsSpaceCombiningSequenceTail(aBuffer, aPos + 1); |
| 931 | case '\n': |
| 932 | return !aStyleText->NewlineIsSignificantStyle() && |
| 933 | aStyleText->mWhiteSpaceCollapse != |
| 934 | StyleWhiteSpaceCollapse::PreserveSpaces; |
| 935 | case '\t': |
| 936 | case '\r': |
| 937 | case '\f': |
| 938 | return !aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS; |
| 939 | default: |
| 940 | return false; |
| 941 | } |
| 942 | } |
| 943 | |
| 944 | static bool IsSelectionInlineWhitespace(const CharacterDataBuffer& aBuffer, |
| 945 | uint32_t aPos) { |
| 946 | NS_ASSERTION(aPos < aBuffer.GetLength(),do { if (!(aPos < aBuffer.GetLength())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "No text for IsSelectionInlineWhitespace!", "aPos < aBuffer.GetLength()" , "./../../../layout/generic/nsTextFrame.cpp", 947); MOZ_PretendNoReturn (); } } while (0) |
| 947 | "No text for IsSelectionInlineWhitespace!")do { if (!(aPos < aBuffer.GetLength())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "No text for IsSelectionInlineWhitespace!", "aPos < aBuffer.GetLength()" , "./../../../layout/generic/nsTextFrame.cpp", 947); MOZ_PretendNoReturn (); } } while (0); |
| 948 | char16_t ch = aBuffer.CharAt(aPos); |
| 949 | if (ch == ' ' || ch == CH_NBSP160) { |
| 950 | return !IsSpaceCombiningSequenceTail(aBuffer, aPos + 1); |
| 951 | } |
| 952 | return ch == '\t' || ch == '\f'; |
| 953 | } |
| 954 | |
| 955 | static bool IsSelectionNewline(const CharacterDataBuffer& aBuffer, |
| 956 | uint32_t aPos) { |
| 957 | NS_ASSERTION(aPos < aBuffer.GetLength(), "No text for IsSelectionNewline!")do { if (!(aPos < aBuffer.GetLength())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "No text for IsSelectionNewline!", "aPos < aBuffer.GetLength()" , "./../../../layout/generic/nsTextFrame.cpp", 957); MOZ_PretendNoReturn (); } } while (0); |
| 958 | char16_t ch = aBuffer.CharAt(aPos); |
| 959 | return ch == '\n' || ch == '\r'; |
| 960 | } |
| 961 | |
| 962 | // Count the amount of trimmable whitespace (as per CSS |
| 963 | // 'white-space:normal/nowrap') in a character buffer. The first |
| 964 | // character is at offset aStartOffset; the maximum number of characters |
| 965 | // to check is aLength. aDirection is -1 or 1 depending on whether we should |
| 966 | // progress backwards or forwards. |
| 967 | static uint32_t GetTrimmableWhitespaceCount(const CharacterDataBuffer& aBuffer, |
| 968 | int32_t aStartOffset, |
| 969 | int32_t aLength, |
| 970 | int32_t aDirection) { |
| 971 | if (!aLength) { |
| 972 | return 0; |
| 973 | } |
| 974 | |
| 975 | int32_t count = 0; |
| 976 | if (aBuffer.Is2b()) { |
| 977 | const char16_t* str = aBuffer.Get2b() + aStartOffset; |
| 978 | int32_t bufferLen = aBuffer.GetLength() - aStartOffset; |
| 979 | for (; count < aLength; ++count) { |
| 980 | if (!IsTrimmableSpace(str, bufferLen)) { |
| 981 | break; |
| 982 | } |
| 983 | str += aDirection; |
| 984 | bufferLen -= aDirection; |
| 985 | } |
| 986 | } else { |
| 987 | const char* str = aBuffer.Get1b() + aStartOffset; |
| 988 | for (; count < aLength; ++count) { |
| 989 | if (!IsTrimmableSpace(*str)) { |
| 990 | break; |
| 991 | } |
| 992 | str += aDirection; |
| 993 | } |
| 994 | } |
| 995 | return count; |
| 996 | } |
| 997 | |
| 998 | static bool IsAllWhitespace(const CharacterDataBuffer& aBuffer, |
| 999 | bool aAllowNewline) { |
| 1000 | if (aBuffer.Is2b()) { |
| 1001 | return false; |
| 1002 | } |
| 1003 | int32_t len = aBuffer.GetLength(); |
| 1004 | const char* str = aBuffer.Get1b(); |
| 1005 | for (int32_t i = 0; i < len; ++i) { |
| 1006 | char ch = str[i]; |
| 1007 | if (ch == ' ' || ch == '\t' || ch == '\r' || |
| 1008 | (ch == '\n' && aAllowNewline)) { |
| 1009 | continue; |
| 1010 | } |
| 1011 | return false; |
| 1012 | } |
| 1013 | return true; |
| 1014 | } |
| 1015 | |
| 1016 | static void ClearObserversFromTextRun(gfxTextRun* aTextRun) { |
| 1017 | if (!(aTextRun->GetFlags2() & |
| 1018 | nsTextFrameUtils::Flags::MightHaveGlyphChanges)) { |
| 1019 | return; |
| 1020 | } |
| 1021 | |
| 1022 | if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { |
| 1023 | static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData()) |
| 1024 | ->mGlyphObservers.Clear(); |
| 1025 | } else { |
| 1026 | static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) |
| 1027 | ->mGlyphObservers.Clear(); |
| 1028 | } |
| 1029 | } |
| 1030 | |
| 1031 | static void CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun) { |
| 1032 | if (!aTextRun->GetUserData()) { |
| 1033 | return; |
| 1034 | } |
| 1035 | |
| 1036 | ClearObserversFromTextRun(aTextRun); |
| 1037 | |
| 1038 | AutoTArray<gfxFont*, 4> fontsWithAnimatedGlyphs; |
| 1039 | uint32_t numGlyphRuns; |
| 1040 | const gfxTextRun::GlyphRun* run = aTextRun->GetGlyphRuns(&numGlyphRuns); |
| 1041 | while (numGlyphRuns--) { |
| 1042 | gfxFont* font = run->mFont; |
| 1043 | if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) { |
| 1044 | fontsWithAnimatedGlyphs.AppendElement(font); |
| 1045 | } |
| 1046 | run++; |
| 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 | for (auto& run : mCreatedTextRuns) { |
| 1130 | CreateObserversForAnimatedGlyphs(run); |
| 1131 | } |
| 1132 | NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared")do { if (!(mBreakSinks.IsEmpty())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Should have been cleared", "mBreakSinks.IsEmpty()", "./../../../layout/generic/nsTextFrame.cpp" , 1132); MOZ_PretendNoReturn(); } } while (0); |
| 1133 | NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared")do { if (!(mLineBreakBeforeFrames.IsEmpty())) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Should have been cleared", "mLineBreakBeforeFrames.IsEmpty()" , "./../../../layout/generic/nsTextFrame.cpp", 1133); MOZ_PretendNoReturn (); } } while (0); |
| 1134 | NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared")do { if (!(mMappedFlows.IsEmpty())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Should have been cleared", "mMappedFlows.IsEmpty()", "./../../../layout/generic/nsTextFrame.cpp" , 1134); MOZ_PretendNoReturn(); } } while (0); |
| 1135 | } |
| 1136 | |
| 1137 | void SetAtStartOfLine() { |
| 1138 | mStartOfLine = true; |
| 1139 | mCanStopOnThisLine = false; |
| 1140 | } |
| 1141 | void SetSkipIncompleteTextRuns(bool aSkip) { |
| 1142 | mSkipIncompleteTextRuns = aSkip; |
| 1143 | } |
| 1144 | void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) { |
| 1145 | mCommonAncestorWithLastFrame = aFrame; |
| 1146 | } |
| 1147 | bool CanStopOnThisLine() { return mCanStopOnThisLine; } |
| 1148 | nsIFrame* GetCommonAncestorWithLastFrame() { |
| 1149 | return mCommonAncestorWithLastFrame; |
| 1150 | } |
| 1151 | void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) { |
| 1152 | if (mCommonAncestorWithLastFrame && |
| 1153 | mCommonAncestorWithLastFrame->GetParent() == aFrame) { |
| 1154 | mCommonAncestorWithLastFrame = aFrame; |
| 1155 | } |
| 1156 | } |
| 1157 | void ScanFrame(nsIFrame* aFrame); |
| 1158 | bool IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun); |
| 1159 | void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak); |
| 1160 | void FlushLineBreaks(gfxTextRun* aTrailingTextRun); |
| 1161 | void ResetRunInfo() { |
| 1162 | mLastFrame = nullptr; |
| 1163 | mMappedFlows.Clear(); |
| 1164 | mLineBreakBeforeFrames.Clear(); |
| 1165 | mMaxTextLength = 0; |
| 1166 | mDoubleByteText = false; |
| 1167 | } |
| 1168 | void AccumulateRunInfo(nsTextFrame* aFrame); |
| 1169 | /** |
| 1170 | * @return null to indicate either textrun construction failed or |
| 1171 | * we constructed just a partial textrun to set up linebreaker and other |
| 1172 | * state for following textruns. |
| 1173 | */ |
| 1174 | already_AddRefed<gfxTextRun> BuildTextRunForFrames(void* aTextBuffer); |
| 1175 | bool SetupLineBreakerContext(gfxTextRun* aTextRun); |
| 1176 | void AssignTextRun(gfxTextRun* aTextRun, float aInflation); |
| 1177 | nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex); |
| 1178 | void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aTextPtr); |
| 1179 | void SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, const void* aTextPtr); |
| 1180 | struct FindBoundaryState { |
| 1181 | nsIFrame* mStopAtFrame; |
| 1182 | nsTextFrame* mFirstTextFrame; |
| 1183 | nsTextFrame* mLastTextFrame; |
| 1184 | bool mSeenTextRunBoundaryOnLaterLine; |
| 1185 | bool mSeenTextRunBoundaryOnThisLine; |
| 1186 | bool mSeenSpaceForLineBreakingOnThisLine; |
| 1187 | nsTArray<char16_t>& mBuffer; |
| 1188 | }; |
| 1189 | enum FindBoundaryResult { |
| 1190 | FB_CONTINUE, |
| 1191 | FB_STOPPED_AT_STOP_FRAME, |
| 1192 | FB_FOUND_VALID_TEXTRUN_BOUNDARY |
| 1193 | }; |
| 1194 | FindBoundaryResult FindBoundaries(nsIFrame* aFrame, |
| 1195 | FindBoundaryState* aState); |
| 1196 | |
| 1197 | bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2); |
| 1198 | |
| 1199 | // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame |
| 1200 | // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then |
| 1201 | // continuations starting from mStartFrame are a sequence of in-flow frames). |
| 1202 | struct MappedFlow { |
| 1203 | nsTextFrame* mStartFrame; |
| 1204 | nsTextFrame* mEndFrame; |
| 1205 | // When we consider breaking between elements, the nearest common |
| 1206 | // ancestor of the elements containing the characters is the one whose |
| 1207 | // CSS 'white-space' property governs. So this records the nearest common |
| 1208 | // ancestor of mStartFrame and the previous text frame, or null if there |
| 1209 | // was no previous text frame on this line. |
| 1210 | nsIFrame* mAncestorControllingInitialBreak; |
| 1211 | |
| 1212 | int32_t GetContentEnd() const { |
| 1213 | int32_t bufferLen = mStartFrame->CharacterDataBuffer().GetLength(); |
| 1214 | return mEndFrame ? std::min(bufferLen, mEndFrame->GetContentOffset()) |
| 1215 | : bufferLen; |
| 1216 | } |
| 1217 | }; |
| 1218 | |
| 1219 | class BreakSink final : public nsILineBreakSink { |
| 1220 | public: |
| 1221 | BreakSink(gfxTextRun* aTextRun, DrawTarget* aDrawTarget, |
| 1222 | uint32_t aOffsetIntoTextRun) |
| 1223 | : mTextRun(aTextRun), |
| 1224 | mDrawTarget(aDrawTarget), |
| 1225 | mOffsetIntoTextRun(aOffsetIntoTextRun) {} |
| 1226 | |
| 1227 | void SetBreaks(uint32_t aOffset, uint32_t aLength, |
| 1228 | uint8_t* aBreakBefore) final { |
| 1229 | gfxTextRun::Range range(aOffset + mOffsetIntoTextRun, |
| 1230 | aOffset + mOffsetIntoTextRun + aLength); |
| 1231 | if (mTextRun->SetPotentialLineBreaks(range, aBreakBefore)) { |
| 1232 | // Be conservative and assume that some breaks have been set |
| 1233 | mTextRun->ClearFlagBits(nsTextFrameUtils::Flags::NoBreaks); |
| 1234 | } |
| 1235 | } |
| 1236 | |
| 1237 | void SetCapitalization(uint32_t aOffset, uint32_t aLength, |
| 1238 | bool* aCapitalize) final { |
| 1239 | MOZ_ASSERT(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed,do { static_assert( mozilla::detail::AssertionConditionType< decltype(mTextRun->GetFlags2() & nsTextFrameUtils::Flags ::IsTransformed)>::isValid, "invalid assertion condition") ; if ((__builtin_expect(!!(!(!!(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed" " (" "Text run should be transformed!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 1240); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed" ") (" "Text run should be transformed!" ")"); do { MOZ_CrashSequence (__null, 1240); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 1240 | "Text run should be transformed!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(mTextRun->GetFlags2() & nsTextFrameUtils::Flags ::IsTransformed)>::isValid, "invalid assertion condition") ; if ((__builtin_expect(!!(!(!!(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed" " (" "Text run should be transformed!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 1240); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed" ") (" "Text run should be transformed!" ")"); do { MOZ_CrashSequence (__null, 1240); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 1241 | if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) { |
| 1242 | nsTransformedTextRun* transformedTextRun = |
| 1243 | static_cast<nsTransformedTextRun*>(mTextRun.get()); |
| 1244 | transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, |
| 1245 | aLength, aCapitalize); |
| 1246 | } |
| 1247 | } |
| 1248 | |
| 1249 | void Finish(gfxMissingFontRecorder* aMFR) { |
| 1250 | if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) { |
| 1251 | nsTransformedTextRun* transformedTextRun = |
| 1252 | static_cast<nsTransformedTextRun*>(mTextRun.get()); |
| 1253 | transformedTextRun->FinishSettingProperties(mDrawTarget, aMFR); |
| 1254 | } |
| 1255 | } |
| 1256 | |
| 1257 | RefPtr<gfxTextRun> mTextRun; |
| 1258 | DrawTarget* mDrawTarget; |
| 1259 | uint32_t mOffsetIntoTextRun; |
| 1260 | }; |
| 1261 | |
| 1262 | private: |
| 1263 | AutoTArray<MappedFlow, 10> mMappedFlows; |
| 1264 | AutoTArray<nsTextFrame*, 50> mLineBreakBeforeFrames; |
| 1265 | AutoTArray<UniquePtr<BreakSink>, 10> mBreakSinks; |
| 1266 | AutoTArray<RefPtr<gfxTextRun>, 10> mCreatedTextRuns; |
| 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")do { static_assert( mozilla::detail::AssertionConditionType< decltype(false)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: " "Unknown white-space-collapse value" ")", "./../../../layout/generic/nsTextFrame.cpp" , 1352); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "Unknown white-space-collapse value" ")"); do { MOZ_CrashSequence(__null, 1352); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 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(do { if (!(!result.mOverflowFrameToScan)) { NS_DebugBreak(NS_DEBUG_WARNING , "Scanning overflow inline frames is something we should avoid" , "!result.mOverflowFrameToScan", "./../../../layout/generic/nsTextFrame.cpp" , 1421); } } while (false) |
| 1420 | !result.mOverflowFrameToScan,do { if (!(!result.mOverflowFrameToScan)) { NS_DebugBreak(NS_DEBUG_WARNING , "Scanning overflow inline frames is something we should avoid" , "!result.mOverflowFrameToScan", "./../../../layout/generic/nsTextFrame.cpp" , 1421); } } while (false) |
| 1421 | "Scanning overflow inline frames is something we should avoid")do { if (!(!result.mOverflowFrameToScan)) { NS_DebugBreak(NS_DEBUG_WARNING , "Scanning overflow inline frames is something we should avoid" , "!result.mOverflowFrameToScan", "./../../../layout/generic/nsTextFrame.cpp" , 1421); } } while (false); |
| 1422 | result.mScanSiblings = true; |
| 1423 | result.mTextRunCanCrossFrameBoundary = true; |
| 1424 | result.mLineBreakerCanCrossFrameBoundary = true; |
| 1425 | } else { |
| 1426 | MOZ_ASSERT(!aFrame->IsRubyTextContainerFrame(),do { static_assert( mozilla::detail::AssertionConditionType< decltype(!aFrame->IsRubyTextContainerFrame())>::isValid , "invalid assertion condition"); if ((__builtin_expect(!!(!( !!(!aFrame->IsRubyTextContainerFrame()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!aFrame->IsRubyTextContainerFrame()" " (" "Shouldn't call this method for ruby text container" ")" , "./../../../layout/generic/nsTextFrame.cpp", 1427); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "!aFrame->IsRubyTextContainerFrame()" ") (" "Shouldn't call this method for ruby text container" ")"); do { MOZ_CrashSequence(__null, 1427); __attribute__((nomerge)) :: abort(); } while (false); } } while (false) |
| 1427 | "Shouldn't call this method for ruby text container")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!aFrame->IsRubyTextContainerFrame())>::isValid , "invalid assertion condition"); if ((__builtin_expect(!!(!( !!(!aFrame->IsRubyTextContainerFrame()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!aFrame->IsRubyTextContainerFrame()" " (" "Shouldn't call this method for ruby text container" ")" , "./../../../layout/generic/nsTextFrame.cpp", 1427); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "!aFrame->IsRubyTextContainerFrame()" ") (" "Shouldn't call this method for ruby text container" ")"); do { MOZ_CrashSequence(__null, 1427); __attribute__((nomerge)) :: abort(); } while (false); } } while (false); |
| 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 | const int32_t start = textFrame->GetContentOffset(); |
| 1471 | const int32_t end = textFrame->GetContentEnd(); |
| 1472 | const int32_t bufferLength = characterDataBuffer.GetLength(); |
| 1473 | |
| 1474 | if (MOZ_UNLIKELY(start < 0 || end < start || end > bufferLength)(__builtin_expect(!!(start < 0 || end < start || end > bufferLength), 0))) { |
| 1475 | MOZ_ASSERT_UNREACHABLE("Invalid text-frame content range")do { static_assert( mozilla::detail::AssertionConditionType< decltype(false)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: " "Invalid text-frame content range" ")", "./../../../layout/generic/nsTextFrame.cpp" , 1475); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "Invalid text-frame content range" ")"); do { MOZ_CrashSequence(__null, 1475); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 1476 | return FB_CONTINUE; |
| 1477 | } |
| 1478 | |
| 1479 | int32_t length = end - start; |
| 1480 | const void* text; |
| 1481 | const nsAtom* language = textFrame->StyleFont()->GetLangAtom(); |
| 1482 | if (characterDataBuffer.Is2b()) { |
| 1483 | // It is possible that we may end up removing all whitespace in |
| 1484 | // a piece of text because of The White Space Processing Rules, |
| 1485 | // so we need to transform it before we can check existence of |
| 1486 | // such whitespaces. |
| 1487 | aState->mBuffer.EnsureLengthAtLeast(length); |
| 1488 | nsTextFrameUtils::CompressionMode compression = |
| 1489 | GetCSSWhitespaceToCompressionMode(textFrame, textFrame->StyleText()); |
| 1490 | uint8_t incomingFlags = 0; |
| 1491 | gfxSkipChars skipChars; |
| 1492 | nsTextFrameUtils::Flags analysisFlags; |
| 1493 | char16_t* bufStart = aState->mBuffer.Elements(); |
| 1494 | char16_t* bufEnd = nsTextFrameUtils::TransformText( |
| 1495 | characterDataBuffer.Get2b() + start, length, bufStart, compression, |
| 1496 | &incomingFlags, &skipChars, &analysisFlags, language); |
| 1497 | text = bufStart; |
| 1498 | length = bufEnd - bufStart; |
| 1499 | } else { |
| 1500 | // If the text only contains ASCII characters, it is currently |
| 1501 | // impossible that TransformText would remove all whitespaces, |
| 1502 | // and thus the check below should return the same result for |
| 1503 | // transformed text and original text. So we don't need to try |
| 1504 | // transforming it here. |
| 1505 | text = static_cast<const void*>(characterDataBuffer.Get1b() + start); |
| 1506 | } |
| 1507 | if (TextContainsLineBreakerWhiteSpace(text, length, |
| 1508 | characterDataBuffer.Is2b())) { |
| 1509 | aState->mSeenSpaceForLineBreakingOnThisLine = true; |
| 1510 | if (aState->mSeenTextRunBoundaryOnLaterLine) { |
| 1511 | return FB_FOUND_VALID_TEXTRUN_BOUNDARY; |
| 1512 | } |
| 1513 | } |
| 1514 | return FB_CONTINUE; |
| 1515 | } |
| 1516 | |
| 1517 | FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame); |
| 1518 | if (!traversal.mTextRunCanCrossFrameBoundary) { |
| 1519 | aState->mSeenTextRunBoundaryOnThisLine = true; |
| 1520 | if (aState->mSeenSpaceForLineBreakingOnThisLine) { |
| 1521 | return FB_FOUND_VALID_TEXTRUN_BOUNDARY; |
| 1522 | } |
| 1523 | } |
| 1524 | |
| 1525 | for (nsIFrame* f = traversal.NextFrameToScan(); f; |
| 1526 | f = traversal.NextFrameToScan()) { |
| 1527 | FindBoundaryResult result = FindBoundaries(f, aState); |
| 1528 | if (result != FB_CONTINUE) { |
| 1529 | return result; |
| 1530 | } |
| 1531 | } |
| 1532 | |
| 1533 | if (!traversal.mTextRunCanCrossFrameBoundary) { |
| 1534 | aState->mSeenTextRunBoundaryOnThisLine = true; |
| 1535 | if (aState->mSeenSpaceForLineBreakingOnThisLine) { |
| 1536 | return FB_FOUND_VALID_TEXTRUN_BOUNDARY; |
| 1537 | } |
| 1538 | } |
| 1539 | |
| 1540 | return FB_CONTINUE; |
| 1541 | } |
| 1542 | |
| 1543 | // build text runs for the 200 lines following aForFrame, and stop after that |
| 1544 | // when we get a chance. |
| 1545 | #define NUM_LINES_TO_BUILD_TEXT_RUNS200 200 |
| 1546 | |
| 1547 | /** |
| 1548 | * General routine for building text runs. This is hairy because of the need |
| 1549 | * to build text runs that span content nodes. |
| 1550 | * |
| 1551 | * @param aContext The gfxContext we're using to construct this text run. |
| 1552 | * @param aForFrame The nsTextFrame for which we're building this text run. |
| 1553 | * @param aLineContainer the line container containing aForFrame; if null, |
| 1554 | * we'll walk the ancestors to find it. It's required to be non-null |
| 1555 | * when aForFrameLine is non-null. |
| 1556 | * @param aForFrameLine the line containing aForFrame; if null, we'll figure |
| 1557 | * out the line (slowly) |
| 1558 | * @param aWhichTextRun The type of text run we want to build. If font inflation |
| 1559 | * is enabled, this will be eInflated, otherwise it's eNotInflated. |
| 1560 | */ |
| 1561 | static void BuildTextRuns(DrawTarget* aDrawTarget, nsTextFrame* aForFrame, |
| 1562 | nsIFrame* aLineContainer, |
| 1563 | const nsLineList::iterator* aForFrameLine, |
| 1564 | nsTextFrame::TextRunType aWhichTextRun) { |
| 1565 | MOZ_ASSERT(aForFrame, "for no frame?")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aForFrame)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aForFrame))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aForFrame" " (" "for no frame?" ")", "./../../../layout/generic/nsTextFrame.cpp", 1565); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aForFrame" ") (" "for no frame?" ")"); do { MOZ_CrashSequence(__null, 1565); __attribute__((nomerge)) :: abort(); } while (false); } } while (false); |
| 1566 | NS_ASSERTION(!aForFrameLine || aLineContainer, "line but no line container")do { if (!(!aForFrameLine || aLineContainer)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "line but no line container", "!aForFrameLine || aLineContainer" , "./../../../layout/generic/nsTextFrame.cpp", 1566); MOZ_PretendNoReturn (); } } while (0); |
| 1567 | |
| 1568 | nsIFrame* lineContainerChild = aForFrame; |
| 1569 | if (!aLineContainer) { |
| 1570 | if (aForFrame->IsFloatingFirstLetterChild()) { |
| 1571 | lineContainerChild = aForFrame->GetParent()->GetPlaceholderFrame(); |
| 1572 | } |
| 1573 | aLineContainer = FindLineContainer(lineContainerChild); |
| 1574 | } else { |
| 1575 | NS_ASSERTION(do { if (!((aLineContainer == FindLineContainer(aForFrame) || (aLineContainer->IsLetterFrame() && aLineContainer ->IsFloating())))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Wrong line container hint" , "(aLineContainer == FindLineContainer(aForFrame) || (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating()))" , "./../../../layout/generic/nsTextFrame.cpp", 1578); MOZ_PretendNoReturn (); } } while (0) |
| 1576 | (aLineContainer == FindLineContainer(aForFrame) ||do { if (!((aLineContainer == FindLineContainer(aForFrame) || (aLineContainer->IsLetterFrame() && aLineContainer ->IsFloating())))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Wrong line container hint" , "(aLineContainer == FindLineContainer(aForFrame) || (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating()))" , "./../../../layout/generic/nsTextFrame.cpp", 1578); MOZ_PretendNoReturn (); } } while (0) |
| 1577 | (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating())),do { if (!((aLineContainer == FindLineContainer(aForFrame) || (aLineContainer->IsLetterFrame() && aLineContainer ->IsFloating())))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Wrong line container hint" , "(aLineContainer == FindLineContainer(aForFrame) || (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating()))" , "./../../../layout/generic/nsTextFrame.cpp", 1578); MOZ_PretendNoReturn (); } } while (0) |
| 1578 | "Wrong line container hint")do { if (!((aLineContainer == FindLineContainer(aForFrame) || (aLineContainer->IsLetterFrame() && aLineContainer ->IsFloating())))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Wrong line container hint" , "(aLineContainer == FindLineContainer(aForFrame) || (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating()))" , "./../../../layout/generic/nsTextFrame.cpp", 1578); MOZ_PretendNoReturn (); } } while (0); |
| 1579 | } |
| 1580 | |
| 1581 | if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) { |
| 1582 | aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML); |
| 1583 | if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) { |
| 1584 | aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI); |
| 1585 | } |
| 1586 | } |
| 1587 | if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) { |
| 1588 | aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT); |
| 1589 | } |
| 1590 | |
| 1591 | nsPresContext* presContext = aLineContainer->PresContext(); |
| 1592 | bool doLineBreaking = !aForFrame->IsInSVGTextSubtree(); |
| 1593 | BuildTextRunsScanner scanner(presContext, aDrawTarget, aLineContainer, |
| 1594 | aWhichTextRun, doLineBreaking); |
| 1595 | |
| 1596 | nsBlockFrame* block = do_QueryFrame(aLineContainer); |
| 1597 | |
| 1598 | if (!block) { |
| 1599 | nsIFrame* textRunContainer = aLineContainer; |
| 1600 | if (aLineContainer->IsRubyTextContainerFrame()) { |
| 1601 | textRunContainer = aForFrame; |
| 1602 | while (textRunContainer && !textRunContainer->IsRubyTextFrame()) { |
| 1603 | textRunContainer = textRunContainer->GetParent(); |
| 1604 | } |
| 1605 | MOZ_ASSERT(textRunContainer &&do { static_assert( mozilla::detail::AssertionConditionType< decltype(textRunContainer && textRunContainer->GetParent () == aLineContainer)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(textRunContainer && textRunContainer ->GetParent() == aLineContainer))), 0))) { do { } while (false ); MOZ_ReportAssertionFailure("textRunContainer && textRunContainer->GetParent() == aLineContainer" , "./../../../layout/generic/nsTextFrame.cpp", 1606); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "textRunContainer && textRunContainer->GetParent() == aLineContainer" ")"); do { MOZ_CrashSequence(__null, 1606); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
| 1606 | textRunContainer->GetParent() == aLineContainer)do { static_assert( mozilla::detail::AssertionConditionType< decltype(textRunContainer && textRunContainer->GetParent () == aLineContainer)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(textRunContainer && textRunContainer ->GetParent() == aLineContainer))), 0))) { do { } while (false ); MOZ_ReportAssertionFailure("textRunContainer && textRunContainer->GetParent() == aLineContainer" , "./../../../layout/generic/nsTextFrame.cpp", 1606); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "textRunContainer && textRunContainer->GetParent() == aLineContainer" ")"); do { MOZ_CrashSequence(__null, 1606); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 1607 | } else { |
| 1608 | NS_ASSERTION(do { if (!(!aLineContainer->GetPrevInFlow() && !aLineContainer ->GetNextInFlow())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Breakable non-block line containers other than " "ruby text container is not supported", "!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow()" , "./../../../layout/generic/nsTextFrame.cpp", 1611); MOZ_PretendNoReturn (); } } while (0) |
| 1609 | !aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),do { if (!(!aLineContainer->GetPrevInFlow() && !aLineContainer ->GetNextInFlow())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Breakable non-block line containers other than " "ruby text container is not supported", "!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow()" , "./../../../layout/generic/nsTextFrame.cpp", 1611); MOZ_PretendNoReturn (); } } while (0) |
| 1610 | "Breakable non-block line containers other than "do { if (!(!aLineContainer->GetPrevInFlow() && !aLineContainer ->GetNextInFlow())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Breakable non-block line containers other than " "ruby text container is not supported", "!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow()" , "./../../../layout/generic/nsTextFrame.cpp", 1611); MOZ_PretendNoReturn (); } } while (0) |
| 1611 | "ruby text container is not supported")do { if (!(!aLineContainer->GetPrevInFlow() && !aLineContainer ->GetNextInFlow())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Breakable non-block line containers other than " "ruby text container is not supported", "!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow()" , "./../../../layout/generic/nsTextFrame.cpp", 1611); MOZ_PretendNoReturn (); } } while (0); |
| 1612 | } |
| 1613 | // Just loop through all the children of the linecontainer ... it's really |
| 1614 | // just one line |
| 1615 | scanner.SetAtStartOfLine(); |
| 1616 | scanner.SetCommonAncestorWithLastFrame(nullptr); |
| 1617 | for (nsIFrame* child : textRunContainer->PrincipalChildList()) { |
| 1618 | scanner.ScanFrame(child); |
| 1619 | } |
| 1620 | // Set mStartOfLine so FlushFrames knows its textrun ends a line |
| 1621 | scanner.SetAtStartOfLine(); |
| 1622 | scanner.FlushFrames(true, false); |
| 1623 | return; |
| 1624 | } |
| 1625 | |
| 1626 | // Find the line containing 'lineContainerChild'. |
| 1627 | |
| 1628 | bool isValid = true; |
| 1629 | nsBlockInFlowLineIterator backIterator(block, &isValid); |
| 1630 | if (aForFrameLine) { |
| 1631 | backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine); |
| 1632 | } else { |
| 1633 | backIterator = |
| 1634 | nsBlockInFlowLineIterator(block, lineContainerChild, &isValid); |
| 1635 | NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us")do { if (!(isValid)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aForFrame not found in block, someone lied to us" , "isValid", "./../../../layout/generic/nsTextFrame.cpp", 1635 ); MOZ_PretendNoReturn(); } } while (0); |
| 1636 | NS_ASSERTION(backIterator.GetContainer() == block,do { if (!(backIterator.GetContainer() == block)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Someone lied to us about the block", "backIterator.GetContainer() == block" , "./../../../layout/generic/nsTextFrame.cpp", 1637); MOZ_PretendNoReturn (); } } while (0) |
| 1637 | "Someone lied to us about the block")do { if (!(backIterator.GetContainer() == block)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Someone lied to us about the block", "backIterator.GetContainer() == block" , "./../../../layout/generic/nsTextFrame.cpp", 1637); MOZ_PretendNoReturn (); } } while (0); |
| 1638 | } |
| 1639 | nsBlockFrame::LineIterator startLine = backIterator.GetLine(); |
| 1640 | |
| 1641 | // Find a line where we can start building text runs. We choose the last line |
| 1642 | // where: |
| 1643 | // -- there is a textrun boundary between the start of the line and the |
| 1644 | // start of aForFrame |
| 1645 | // -- there is a space between the start of the line and the textrun boundary |
| 1646 | // (this is so we can be sure the line breaks will be set properly |
| 1647 | // on the textruns we construct). |
| 1648 | // The possibly-partial text runs up to and including the first space |
| 1649 | // are not reconstructed. We construct partial text runs for that text --- |
| 1650 | // for the sake of simplifying the code and feeding the linebreaker --- |
| 1651 | // but we discard them instead of assigning them to frames. |
| 1652 | // This is a little awkward because we traverse lines in the reverse direction |
| 1653 | // but we traverse the frames in each line in the forward direction. |
| 1654 | nsBlockInFlowLineIterator forwardIterator = backIterator; |
| 1655 | nsIFrame* stopAtFrame = lineContainerChild; |
| 1656 | nsTextFrame* nextLineFirstTextFrame = nullptr; |
| 1657 | AutoTArray<char16_t, BIG_TEXT_NODE_SIZE4096> buffer; |
| 1658 | bool seenTextRunBoundaryOnLaterLine = false; |
| 1659 | bool mayBeginInTextRun = true; |
| 1660 | while (true) { |
| 1661 | forwardIterator = backIterator; |
| 1662 | nsBlockFrame::LineIterator line = backIterator.GetLine(); |
| 1663 | if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) { |
| 1664 | mayBeginInTextRun = false; |
| 1665 | break; |
| 1666 | } |
| 1667 | |
| 1668 | BuildTextRunsScanner::FindBoundaryState state = { |
| 1669 | stopAtFrame, nullptr, nullptr, bool(seenTextRunBoundaryOnLaterLine), |
| 1670 | false, false, buffer}; |
| 1671 | bool foundBoundary = false; |
| 1672 | for (nsIFrame* child : line->ChildFrames()) { |
| 1673 | BuildTextRunsScanner::FindBoundaryResult result = |
| 1674 | scanner.FindBoundaries(child, &state); |
| 1675 | if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) { |
| 1676 | foundBoundary = true; |
| 1677 | break; |
| 1678 | } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) { |
| 1679 | break; |
| 1680 | } |
| 1681 | } |
| 1682 | if (foundBoundary) { |
| 1683 | break; |
| 1684 | } |
| 1685 | if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame && |
| 1686 | !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame, |
| 1687 | nextLineFirstTextFrame)) { |
| 1688 | // Found a usable textrun boundary at the end of the line |
| 1689 | if (state.mSeenSpaceForLineBreakingOnThisLine) { |
| 1690 | break; |
| 1691 | } |
| 1692 | seenTextRunBoundaryOnLaterLine = true; |
| 1693 | } else if (state.mSeenTextRunBoundaryOnThisLine) { |
| 1694 | seenTextRunBoundaryOnLaterLine = true; |
| 1695 | } |
| 1696 | stopAtFrame = nullptr; |
| 1697 | if (state.mFirstTextFrame) { |
| 1698 | nextLineFirstTextFrame = state.mFirstTextFrame; |
| 1699 | } |
| 1700 | } |
| 1701 | scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun); |
| 1702 | |
| 1703 | // Now iterate over all text frames starting from the current line. |
| 1704 | // First-in-flow text frames will be accumulated into textRunFrames as we go. |
| 1705 | // When a text run boundary is required we flush textRunFrames ((re)building |
| 1706 | // their gfxTextRuns as necessary). |
| 1707 | bool seenStartLine = false; |
| 1708 | uint32_t linesAfterStartLine = 0; |
| 1709 | do { |
| 1710 | nsBlockFrame::LineIterator line = forwardIterator.GetLine(); |
| 1711 | if (line->IsBlock()) { |
| 1712 | break; |
| 1713 | } |
| 1714 | line->SetInvalidateTextRuns(false); |
| 1715 | scanner.SetAtStartOfLine(); |
| 1716 | scanner.SetCommonAncestorWithLastFrame(nullptr); |
| 1717 | for (nsIFrame* child : line->ChildFrames()) { |
| 1718 | scanner.ScanFrame(child); |
| 1719 | } |
| 1720 | if (line.get() == startLine.get()) { |
| 1721 | seenStartLine = true; |
| 1722 | } |
| 1723 | if (seenStartLine) { |
| 1724 | ++linesAfterStartLine; |
| 1725 | if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS200 && |
| 1726 | scanner.CanStopOnThisLine()) { |
| 1727 | // Don't flush frames; we may be in the middle of a textrun |
| 1728 | // that we can't end here. That's OK, we just won't build it. |
| 1729 | // Note that we must already have finished the textrun for aForFrame, |
| 1730 | // because we've seen the end of a textrun in a line after the line |
| 1731 | // containing aForFrame. |
| 1732 | scanner.FlushLineBreaks(nullptr); |
| 1733 | // This flushes out mMappedFlows and mLineBreakBeforeFrames, which |
| 1734 | // silences assertions in the scanner destructor. |
| 1735 | scanner.ResetRunInfo(); |
| 1736 | return; |
| 1737 | } |
| 1738 | } |
| 1739 | } while (forwardIterator.Next()); |
| 1740 | |
| 1741 | // Set mStartOfLine so FlushFrames knows its textrun ends a line |
| 1742 | scanner.SetAtStartOfLine(); |
| 1743 | scanner.FlushFrames(true, false); |
| 1744 | } |
| 1745 | |
| 1746 | static char16_t* ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount) { |
| 1747 | while (aCount) { |
| 1748 | *aDest = *aSrc; |
| 1749 | ++aDest; |
| 1750 | ++aSrc; |
| 1751 | --aCount; |
| 1752 | } |
| 1753 | return aDest; |
| 1754 | } |
| 1755 | |
| 1756 | bool BuildTextRunsScanner::IsTextRunValidForMappedFlows( |
| 1757 | const gfxTextRun* aTextRun) { |
| 1758 | if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { |
| 1759 | return mMappedFlows.Length() == 1 && |
| 1760 | mMappedFlows[0].mStartFrame == GetFrameForSimpleFlow(aTextRun) && |
| 1761 | mMappedFlows[0].mEndFrame == nullptr; |
| 1762 | } |
| 1763 | |
| 1764 | auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData()); |
| 1765 | TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun); |
| 1766 | if (userData->mMappedFlowCount != mMappedFlows.Length()) { |
| 1767 | return false; |
| 1768 | } |
| 1769 | for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { |
| 1770 | if (userMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame || |
| 1771 | int32_t(userMappedFlows[i].mContentLength) != |
| 1772 | mMappedFlows[i].GetContentEnd() - |
| 1773 | mMappedFlows[i].mStartFrame->GetContentOffset()) { |
| 1774 | return false; |
| 1775 | } |
| 1776 | } |
| 1777 | return true; |
| 1778 | } |
| 1779 | |
| 1780 | /** |
| 1781 | * This gets called when we need to make a text run for the current list of |
| 1782 | * frames. |
| 1783 | */ |
| 1784 | void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks, |
| 1785 | bool aSuppressTrailingBreak) { |
| 1786 | RefPtr<gfxTextRun> textRun; |
| 1787 | if (!mMappedFlows.IsEmpty()) { |
| 1788 | if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun && |
| 1789 | !!(mCurrentFramesAllSameTextRun->GetFlags2() & |
| 1790 | nsTextFrameUtils::Flags::IncomingWhitespace) == |
| 1791 | !!(mCurrentRunContextInfo & |
| 1792 | nsTextFrameUtils::INCOMING_WHITESPACE) && |
| 1793 | !!(mCurrentFramesAllSameTextRun->GetFlags() & |
| 1794 | gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR) == |
| 1795 | !!(mCurrentRunContextInfo & |
| 1796 | nsTextFrameUtils::INCOMING_ARABICCHAR) && |
| 1797 | IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) { |
| 1798 | // Optimization: We do not need to (re)build the textrun. |
| 1799 | textRun = mCurrentFramesAllSameTextRun; |
| 1800 | |
| 1801 | if (mDoLineBreaking) { |
| 1802 | // Feed this run's text into the linebreaker to provide context. |
| 1803 | if (!SetupLineBreakerContext(textRun)) { |
| 1804 | return; |
| 1805 | } |
| 1806 | } |
| 1807 | |
| 1808 | // Update mNextRunContextInfo appropriately |
| 1809 | mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE; |
| 1810 | if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TrailingWhitespace) { |
| 1811 | mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE; |
| 1812 | } |
| 1813 | if (textRun->GetFlags() & |
| 1814 | gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR) { |
| 1815 | mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR; |
| 1816 | } |
| 1817 | } else { |
| 1818 | AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE4096> buffer; |
| 1819 | uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1); |
| 1820 | if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX(4294967295U) || |
| 1821 | !buffer.AppendElements(bufferSize, fallible)) { |
| 1822 | return; |
| 1823 | } |
| 1824 | textRun = BuildTextRunForFrames(buffer.Elements()); |
| 1825 | if (textRun) { |
| 1826 | if (gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) { |
| 1827 | // Record the textrun for call to CreateObserversForAnimatedGlyphs, |
| 1828 | // done after line-breaking etc is complete. |
| 1829 | mCreatedTextRuns.AppendElement(textRun); |
| 1830 | } |
| 1831 | } |
| 1832 | } |
| 1833 | } |
| 1834 | |
| 1835 | if (aFlushLineBreaks) { |
| 1836 | FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun.get()); |
| 1837 | } |
| 1838 | |
| 1839 | mCanStopOnThisLine = true; |
| 1840 | ResetRunInfo(); |
| 1841 | } |
| 1842 | |
| 1843 | void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun) { |
| 1844 | // If the line-breaker is buffering a potentially-unfinished word, |
| 1845 | // preserve the state of being in-word so that we don't spuriously |
| 1846 | // capitalize the next letter. |
| 1847 | bool inWord = mLineBreaker.InWord(); |
| 1848 | bool trailingLineBreak; |
| 1849 | nsresult rv = mLineBreaker.Reset(&trailingLineBreak); |
| 1850 | mLineBreaker.SetWordContinuation(inWord); |
| 1851 | // textRun may be null for various reasons, including because we constructed |
| 1852 | // a partial textrun just to get the linebreaker and other state set up |
| 1853 | // to build the next textrun. |
| 1854 | if (NS_SUCCEEDED(rv)((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1))) && trailingLineBreak && aTrailingTextRun) { |
| 1855 | aTrailingTextRun->SetFlagBits(nsTextFrameUtils::Flags::HasTrailingBreak); |
| 1856 | } |
| 1857 | |
| 1858 | for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) { |
| 1859 | // TODO cause frames associated with the textrun to be reflowed, if they |
| 1860 | // aren't being reflowed already! |
| 1861 | mBreakSinks[i]->Finish(mMissingFonts); |
| 1862 | } |
| 1863 | mBreakSinks.Clear(); |
| 1864 | } |
| 1865 | |
| 1866 | void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame) { |
| 1867 | if (mMaxTextLength != UINT32_MAX(4294967295U)) { |
| 1868 | NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(),do { if (!(mMaxTextLength < (4294967295U) - aFrame->GetContentLength ())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "integer overflow", "mMaxTextLength < UINT32_MAX - aFrame->GetContentLength()" , "./../../../layout/generic/nsTextFrame.cpp", 1869); MOZ_PretendNoReturn (); } } while (0) |
| 1869 | "integer overflow")do { if (!(mMaxTextLength < (4294967295U) - aFrame->GetContentLength ())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "integer overflow", "mMaxTextLength < UINT32_MAX - aFrame->GetContentLength()" , "./../../../layout/generic/nsTextFrame.cpp", 1869); MOZ_PretendNoReturn (); } } while (0); |
| 1870 | if (mMaxTextLength >= UINT32_MAX(4294967295U) - aFrame->GetContentLength()) { |
| 1871 | mMaxTextLength = UINT32_MAX(4294967295U); |
| 1872 | } else { |
| 1873 | mMaxTextLength += aFrame->GetContentLength(); |
| 1874 | } |
| 1875 | } |
| 1876 | mDoubleByteText |= aFrame->CharacterDataBuffer().Is2b(); |
| 1877 | mLastFrame = aFrame; |
| 1878 | mCommonAncestorWithLastFrame = aFrame->GetParent(); |
| 1879 | |
| 1880 | MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; |
| 1881 | NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||do { if (!(mappedFlow->mStartFrame == aFrame || mappedFlow ->GetContentEnd() == aFrame->GetContentOffset())) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Overlapping or discontiguous frames => BAD" , "mappedFlow->mStartFrame == aFrame || mappedFlow->GetContentEnd() == aFrame->GetContentOffset()" , "./../../../layout/generic/nsTextFrame.cpp", 1883); MOZ_PretendNoReturn (); } } while (0) |
| 1882 | mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),do { if (!(mappedFlow->mStartFrame == aFrame || mappedFlow ->GetContentEnd() == aFrame->GetContentOffset())) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Overlapping or discontiguous frames => BAD" , "mappedFlow->mStartFrame == aFrame || mappedFlow->GetContentEnd() == aFrame->GetContentOffset()" , "./../../../layout/generic/nsTextFrame.cpp", 1883); MOZ_PretendNoReturn (); } } while (0) |
| 1883 | "Overlapping or discontiguous frames => BAD")do { if (!(mappedFlow->mStartFrame == aFrame || mappedFlow ->GetContentEnd() == aFrame->GetContentOffset())) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Overlapping or discontiguous frames => BAD" , "mappedFlow->mStartFrame == aFrame || mappedFlow->GetContentEnd() == aFrame->GetContentOffset()" , "./../../../layout/generic/nsTextFrame.cpp", 1883); MOZ_PretendNoReturn (); } } while (0); |
| 1884 | mappedFlow->mEndFrame = aFrame->GetNextContinuation(); |
| 1885 | if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) { |
| 1886 | mCurrentFramesAllSameTextRun = nullptr; |
| 1887 | } |
| 1888 | |
| 1889 | if (mStartOfLine) { |
| 1890 | mLineBreakBeforeFrames.AppendElement(aFrame); |
| 1891 | mStartOfLine = false; |
| 1892 | } |
| 1893 | |
| 1894 | // Default limits used by `hyphenate-limit-chars` for `auto` components, as |
| 1895 | // suggested by the CSS Text spec. |
| 1896 | // TODO: consider making these sensitive to the context, e.g. increasing the |
| 1897 | // values for long line lengths to reduce the tendency to hyphenate too much. |
| 1898 | const uint32_t kDefaultHyphenateTotalWordLength = 5; |
| 1899 | const uint32_t kDefaultHyphenatePreBreakLength = 2; |
| 1900 | const uint32_t kDefaultHyphenatePostBreakLength = 2; |
| 1901 | |
| 1902 | const auto& hyphenateLimitChars = aFrame->StyleText()->mHyphenateLimitChars; |
| 1903 | uint32_t pre = |
| 1904 | hyphenateLimitChars.pre_hyphen_length.IsAuto() |
| 1905 | ? kDefaultHyphenatePreBreakLength |
| 1906 | : std::max(0, hyphenateLimitChars.pre_hyphen_length.AsNumber()); |
| 1907 | uint32_t post = |
| 1908 | hyphenateLimitChars.post_hyphen_length.IsAuto() |
| 1909 | ? kDefaultHyphenatePostBreakLength |
| 1910 | : std::max(0, hyphenateLimitChars.post_hyphen_length.AsNumber()); |
| 1911 | uint32_t total = |
| 1912 | hyphenateLimitChars.total_word_length.IsAuto() |
| 1913 | ? kDefaultHyphenateTotalWordLength |
| 1914 | : std::max(0, hyphenateLimitChars.total_word_length.AsNumber()); |
| 1915 | total = std::max(total, pre + post); |
| 1916 | mLineBreaker.SetHyphenateLimitChars(total, pre, post); |
| 1917 | } |
| 1918 | |
| 1919 | static bool HasTerminalNewline(const nsTextFrame* aFrame) { |
| 1920 | if (aFrame->GetContentLength() == 0) { |
| 1921 | return false; |
| 1922 | } |
| 1923 | const CharacterDataBuffer& characterDataBuffer = |
| 1924 | aFrame->CharacterDataBuffer(); |
| 1925 | return characterDataBuffer.CharAt( |
| 1926 | AssertedCast<uint32_t>(aFrame->GetContentEnd()) - 1) == '\n'; |
| 1927 | } |
| 1928 | |
| 1929 | static gfxFont::Metrics GetFirstFontMetrics(gfxFontGroup* aFontGroup, |
| 1930 | bool aVerticalMetrics) { |
| 1931 | if (!aFontGroup) { |
| 1932 | return gfxFont::Metrics(); |
| 1933 | } |
| 1934 | RefPtr<gfxFont> font = aFontGroup->GetFirstValidFont(); |
| 1935 | return font->GetMetrics(aVerticalMetrics ? nsFontMetrics::eVertical |
| 1936 | : nsFontMetrics::eHorizontal); |
| 1937 | } |
| 1938 | |
| 1939 | static gfxFloat GetMinTabAdvanceAppUnits(const gfxTextRun* aTextRun) { |
| 1940 | gfxFloat chWidthAppUnits = NS_round( |
| 1941 | GetFirstFontMetrics(aTextRun->GetFontGroup(), aTextRun->IsVertical()) |
| 1942 | .ZeroOrAveCharWidth() * |
| 1943 | aTextRun->GetAppUnitsPerDevUnit()); |
| 1944 | return 0.5 * chWidthAppUnits; |
| 1945 | } |
| 1946 | |
| 1947 | static float GetSVGFontSizeScaleFactor(nsIFrame* aFrame) { |
| 1948 | if (!aFrame->IsInSVGTextSubtree()) { |
| 1949 | return 1.0f; |
| 1950 | } |
| 1951 | auto* container = |
| 1952 | nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText); |
| 1953 | MOZ_ASSERT(container)do { static_assert( mozilla::detail::AssertionConditionType< decltype(container)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(container))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("container", "./../../../layout/generic/nsTextFrame.cpp" , 1953); AnnotateMozCrashReason("MOZ_ASSERT" "(" "container" ")" ); do { MOZ_CrashSequence(__null, 1953); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 1954 | return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor(); |
| 1955 | } |
| 1956 | |
| 1957 | static nscoord ResolveLetterSpacing(nsIFrame* aFrame, |
| 1958 | const nsStyleText& aStyleText) { |
| 1959 | if (aFrame->IsInSVGTextSubtree()) { |
| 1960 | // SVG text can have a scaling factor applied so that very small or very |
| 1961 | // large font-sizes don't suffer from poor glyph placement due to app unit |
| 1962 | // rounding. The used letter-spacing value must be scaled by the same |
| 1963 | // factor. |
| 1964 | return GetSVGFontSizeScaleFactor(aFrame) * |
| 1965 | aStyleText.mLetterSpacing.Resolve( |
| 1966 | [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); }); |
| 1967 | } |
| 1968 | |
| 1969 | return aStyleText.mLetterSpacing.Resolve( |
| 1970 | [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); }); |
| 1971 | } |
| 1972 | |
| 1973 | // This function converts non-coord values (e.g. percentages) to nscoord. |
| 1974 | static nscoord ResolveWordSpacing(nsIFrame* aFrame, |
| 1975 | const nsStyleText& aStyleText) { |
| 1976 | if (aFrame->IsInSVGTextSubtree()) { |
| 1977 | // SVG text can have a scaling factor applied so that very small or very |
| 1978 | // large font-sizes don't suffer from poor glyph placement due to app unit |
| 1979 | // rounding. The used word-spacing value must be scaled by the same |
| 1980 | // factor. |
| 1981 | return GetSVGFontSizeScaleFactor(aFrame) * |
| 1982 | aStyleText.mWordSpacing.Resolve( |
| 1983 | [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); }); |
| 1984 | } |
| 1985 | |
| 1986 | return aStyleText.mWordSpacing.Resolve( |
| 1987 | [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); }); |
| 1988 | } |
| 1989 | |
| 1990 | gfx::ShapedTextFlags nsTextFrame::GetSpacingFlags() const { |
| 1991 | const nsStyleText* styleText = StyleText(); |
| 1992 | const auto& ls = styleText->mLetterSpacing; |
| 1993 | const auto& ws = styleText->mWordSpacing; |
| 1994 | |
| 1995 | // It's possible to have a calc() value that computes to zero but for which |
| 1996 | // IsDefinitelyZero() is false, in which case we'll return |
| 1997 | // TEXT_ENABLE_SPACING unnecessarily. That's ok because such cases are likely |
| 1998 | // to be rare, and avoiding TEXT_ENABLE_SPACING is just an optimization. |
| 1999 | bool nonStandardSpacing = |
| 2000 | !ls.IsDefinitelyZero() || !ws.IsDefinitelyZero() || |
| 2001 | TextAutospace::Enabled(styleText->EffectiveTextAutospace(), this); |
| 2002 | return nonStandardSpacing ? gfx::ShapedTextFlags::TEXT_ENABLE_SPACING |
| 2003 | : gfx::ShapedTextFlags(); |
| 2004 | } |
| 2005 | |
| 2006 | // Returns true if the frame has alignment-baseline and baseline-shift |
| 2007 | // set to their initial values. |
| 2008 | static bool HasDefaultVerticalAlignment(const nsIFrame* aFrame) { |
| 2009 | const auto& alignmentBaseline = aFrame->StyleDisplay()->mAlignmentBaseline; |
| 2010 | if (alignmentBaseline != StyleAlignmentBaseline::Baseline) { |
| 2011 | return false; |
| 2012 | } |
| 2013 | |
| 2014 | const auto& baselineShift = aFrame->StyleDisplay()->mBaselineShift; |
| 2015 | if (baselineShift.IsKeyword() || |
| 2016 | !baselineShift.AsLength().IsDefinitelyZero()) { |
| 2017 | return false; |
| 2018 | } |
| 2019 | |
| 2020 | return true; |
| 2021 | } |
| 2022 | |
| 2023 | bool BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, |
| 2024 | nsTextFrame* aFrame2) { |
| 2025 | // We don't need to check font size inflation, since |
| 2026 | // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|) |
| 2027 | // ensures that text runs never cross block boundaries. This means |
| 2028 | // that the font size inflation on all text frames in the text run is |
| 2029 | // already guaranteed to be the same as each other (and for the line |
| 2030 | // container). |
| 2031 | if (mBidiEnabled) { |
| 2032 | FrameBidiData data1 = aFrame1->GetBidiData(); |
| 2033 | FrameBidiData data2 = aFrame2->GetBidiData(); |
| 2034 | if (data1.embeddingLevel != data2.embeddingLevel || |
| 2035 | data2.precedingControl != kBidiLevelNone) { |
| 2036 | return false; |
| 2037 | } |
| 2038 | } |
| 2039 | |
| 2040 | ComputedStyle* sc1 = aFrame1->Style(); |
| 2041 | ComputedStyle* sc2 = aFrame2->Style(); |
| 2042 | |
| 2043 | // Any difference in writing-mode/directionality inhibits shaping across |
| 2044 | // the boundary. |
| 2045 | WritingMode wm(sc1); |
| 2046 | if (wm != WritingMode(sc2)) { |
| 2047 | return false; |
| 2048 | } |
| 2049 | |
| 2050 | const nsStyleText* textStyle1 = sc1->StyleText(); |
| 2051 | // If the first frame ends in a preformatted newline, then we end the textrun |
| 2052 | // here. This avoids creating giant textruns for an entire plain text file. |
| 2053 | // Note that we create a single text frame for a preformatted text node, |
| 2054 | // even if it has newlines in it, so typically we won't see trailing newlines |
| 2055 | // until after reflow has broken up the frame into one (or more) frames per |
| 2056 | // line. That's OK though. |
| 2057 | if (textStyle1->NewlineIsSignificant(aFrame1) && |
| 2058 | HasTerminalNewline(aFrame1)) { |
| 2059 | return false; |
| 2060 | } |
| 2061 | |
| 2062 | if (aFrame1->GetParent()->GetContent() != |
| 2063 | aFrame2->GetParent()->GetContent()) { |
| 2064 | // Does aFrame, or any ancestor between it and aAncestor, have a property |
| 2065 | // that should inhibit cross-element-boundary shaping on aSide? |
| 2066 | auto PreventCrossBoundaryShaping = |
| 2067 | [](const nsIFrame* aFrame, const nsIFrame* aAncestor, Side aSide) { |
| 2068 | while (aFrame != aAncestor) { |
| 2069 | ComputedStyle* ctx = aFrame->Style(); |
| 2070 | const auto anchorResolutionParams = |
| 2071 | AnchorPosResolutionParams::From(aFrame); |
| 2072 | // According to https://drafts.csswg.org/css-text/#boundary-shaping: |
| 2073 | // |
| 2074 | // Text shaping must be broken at inline box boundaries when any of |
| 2075 | // the following are true for any box whose boundary separates the |
| 2076 | // two typographic character units: |
| 2077 | // |
| 2078 | // 1. Any of margin/border/padding separating the two typographic |
| 2079 | // character units in the inline axis is non-zero. |
| 2080 | const auto margin = |
| 2081 | ctx->StyleMargin()->GetMargin(aSide, anchorResolutionParams); |
| 2082 | if (!margin->ConvertsToLength() || |
| 2083 | margin->AsLengthPercentage().ToLength() != 0) { |
| 2084 | return true; |
| 2085 | } |
| 2086 | const auto& padding = ctx->StylePadding()->mPadding.Get(aSide); |
| 2087 | if (!padding.ConvertsToLength() || padding.ToLength() != 0) { |
| 2088 | return true; |
| 2089 | } |
| 2090 | if (ctx->StyleBorder()->GetComputedBorderWidth(aSide) != 0) { |
| 2091 | return true; |
| 2092 | } |
| 2093 | |
| 2094 | // 2. vertical-align is not baseline. |
| 2095 | if (!HasDefaultVerticalAlignment(aFrame)) { |
| 2096 | return true; |
| 2097 | } |
| 2098 | |
| 2099 | // 3. The boundary is a bidi isolation boundary. |
| 2100 | const auto unicodeBidi = ctx->StyleTextReset()->mUnicodeBidi; |
| 2101 | if (unicodeBidi == StyleUnicodeBidi::Isolate || |
| 2102 | unicodeBidi == StyleUnicodeBidi::IsolateOverride) { |
| 2103 | return true; |
| 2104 | } |
| 2105 | |
| 2106 | aFrame = aFrame->GetParent(); |
| 2107 | } |
| 2108 | return false; |
| 2109 | }; |
| 2110 | |
| 2111 | const nsIFrame* ancestor = |
| 2112 | nsLayoutUtils::FindNearestCommonAncestorFrameWithinBlock(aFrame1, |
| 2113 | aFrame2); |
| 2114 | |
| 2115 | if (!ancestor) { |
| 2116 | // The two frames are within different blocks, e.g. due to block |
| 2117 | // fragmentation. In theory we shouldn't prevent cross-frame shaping |
| 2118 | // here, but it's an edge case where we should rarely decide to allow |
| 2119 | // cross-frame shaping, so we don't try harder here. |
| 2120 | return false; |
| 2121 | } |
| 2122 | |
| 2123 | // We inhibit cross-element-boundary shaping if we're in SVG content, |
| 2124 | // as there are too many things SVG might be doing (like applying per- |
| 2125 | // element positioning) that wouldn't make sense with shaping across |
| 2126 | // the boundary. |
| 2127 | if (ancestor->IsInSVGTextSubtree()) { |
| 2128 | return false; |
| 2129 | } |
| 2130 | |
| 2131 | // Map inline-end and inline-start to physical sides for checking presence |
| 2132 | // of non-zero margin/border/padding. |
| 2133 | Side side1 = wm.PhysicalSide(LogicalSide::IEnd); |
| 2134 | Side side2 = wm.PhysicalSide(LogicalSide::IStart); |
| 2135 | // If the frames have an embedding level that is opposite to the writing |
| 2136 | // mode, we need to swap which sides we're checking. |
| 2137 | if (aFrame1->GetEmbeddingLevel().IsRTL() == wm.IsBidiLTR()) { |
| 2138 | std::swap(side1, side2); |
| 2139 | } |
| 2140 | |
| 2141 | if (PreventCrossBoundaryShaping(aFrame1, ancestor, side1) || |
| 2142 | PreventCrossBoundaryShaping(aFrame2, ancestor, side2)) { |
| 2143 | return false; |
| 2144 | } |
| 2145 | } |
| 2146 | |
| 2147 | if (aFrame1->GetContent() == aFrame2->GetContent() && |
| 2148 | aFrame1->GetNextInFlow() != aFrame2) { |
| 2149 | // aFrame2 must be a non-fluid continuation of aFrame1. This can happen |
| 2150 | // sometimes when the unicode-bidi property is used; the bidi resolver |
| 2151 | // breaks text into different frames even though the text has the same |
| 2152 | // direction. We can't allow these two frames to share the same textrun |
| 2153 | // because that would violate our invariant that two flows in the same |
| 2154 | // textrun have different content elements. |
| 2155 | return false; |
| 2156 | } |
| 2157 | |
| 2158 | if (sc1 == sc2) { |
| 2159 | return true; |
| 2160 | } |
| 2161 | |
| 2162 | const nsStyleText* textStyle2 = sc2->StyleText(); |
| 2163 | if (textStyle1->mTextTransform != textStyle2->mTextTransform || |
| 2164 | textStyle1->EffectiveWordBreak() != textStyle2->EffectiveWordBreak() || |
| 2165 | textStyle1->mLineBreak != textStyle2->mLineBreak) { |
| 2166 | return false; |
| 2167 | } |
| 2168 | |
| 2169 | nsPresContext* pc = aFrame1->PresContext(); |
| 2170 | MOZ_ASSERT(pc == aFrame2->PresContext())do { static_assert( mozilla::detail::AssertionConditionType< decltype(pc == aFrame2->PresContext())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(pc == aFrame2->PresContext ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("pc == aFrame2->PresContext()", "./../../../layout/generic/nsTextFrame.cpp" , 2170); AnnotateMozCrashReason("MOZ_ASSERT" "(" "pc == aFrame2->PresContext()" ")"); do { MOZ_CrashSequence(__null, 2170); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 2171 | |
| 2172 | const nsStyleFont* fontStyle1 = sc1->StyleFont(); |
| 2173 | const nsStyleFont* fontStyle2 = sc2->StyleFont(); |
| 2174 | nscoord letterSpacing1 = ResolveLetterSpacing(aFrame1, *textStyle1); |
| 2175 | nscoord letterSpacing2 = ResolveLetterSpacing(aFrame2, *textStyle2); |
| 2176 | return fontStyle1->mFont == fontStyle2->mFont && |
| 2177 | fontStyle1->mLanguage == fontStyle2->mLanguage && |
| 2178 | nsLayoutUtils::GetTextRunFlagsForStyle(sc1, pc, fontStyle1, textStyle1, |
| 2179 | letterSpacing1) == |
| 2180 | nsLayoutUtils::GetTextRunFlagsForStyle(sc2, pc, fontStyle2, |
| 2181 | textStyle2, letterSpacing2); |
| 2182 | } |
| 2183 | |
| 2184 | void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame) { |
| 2185 | LayoutFrameType frameType = aFrame->Type(); |
| 2186 | if (frameType == LayoutFrameType::RubyTextContainer) { |
| 2187 | // Don't include any ruby text container into the text run. |
| 2188 | return; |
| 2189 | } |
| 2190 | |
| 2191 | // First check if we can extend the current mapped frame block. This is |
| 2192 | // common. |
| 2193 | if (mMappedFlows.Length() > 0) { |
| 2194 | MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; |
| 2195 | if (mappedFlow->mEndFrame == aFrame && |
| 2196 | aFrame->HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION)) { |
| 2197 | NS_ASSERTION(frameType == LayoutFrameType::Text,do { if (!(frameType == LayoutFrameType::Text)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Flow-sibling of a text frame is not a text frame?" , "frameType == LayoutFrameType::Text", "./../../../layout/generic/nsTextFrame.cpp" , 2198); MOZ_PretendNoReturn(); } } while (0) |
| 2198 | "Flow-sibling of a text frame is not a text frame?")do { if (!(frameType == LayoutFrameType::Text)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Flow-sibling of a text frame is not a text frame?" , "frameType == LayoutFrameType::Text", "./../../../layout/generic/nsTextFrame.cpp" , 2198); MOZ_PretendNoReturn(); } } while (0); |
| 2199 | |
| 2200 | // Don't do this optimization if mLastFrame has a terminal newline... |
| 2201 | // it's quite likely preformatted and we might want to end the textrun |
| 2202 | // here. This is almost always true: |
| 2203 | if (mLastFrame->Style() == aFrame->Style() && |
| 2204 | !HasTerminalNewline(mLastFrame)) { |
| 2205 | AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame)); |
| 2206 | return; |
| 2207 | } |
| 2208 | } |
| 2209 | } |
| 2210 | |
| 2211 | // Now see if we can add a new set of frames to the current textrun |
| 2212 | if (frameType == LayoutFrameType::Text) { |
| 2213 | nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame); |
| 2214 | |
| 2215 | if (mLastFrame) { |
| 2216 | if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) { |
| 2217 | FlushFrames(false, false); |
| 2218 | } else { |
| 2219 | if (mLastFrame->GetContent() == frame->GetContent()) { |
| 2220 | AccumulateRunInfo(frame); |
| 2221 | return; |
| 2222 | } |
| 2223 | } |
| 2224 | } |
| 2225 | |
| 2226 | MappedFlow* mappedFlow = mMappedFlows.AppendElement(); |
| 2227 | mappedFlow->mStartFrame = frame; |
| 2228 | mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame; |
| 2229 | |
| 2230 | AccumulateRunInfo(frame); |
| 2231 | if (mMappedFlows.Length() == 1) { |
| 2232 | mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun); |
| 2233 | mCurrentRunContextInfo = mNextRunContextInfo; |
| 2234 | } |
| 2235 | return; |
| 2236 | } |
| 2237 | |
| 2238 | if (frameType == LayoutFrameType::Placeholder && |
| 2239 | aFrame->HasAnyStateBits(PLACEHOLDER_FOR_ABSPOS | |
| 2240 | PLACEHOLDER_FOR_FIXEDPOS)) { |
| 2241 | // Somewhat hacky fix for bug 1418472: |
| 2242 | // If this is a placeholder for an absolute-positioned frame, we need to |
| 2243 | // flush the line-breaker to prevent the placeholder becoming separated |
| 2244 | // from the immediately-following content. |
| 2245 | // XXX This will interrupt text shaping (ligatures, etc) if an abs-pos |
| 2246 | // element occurs within a word where shaping should be in effect, but |
| 2247 | // that's an edge case, unlikely to occur in real content. A more precise |
| 2248 | // fix might require better separation of line-breaking from textrun setup, |
| 2249 | // but that's a big invasive change (and potentially expensive for perf, as |
| 2250 | // it might introduce an additional pass over all the frames). |
| 2251 | FlushFrames(true, false); |
| 2252 | } |
| 2253 | |
| 2254 | FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame); |
| 2255 | bool isBR = frameType == LayoutFrameType::Br; |
| 2256 | if (!traversal.mLineBreakerCanCrossFrameBoundary) { |
| 2257 | // BR frames are special. We do not need or want to record a break |
| 2258 | // opportunity before a BR frame. |
| 2259 | FlushFrames(true, isBR); |
| 2260 | mCommonAncestorWithLastFrame = aFrame; |
| 2261 | mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE; |
| 2262 | mStartOfLine = false; |
| 2263 | } else if (!traversal.mTextRunCanCrossFrameBoundary) { |
| 2264 | FlushFrames(false, false); |
| 2265 | } |
| 2266 | |
| 2267 | for (nsIFrame* f = traversal.NextFrameToScan(); f; |
| 2268 | f = traversal.NextFrameToScan()) { |
| 2269 | ScanFrame(f); |
| 2270 | } |
| 2271 | |
| 2272 | if (!traversal.mLineBreakerCanCrossFrameBoundary) { |
| 2273 | // Really if we're a BR frame this is unnecessary since descendInto will be |
| 2274 | // false. In fact this whole "if" statement should move into the |
| 2275 | // descendInto. |
| 2276 | FlushFrames(true, isBR); |
| 2277 | mCommonAncestorWithLastFrame = aFrame; |
| 2278 | mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE; |
| 2279 | } else if (!traversal.mTextRunCanCrossFrameBoundary) { |
| 2280 | FlushFrames(false, false); |
| 2281 | } |
| 2282 | |
| 2283 | LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent()); |
| 2284 | } |
| 2285 | |
| 2286 | nsTextFrame* BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex) { |
| 2287 | uint32_t index = *aIndex; |
| 2288 | if (index >= mLineBreakBeforeFrames.Length()) { |
| 2289 | return nullptr; |
| 2290 | } |
| 2291 | *aIndex = index + 1; |
| 2292 | return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index)); |
| 2293 | } |
| 2294 | |
| 2295 | static gfxFontGroup* GetFontGroupForFrame( |
| 2296 | const nsIFrame* aFrame, float aFontSizeInflation, |
| 2297 | nsFontMetrics** aOutFontMetrics = nullptr) { |
| 2298 | RefPtr<nsFontMetrics> metrics = |
| 2299 | nsLayoutUtils::GetFontMetricsForFrame(aFrame, aFontSizeInflation); |
| 2300 | gfxFontGroup* fontGroup = metrics->GetThebesFontGroup(); |
| 2301 | |
| 2302 | // Populate outparam before we return: |
| 2303 | if (aOutFontMetrics) { |
| 2304 | metrics.forget(aOutFontMetrics); |
| 2305 | } |
| 2306 | // XXX this is a bit bogus, we're releasing 'metrics' so the |
| 2307 | // returned font-group might actually be torn down, although because |
| 2308 | // of the way the device context caches font metrics, this seems to |
| 2309 | // not actually happen. But we should fix this. |
| 2310 | return fontGroup; |
| 2311 | } |
| 2312 | |
| 2313 | nsFontMetrics* nsTextFrame::InflatedFontMetrics() const { |
| 2314 | if (!mFontMetrics) { |
| 2315 | float inflation = nsLayoutUtils::FontSizeInflationFor(this); |
| 2316 | mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(this, inflation); |
| 2317 | } |
| 2318 | return mFontMetrics; |
| 2319 | } |
| 2320 | |
| 2321 | static gfxFontGroup* GetInflatedFontGroupForFrame(nsTextFrame* aFrame) { |
| 2322 | gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated); |
| 2323 | if (textRun) { |
| 2324 | return textRun->GetFontGroup(); |
| 2325 | } |
| 2326 | return aFrame->InflatedFontMetrics()->GetThebesFontGroup(); |
| 2327 | } |
| 2328 | |
| 2329 | static already_AddRefed<DrawTarget> CreateReferenceDrawTarget( |
| 2330 | const nsTextFrame* aTextFrame) { |
| 2331 | UniquePtr<gfxContext> ctx = |
| 2332 | aTextFrame->PresShell()->CreateReferenceRenderingContext(); |
| 2333 | RefPtr<DrawTarget> dt = ctx->GetDrawTarget(); |
| 2334 | return dt.forget(); |
| 2335 | } |
| 2336 | |
| 2337 | static already_AddRefed<gfxTextRun> GetHyphenTextRun(nsTextFrame* aTextFrame, |
| 2338 | DrawTarget* aDrawTarget) { |
| 2339 | RefPtr<DrawTarget> dt = aDrawTarget; |
| 2340 | if (!dt) { |
| 2341 | dt = CreateReferenceDrawTarget(aTextFrame); |
| 2342 | if (!dt) { |
| 2343 | return nullptr; |
| 2344 | } |
| 2345 | } |
| 2346 | |
| 2347 | RefPtr<nsFontMetrics> fm = |
| 2348 | nsLayoutUtils::GetInflatedFontMetricsForFrame(aTextFrame); |
| 2349 | auto* fontGroup = fm->GetThebesFontGroup(); |
| 2350 | auto appPerDev = aTextFrame->PresContext()->AppUnitsPerDevPixel(); |
| 2351 | const auto& hyphenateChar = aTextFrame->StyleText()->mHyphenateCharacter; |
| 2352 | gfx::ShapedTextFlags flags = |
| 2353 | nsLayoutUtils::GetTextRunOrientFlagsForStyle(aTextFrame->Style()); |
| 2354 | // Make the directionality of the hyphen run (in case it is multi-char) match |
| 2355 | // the text frame. |
| 2356 | if (aTextFrame->GetWritingMode().IsBidiRTL()) { |
| 2357 | flags |= gfx::ShapedTextFlags::TEXT_IS_RTL; |
| 2358 | } |
| 2359 | if (hyphenateChar.IsAuto()) { |
| 2360 | return fontGroup->MakeHyphenTextRun(dt, flags, appPerDev); |
| 2361 | } |
| 2362 | auto* missingFonts = aTextFrame->PresContext()->MissingFontRecorder(); |
| 2363 | const NS_ConvertUTF8toUTF16 hyphenStr(hyphenateChar.AsString().AsString()); |
| 2364 | return fontGroup->MakeTextRun(hyphenStr.BeginReading(), hyphenStr.Length(), |
| 2365 | dt, appPerDev, flags, nsTextFrameUtils::Flags(), |
| 2366 | missingFonts); |
| 2367 | } |
| 2368 | |
| 2369 | already_AddRefed<gfxTextRun> BuildTextRunsScanner::BuildTextRunForFrames( |
| 2370 | void* aTextBuffer) { |
| 2371 | gfxSkipChars skipChars; |
| 2372 | |
| 2373 | const void* textPtr = aTextBuffer; |
| 2374 | bool anyTextTransformStyle = false; |
| 2375 | bool anyMathMLStyling = false; |
| 2376 | bool anyTextEmphasis = false; |
| 2377 | uint8_t sstyScriptLevel = 0; |
| 2378 | uint32_t mathFlags = 0; |
| 2379 | gfx::ShapedTextFlags flags = gfx::ShapedTextFlags(); |
| 2380 | nsTextFrameUtils::Flags flags2 = nsTextFrameUtils::Flags::NoBreaks; |
| 2381 | |
| 2382 | if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { |
| 2383 | flags2 |= nsTextFrameUtils::Flags::IncomingWhitespace; |
| 2384 | } |
| 2385 | if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { |
| 2386 | flags |= gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR; |
| 2387 | } |
| 2388 | |
| 2389 | AutoTArray<int32_t, 50> textBreakPoints; |
| 2390 | TextRunUserData dummyData; |
| 2391 | TextRunMappedFlow dummyMappedFlow; |
| 2392 | TextRunMappedFlow* userMappedFlows; |
| 2393 | TextRunUserData* userData; |
| 2394 | TextRunUserData* userDataToDestroy; |
| 2395 | // If the situation is particularly simple (and common) we don't need to |
| 2396 | // allocate userData. |
| 2397 | if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame && |
| 2398 | mMappedFlows[0].mStartFrame->GetContentOffset() == 0) { |
| 2399 | userData = &dummyData; |
| 2400 | userMappedFlows = &dummyMappedFlow; |
| 2401 | userDataToDestroy = nullptr; |
| 2402 | dummyData.mMappedFlowCount = mMappedFlows.Length(); |
| 2403 | dummyData.mLastFlowIndex = 0; |
| 2404 | } else { |
| 2405 | userData = CreateUserData(mMappedFlows.Length()); |
| 2406 | userMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1); |
| 2407 | userDataToDestroy = userData; |
| 2408 | } |
| 2409 | |
| 2410 | uint32_t currentTransformedTextOffset = 0; |
| 2411 | |
| 2412 | uint32_t nextBreakIndex = 0; |
| 2413 | nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); |
| 2414 | bool isSVG = mLineContainer->IsInSVGTextSubtree(); |
| 2415 | bool enabledJustification = |
| 2416 | (mLineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify || |
| 2417 | mLineContainer->StyleText()->mTextAlignLast == |
| 2418 | StyleTextAlignLast::Justify); |
| 2419 | |
| 2420 | const nsStyleText* textStyle = nullptr; |
| 2421 | const nsStyleFont* fontStyle = nullptr; |
| 2422 | ComputedStyle* lastComputedStyle = nullptr; |
| 2423 | for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { |
| 2424 | MappedFlow* mappedFlow = &mMappedFlows[i]; |
| 2425 | nsTextFrame* f = mappedFlow->mStartFrame; |
| 2426 | |
| 2427 | lastComputedStyle = f->Style(); |
| 2428 | // Detect use of text-transform or font-variant anywhere in the run |
| 2429 | textStyle = f->StyleText(); |
| 2430 | if (!textStyle->mTextTransform.IsNone() || |
| 2431 | textStyle->mWebkitTextSecurity != StyleTextSecurity::None || |
| 2432 | // text-combine-upright requires converting from full-width |
| 2433 | // characters to non-full-width correspendent in some cases. |
| 2434 | lastComputedStyle->IsTextCombined()) { |
| 2435 | anyTextTransformStyle = true; |
| 2436 | } |
| 2437 | if (textStyle->HasEffectiveTextEmphasis()) { |
| 2438 | anyTextEmphasis = true; |
| 2439 | } |
| 2440 | flags |= f->GetSpacingFlags(); |
| 2441 | nsTextFrameUtils::CompressionMode compression = |
| 2442 | GetCSSWhitespaceToCompressionMode(f, textStyle); |
| 2443 | if ((enabledJustification || f->ShouldSuppressLineBreak()) && !isSVG) { |
| 2444 | flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING; |
| 2445 | } |
| 2446 | fontStyle = f->StyleFont(); |
| 2447 | nsIFrame* parent = mLineContainer->GetParent(); |
| 2448 | if (StyleMathVariant::None != fontStyle->mMathVariant) { |
| 2449 | if (StyleMathVariant::Normal != fontStyle->mMathVariant) { |
| 2450 | anyMathMLStyling = true; |
| 2451 | } |
| 2452 | } else if (mLineContainer->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) { |
| 2453 | flags2 |= nsTextFrameUtils::Flags::IsSingleCharMi; |
| 2454 | anyMathMLStyling = true; |
| 2455 | } |
| 2456 | if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) { |
| 2457 | // All MathML tokens except <mtext> use 'math' script. |
| 2458 | if (!(parent && parent->GetContent() && |
| 2459 | parent->GetContent()->IsMathMLElement(nsGkAtoms::mtext))) { |
| 2460 | flags |= gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT; |
| 2461 | } |
| 2462 | nsIMathMLFrame* mathFrame = do_QueryFrame(parent); |
| 2463 | if (mathFrame) { |
| 2464 | nsPresentationData presData; |
| 2465 | mathFrame->GetPresentationData(presData); |
| 2466 | if (presData.flags.contains(MathMLPresentationFlag::Dtls)) { |
| 2467 | mathFlags |= MathMLTextRunFactory::MATH_FONT_FEATURE_DTLS; |
| 2468 | anyMathMLStyling = true; |
| 2469 | } |
| 2470 | } |
| 2471 | } |
| 2472 | nsIFrame* child = mLineContainer; |
| 2473 | uint8_t oldScriptLevel = 0; |
| 2474 | while (parent && |
| 2475 | child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) { |
| 2476 | // Reconstruct the script level ignoring any user overrides. It is |
| 2477 | // calculated this way instead of using scriptlevel to ensure the |
| 2478 | // correct ssty font feature setting is used even if the user sets a |
| 2479 | // different (especially negative) scriptlevel. |
| 2480 | nsIMathMLFrame* mathFrame = do_QueryFrame(parent); |
| 2481 | if (mathFrame) { |
| 2482 | sstyScriptLevel += mathFrame->ScriptIncrement(child); |
| 2483 | } |
| 2484 | if (sstyScriptLevel < oldScriptLevel) { |
| 2485 | // overflow |
| 2486 | sstyScriptLevel = UINT8_MAX(255); |
| 2487 | break; |
| 2488 | } |
| 2489 | child = parent; |
| 2490 | parent = parent->GetParent(); |
| 2491 | oldScriptLevel = sstyScriptLevel; |
| 2492 | } |
| 2493 | if (sstyScriptLevel) { |
| 2494 | anyMathMLStyling = true; |
| 2495 | } |
| 2496 | |
| 2497 | // Figure out what content is included in this flow. |
| 2498 | nsIContent* content = f->GetContent(); |
| 2499 | const CharacterDataBuffer& characterDataBuffer = f->CharacterDataBuffer(); |
| 2500 | int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset(); |
| 2501 | int32_t contentEnd = mappedFlow->GetContentEnd(); |
| 2502 | int32_t contentLength = contentEnd - contentStart; |
| 2503 | const nsAtom* language = f->StyleFont()->GetLangAtom(); |
| 2504 | |
| 2505 | TextRunMappedFlow* newFlow = &userMappedFlows[i]; |
| 2506 | newFlow->mStartFrame = mappedFlow->mStartFrame; |
| 2507 | newFlow->mDOMOffsetToBeforeTransformOffset = |
| 2508 | skipChars.GetOriginalCharCount() - |
| 2509 | mappedFlow->mStartFrame->GetContentOffset(); |
| 2510 | newFlow->mContentLength = contentLength; |
| 2511 | |
| 2512 | while (nextBreakBeforeFrame && |
| 2513 | nextBreakBeforeFrame->GetContent() == content) { |
| 2514 | textBreakPoints.AppendElement(nextBreakBeforeFrame->GetContentOffset() + |
| 2515 | newFlow->mDOMOffsetToBeforeTransformOffset); |
| 2516 | nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); |
| 2517 | } |
| 2518 | |
| 2519 | nsTextFrameUtils::Flags analysisFlags; |
| 2520 | if (characterDataBuffer.Is2b()) { |
| 2521 | NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!")do { if (!(mDoubleByteText)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Wrong buffer char size!", "mDoubleByteText", "./../../../layout/generic/nsTextFrame.cpp" , 2521); MOZ_PretendNoReturn(); } } while (0); |
| 2522 | char16_t* bufStart = static_cast<char16_t*>(aTextBuffer); |
| 2523 | char16_t* bufEnd = nsTextFrameUtils::TransformText( |
| 2524 | characterDataBuffer.Get2b() + contentStart, contentLength, bufStart, |
| 2525 | compression, &mNextRunContextInfo, &skipChars, &analysisFlags, |
| 2526 | language); |
| 2527 | aTextBuffer = bufEnd; |
| 2528 | currentTransformedTextOffset = |
| 2529 | bufEnd - static_cast<const char16_t*>(textPtr); |
| 2530 | } else { |
| 2531 | if (mDoubleByteText) { |
| 2532 | // Need to expand the text. First transform it into a temporary buffer, |
| 2533 | // then expand. |
| 2534 | AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE4096> tempBuf; |
| 2535 | uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible); |
| 2536 | if (!bufStart) { |
| 2537 | DestroyUserData(userDataToDestroy); |
| 2538 | return nullptr; |
| 2539 | } |
| 2540 | uint8_t* end = nsTextFrameUtils::TransformText( |
| 2541 | reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) + |
| 2542 | contentStart, |
| 2543 | contentLength, bufStart, compression, &mNextRunContextInfo, |
| 2544 | &skipChars, &analysisFlags, language); |
| 2545 | aTextBuffer = |
| 2546 | ExpandBuffer(static_cast<char16_t*>(aTextBuffer), |
| 2547 | tempBuf.Elements(), end - tempBuf.Elements()); |
| 2548 | currentTransformedTextOffset = static_cast<char16_t*>(aTextBuffer) - |
| 2549 | static_cast<const char16_t*>(textPtr); |
| 2550 | } else { |
| 2551 | uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer); |
| 2552 | uint8_t* end = nsTextFrameUtils::TransformText( |
| 2553 | reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) + |
| 2554 | contentStart, |
| 2555 | contentLength, bufStart, compression, &mNextRunContextInfo, |
| 2556 | &skipChars, &analysisFlags, language); |
| 2557 | aTextBuffer = end; |
| 2558 | currentTransformedTextOffset = |
| 2559 | end - static_cast<const uint8_t*>(textPtr); |
| 2560 | } |
| 2561 | } |
| 2562 | flags2 |= analysisFlags; |
| 2563 | } |
| 2564 | |
| 2565 | void* finalUserData; |
| 2566 | if (userData == &dummyData) { |
| 2567 | flags2 |= nsTextFrameUtils::Flags::IsSimpleFlow; |
| 2568 | userData = nullptr; |
| 2569 | finalUserData = mMappedFlows[0].mStartFrame; |
| 2570 | } else { |
| 2571 | finalUserData = userData; |
| 2572 | } |
| 2573 | |
| 2574 | uint32_t transformedLength = currentTransformedTextOffset; |
| 2575 | |
| 2576 | // Now build the textrun |
| 2577 | nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame; |
| 2578 | float fontInflation; |
| 2579 | gfxFontGroup* fontGroup; |
| 2580 | if (mWhichTextRun == nsTextFrame::eNotInflated) { |
| 2581 | fontInflation = 1.0f; |
| 2582 | fontGroup = GetFontGroupForFrame(firstFrame, fontInflation); |
| 2583 | } else { |
| 2584 | fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame); |
| 2585 | fontGroup = GetInflatedFontGroupForFrame(firstFrame); |
| 2586 | } |
| 2587 | MOZ_ASSERT(fontGroup)do { static_assert( mozilla::detail::AssertionConditionType< decltype(fontGroup)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(fontGroup))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("fontGroup", "./../../../layout/generic/nsTextFrame.cpp" , 2587); AnnotateMozCrashReason("MOZ_ASSERT" "(" "fontGroup" ")" ); do { MOZ_CrashSequence(__null, 2587); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 2588 | |
| 2589 | if (flags2 & nsTextFrameUtils::Flags::HasTab) { |
| 2590 | flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING; |
| 2591 | } |
| 2592 | if (flags2 & nsTextFrameUtils::Flags::HasShy) { |
| 2593 | flags |= gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS; |
| 2594 | } |
| 2595 | if (mBidiEnabled && (firstFrame->GetEmbeddingLevel().IsRTL())) { |
| 2596 | flags |= gfx::ShapedTextFlags::TEXT_IS_RTL; |
| 2597 | } |
| 2598 | if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { |
| 2599 | flags2 |= nsTextFrameUtils::Flags::TrailingWhitespace; |
| 2600 | } |
| 2601 | if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { |
| 2602 | flags |= gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR; |
| 2603 | } |
| 2604 | // ContinueTextRunAcrossFrames guarantees that it doesn't matter which |
| 2605 | // frame's style is used, so we use a mixture of the first frame and |
| 2606 | // last frame's style |
| 2607 | flags |= nsLayoutUtils::GetTextRunFlagsForStyle( |
| 2608 | lastComputedStyle, firstFrame->PresContext(), fontStyle, textStyle, |
| 2609 | ResolveLetterSpacing(firstFrame, *textStyle)); |
| 2610 | // XXX this is a bit of a hack. For performance reasons, if we're favouring |
| 2611 | // performance over quality, don't try to get accurate glyph extents. |
| 2612 | if (!(flags & gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED)) { |
| 2613 | flags |= gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX; |
| 2614 | } |
| 2615 | |
| 2616 | // Convert linebreak coordinates to transformed string offsets |
| 2617 | NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),do { if (!(nextBreakIndex == mLineBreakBeforeFrames.Length()) ) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Didn't find all the frames to break-before..." , "nextBreakIndex == mLineBreakBeforeFrames.Length()", "./../../../layout/generic/nsTextFrame.cpp" , 2618); MOZ_PretendNoReturn(); } } while (0) |
| 2618 | "Didn't find all the frames to break-before...")do { if (!(nextBreakIndex == mLineBreakBeforeFrames.Length()) ) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Didn't find all the frames to break-before..." , "nextBreakIndex == mLineBreakBeforeFrames.Length()", "./../../../layout/generic/nsTextFrame.cpp" , 2618); MOZ_PretendNoReturn(); } } while (0); |
| 2619 | gfxSkipCharsIterator iter(skipChars); |
| 2620 | AutoTArray<uint32_t, 50> textBreakPointsAfterTransform; |
| 2621 | for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) { |
| 2622 | nsTextFrameUtils::AppendLineBreakOffset( |
| 2623 | &textBreakPointsAfterTransform, |
| 2624 | iter.ConvertOriginalToSkipped(textBreakPoints[i])); |
| 2625 | } |
| 2626 | if (mStartOfLine) { |
| 2627 | nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform, |
| 2628 | transformedLength); |
| 2629 | } |
| 2630 | |
| 2631 | // Setup factory chain |
| 2632 | bool needsToMaskPassword = NeedsToMaskPassword(firstFrame); |
| 2633 | UniquePtr<nsTransformingTextRunFactory> transformingFactory; |
| 2634 | if (anyTextTransformStyle || needsToMaskPassword) { |
| 2635 | bool useCapitalEsZett = false; |
| 2636 | if (StaticPrefs::layout_css_text_transform_uppercase_eszett_enabled()) { |
| 2637 | RefPtr firstFont = fontGroup->GetFirstValidFont(0xdf); |
| 2638 | useCapitalEsZett = firstFont && firstFont->HasCharacter(0x1e9e); |
| 2639 | } |
| 2640 | char16_t maskChar = |
| 2641 | needsToMaskPassword ? 0 : textStyle->TextSecurityMaskChar(); |
| 2642 | transformingFactory = MakeUnique<nsCaseTransformTextRunFactory>( |
| 2643 | std::move(transformingFactory), false, useCapitalEsZett, maskChar); |
| 2644 | } |
| 2645 | if (anyMathMLStyling) { |
| 2646 | transformingFactory = MakeUnique<MathMLTextRunFactory>( |
| 2647 | std::move(transformingFactory), mathFlags, sstyScriptLevel, |
| 2648 | fontInflation); |
| 2649 | } |
| 2650 | nsTArray<RefPtr<nsTransformedCharStyle>> styles; |
| 2651 | if (transformingFactory) { |
| 2652 | uint32_t unmaskStart = 0, unmaskEnd = UINT32_MAX(4294967295U); |
| 2653 | if (needsToMaskPassword) { |
| 2654 | unmaskStart = unmaskEnd = UINT32_MAX(4294967295U); |
| 2655 | const TextEditor* const passwordEditor = |
| 2656 | nsContentUtils::GetExtantTextEditorFromAnonymousNode( |
| 2657 | firstFrame->GetContent()); |
| 2658 | if (passwordEditor && !passwordEditor->IsAllMasked()) { |
| 2659 | unmaskStart = passwordEditor->UnmaskedStart(); |
| 2660 | unmaskEnd = passwordEditor->UnmaskedEnd(); |
| 2661 | } |
| 2662 | } |
| 2663 | |
| 2664 | iter.SetOriginalOffset(0); |
| 2665 | for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { |
| 2666 | MappedFlow* mappedFlow = &mMappedFlows[i]; |
| 2667 | nsTextFrame* f; |
| 2668 | ComputedStyle* sc = nullptr; |
| 2669 | RefPtr<nsTransformedCharStyle> defaultStyle; |
| 2670 | RefPtr<nsTransformedCharStyle> unmaskStyle; |
| 2671 | for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame; |
| 2672 | f = f->GetNextContinuation()) { |
| 2673 | uint32_t skippedOffset = iter.GetSkippedOffset(); |
| 2674 | // Text-combined frames have content-dependent transform, so we |
| 2675 | // want to create new nsTransformedCharStyle for them anyway. |
| 2676 | if (sc != f->Style() || sc->IsTextCombined()) { |
| 2677 | sc = f->Style(); |
| 2678 | auto* pc = f->PresContext(); |
| 2679 | defaultStyle = MakeRefPtr<nsTransformedCharStyle>(sc, pc); |
| 2680 | if (sc->IsTextCombined() && f->CountGraphemeClusters() > 1) { |
| 2681 | defaultStyle->mForceNonFullWidth = true; |
| 2682 | } |
| 2683 | if (needsToMaskPassword) { |
| 2684 | defaultStyle->mMaskPassword = true; |
| 2685 | if (unmaskStart != unmaskEnd) { |
| 2686 | unmaskStyle = MakeRefPtr<nsTransformedCharStyle>(sc, pc); |
| 2687 | unmaskStyle->mForceNonFullWidth = |
| 2688 | defaultStyle->mForceNonFullWidth; |
| 2689 | } |
| 2690 | } |
| 2691 | } |
| 2692 | iter.AdvanceOriginal(f->GetContentLength()); |
| 2693 | uint32_t skippedEnd = iter.GetSkippedOffset(); |
| 2694 | if (unmaskStyle) { |
| 2695 | uint32_t skippedUnmaskStart = |
| 2696 | iter.ConvertOriginalToSkipped(unmaskStart); |
| 2697 | uint32_t skippedUnmaskEnd = iter.ConvertOriginalToSkipped(unmaskEnd); |
| 2698 | iter.SetSkippedOffset(skippedEnd); |
| 2699 | for (; skippedOffset < std::min(skippedEnd, skippedUnmaskStart); |
| 2700 | ++skippedOffset) { |
| 2701 | styles.AppendElement(defaultStyle); |
| 2702 | } |
| 2703 | for (; skippedOffset < std::min(skippedEnd, skippedUnmaskEnd); |
| 2704 | ++skippedOffset) { |
| 2705 | styles.AppendElement(unmaskStyle); |
| 2706 | } |
| 2707 | for (; skippedOffset < skippedEnd; ++skippedOffset) { |
| 2708 | styles.AppendElement(defaultStyle); |
| 2709 | } |
| 2710 | } else { |
| 2711 | for (; skippedOffset < skippedEnd; ++skippedOffset) { |
| 2712 | styles.AppendElement(defaultStyle); |
| 2713 | } |
| 2714 | } |
| 2715 | } |
| 2716 | } |
| 2717 | flags2 |= nsTextFrameUtils::Flags::IsTransformed; |
| 2718 | NS_ASSERTION(iter.GetSkippedOffset() == transformedLength,do { if (!(iter.GetSkippedOffset() == transformedLength)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "We didn't cover all the characters in the text run!" , "iter.GetSkippedOffset() == transformedLength", "./../../../layout/generic/nsTextFrame.cpp" , 2719); MOZ_PretendNoReturn(); } } while (0) |
| 2719 | "We didn't cover all the characters in the text run!")do { if (!(iter.GetSkippedOffset() == transformedLength)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "We didn't cover all the characters in the text run!" , "iter.GetSkippedOffset() == transformedLength", "./../../../layout/generic/nsTextFrame.cpp" , 2719); MOZ_PretendNoReturn(); } } while (0); |
| 2720 | } |
| 2721 | |
| 2722 | RefPtr<gfxTextRun> textRun; |
| 2723 | gfxTextRunFactory::Parameters params = { |
| 2724 | mDrawTarget, |
| 2725 | finalUserData, |
| 2726 | &skipChars, |
| 2727 | textBreakPointsAfterTransform.Elements(), |
| 2728 | uint32_t(textBreakPointsAfterTransform.Length()), |
| 2729 | int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())}; |
| 2730 | |
| 2731 | if (mDoubleByteText) { |
| 2732 | const char16_t* text = static_cast<const char16_t*>(textPtr); |
| 2733 | if (transformingFactory) { |
| 2734 | textRun = transformingFactory->MakeTextRun( |
| 2735 | text, transformedLength, ¶ms, fontGroup, flags, flags2, |
| 2736 | std::move(styles), true); |
| 2737 | } else { |
| 2738 | textRun = fontGroup->MakeTextRun(text, transformedLength, ¶ms, flags, |
| 2739 | flags2, mMissingFonts); |
| 2740 | } |
| 2741 | } else { |
| 2742 | const uint8_t* text = static_cast<const uint8_t*>(textPtr); |
| 2743 | flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT; |
| 2744 | if (transformingFactory) { |
| 2745 | textRun = transformingFactory->MakeTextRun( |
| 2746 | text, transformedLength, ¶ms, fontGroup, flags, flags2, |
| 2747 | std::move(styles), true); |
| 2748 | } else { |
| 2749 | textRun = fontGroup->MakeTextRun(text, transformedLength, ¶ms, flags, |
| 2750 | flags2, mMissingFonts); |
| 2751 | } |
| 2752 | } |
| 2753 | if (!textRun) { |
| 2754 | DestroyUserData(userDataToDestroy); |
| 2755 | return nullptr; |
| 2756 | } |
| 2757 | |
| 2758 | // We have to set these up after we've created the textrun, because |
| 2759 | // the breaks may be stored in the textrun during this very call. |
| 2760 | // This is a bit annoying because it requires another loop over the frames |
| 2761 | // making up the textrun, but I don't see a way to avoid this. |
| 2762 | // We have to do this if line-breaking is required OR if a text-transform |
| 2763 | // is in effect, because we depend on the line-breaker's scanner (via |
| 2764 | // BreakSink::Finish) to finish building transformed textruns. |
| 2765 | if (mDoLineBreaking || transformingFactory) { |
| 2766 | SetupBreakSinksForTextRun(textRun.get(), textPtr); |
| 2767 | } |
| 2768 | |
| 2769 | // Ownership of the factory has passed to the textrun |
| 2770 | // TODO: bug 1285316: clean up ownership transfer from the factory to |
| 2771 | // the textrun |
| 2772 | (void)transformingFactory.release(); |
| 2773 | |
| 2774 | if (anyTextEmphasis) { |
| 2775 | SetupTextEmphasisForTextRun(textRun.get(), textPtr); |
| 2776 | } |
| 2777 | |
| 2778 | if (mSkipIncompleteTextRuns) { |
| 2779 | mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace( |
| 2780 | textPtr, transformedLength, mDoubleByteText); |
| 2781 | // Since we're doing to destroy the user data now, avoid a dangling |
| 2782 | // pointer. Strictly speaking we don't need to do this since it should |
| 2783 | // not be used (since this textrun will not be used and will be |
| 2784 | // itself deleted soon), but it's always better to not have dangling |
| 2785 | // pointers around. |
| 2786 | textRun->SetUserData(nullptr); |
| 2787 | DestroyUserData(userDataToDestroy); |
| 2788 | return nullptr; |
| 2789 | } |
| 2790 | |
| 2791 | // Actually wipe out the textruns associated with the mapped frames and |
| 2792 | // associate those frames with this text run. |
| 2793 | AssignTextRun(textRun.get(), fontInflation); |
| 2794 | return textRun.forget(); |
| 2795 | } |
| 2796 | |
| 2797 | // This is a cut-down version of BuildTextRunForFrames used to set up |
| 2798 | // context for the line-breaker, when the textrun has already been created. |
| 2799 | // So it does the same walk over the mMappedFlows, but doesn't actually |
| 2800 | // build a new textrun. |
| 2801 | bool BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun* aTextRun) { |
| 2802 | AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE4096> buffer; |
| 2803 | uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1); |
| 2804 | if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX(4294967295U)) { |
| 2805 | return false; |
| 2806 | } |
| 2807 | void* textPtr = buffer.AppendElements(bufferSize, fallible); |
| 2808 | if (!textPtr) { |
| 2809 | return false; |
| 2810 | } |
| 2811 | |
| 2812 | gfxSkipChars skipChars; |
| 2813 | const nsAtom* language = |
| 2814 | mMappedFlows[0].mStartFrame->StyleFont()->GetLangAtom(); |
| 2815 | |
| 2816 | for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { |
| 2817 | MappedFlow* mappedFlow = &mMappedFlows[i]; |
| 2818 | nsTextFrame* f = mappedFlow->mStartFrame; |
| 2819 | |
| 2820 | const nsStyleText* textStyle = f->StyleText(); |
| 2821 | nsTextFrameUtils::CompressionMode compression = |
| 2822 | GetCSSWhitespaceToCompressionMode(f, textStyle); |
| 2823 | |
| 2824 | // Figure out what content is included in this flow. |
| 2825 | const CharacterDataBuffer& characterDataBuffer = f->CharacterDataBuffer(); |
| 2826 | int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset(); |
| 2827 | int32_t contentEnd = mappedFlow->GetContentEnd(); |
| 2828 | int32_t contentLength = contentEnd - contentStart; |
| 2829 | |
| 2830 | nsTextFrameUtils::Flags analysisFlags; |
| 2831 | if (characterDataBuffer.Is2b()) { |
| 2832 | NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!")do { if (!(mDoubleByteText)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Wrong buffer char size!", "mDoubleByteText", "./../../../layout/generic/nsTextFrame.cpp" , 2832); MOZ_PretendNoReturn(); } } while (0); |
| 2833 | char16_t* bufStart = static_cast<char16_t*>(textPtr); |
| 2834 | char16_t* bufEnd = nsTextFrameUtils::TransformText( |
| 2835 | characterDataBuffer.Get2b() + contentStart, contentLength, bufStart, |
| 2836 | compression, &mNextRunContextInfo, &skipChars, &analysisFlags, |
| 2837 | language); |
| 2838 | textPtr = bufEnd; |
| 2839 | } else { |
| 2840 | if (mDoubleByteText) { |
| 2841 | // Need to expand the text. First transform it into a temporary buffer, |
| 2842 | // then expand. |
| 2843 | AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE4096> tempBuf; |
| 2844 | uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible); |
| 2845 | if (!bufStart) { |
| 2846 | return false; |
| 2847 | } |
| 2848 | uint8_t* end = nsTextFrameUtils::TransformText( |
| 2849 | reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) + |
| 2850 | contentStart, |
| 2851 | contentLength, bufStart, compression, &mNextRunContextInfo, |
| 2852 | &skipChars, &analysisFlags, language); |
| 2853 | textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr), |
| 2854 | tempBuf.Elements(), end - tempBuf.Elements()); |
| 2855 | } else { |
| 2856 | uint8_t* bufStart = static_cast<uint8_t*>(textPtr); |
| 2857 | uint8_t* end = nsTextFrameUtils::TransformText( |
| 2858 | reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) + |
| 2859 | contentStart, |
| 2860 | contentLength, bufStart, compression, &mNextRunContextInfo, |
| 2861 | &skipChars, &analysisFlags, language); |
| 2862 | textPtr = end; |
| 2863 | } |
| 2864 | } |
| 2865 | } |
| 2866 | |
| 2867 | // We have to set these up after we've created the textrun, because |
| 2868 | // the breaks may be stored in the textrun during this very call. |
| 2869 | // This is a bit annoying because it requires another loop over the frames |
| 2870 | // making up the textrun, but I don't see a way to avoid this. |
| 2871 | SetupBreakSinksForTextRun(aTextRun, buffer.Elements()); |
| 2872 | |
| 2873 | return true; |
| 2874 | } |
| 2875 | |
| 2876 | static bool HasCompressedLeadingWhitespace( |
| 2877 | nsTextFrame* aFrame, const nsStyleText* aStyleText, |
| 2878 | int32_t aContentEndOffset, const gfxSkipCharsIterator& aIterator) { |
| 2879 | if (!aIterator.IsOriginalCharSkipped()) { |
| 2880 | return false; |
| 2881 | } |
| 2882 | |
| 2883 | gfxSkipCharsIterator iter = aIterator; |
| 2884 | int32_t frameContentOffset = aFrame->GetContentOffset(); |
| 2885 | const CharacterDataBuffer& characterDataBuffer = |
| 2886 | aFrame->CharacterDataBuffer(); |
| 2887 | while (frameContentOffset < aContentEndOffset && |
| 2888 | iter.IsOriginalCharSkipped()) { |
| 2889 | if (IsTrimmableSpace(characterDataBuffer, frameContentOffset, aStyleText)) { |
| 2890 | return true; |
| 2891 | } |
| 2892 | ++frameContentOffset; |
| 2893 | iter.AdvanceOriginal(1); |
| 2894 | } |
| 2895 | return false; |
| 2896 | } |
| 2897 | |
| 2898 | void BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun, |
| 2899 | const void* aTextPtr) { |
| 2900 | using mozilla::intl::LineBreakRule; |
| 2901 | using mozilla::intl::WordBreakRule; |
| 2902 | |
| 2903 | // textruns have uniform language |
| 2904 | const nsStyleFont* styleFont = mMappedFlows[0].mStartFrame->StyleFont(); |
| 2905 | // We should only use a language for hyphenation if it was specified |
| 2906 | // explicitly. |
| 2907 | nsAtom* hyphenationLanguage = |
| 2908 | styleFont->mExplicitLanguage ? styleFont->GetLangAtom() : nullptr; |
| 2909 | // We keep this pointed at the skip-chars data for the current mappedFlow. |
| 2910 | // This lets us cheaply check whether the flow has compressed initial |
| 2911 | // whitespace... |
| 2912 | gfxSkipCharsIterator iter(aTextRun->GetSkipChars()); |
| 2913 | |
| 2914 | for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { |
| 2915 | MappedFlow* mappedFlow = &mMappedFlows[i]; |
| 2916 | // The CSS word-break value may change within a word, so we reset it for |
| 2917 | // each MappedFlow. The line-breaker will flush its text if the property |
| 2918 | // actually changes. |
| 2919 | const auto* styleText = mappedFlow->mStartFrame->StyleText(); |
| 2920 | auto wordBreak = styleText->EffectiveWordBreak(); |
| 2921 | switch (wordBreak) { |
| 2922 | case StyleWordBreak::BreakAll: |
| 2923 | mLineBreaker.SetWordBreak(WordBreakRule::BreakAll); |
| 2924 | break; |
| 2925 | case StyleWordBreak::KeepAll: |
| 2926 | mLineBreaker.SetWordBreak(WordBreakRule::KeepAll); |
| 2927 | break; |
| 2928 | case StyleWordBreak::Normal: |
| 2929 | default: |
| 2930 | MOZ_ASSERT(wordBreak == StyleWordBreak::Normal)do { static_assert( mozilla::detail::AssertionConditionType< decltype(wordBreak == StyleWordBreak::Normal)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(wordBreak == StyleWordBreak:: Normal))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("wordBreak == StyleWordBreak::Normal", "./../../../layout/generic/nsTextFrame.cpp" , 2930); AnnotateMozCrashReason("MOZ_ASSERT" "(" "wordBreak == StyleWordBreak::Normal" ")"); do { MOZ_CrashSequence(__null, 2930); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 2931 | mLineBreaker.SetWordBreak(WordBreakRule::Normal); |
| 2932 | break; |
| 2933 | } |
| 2934 | switch (styleText->mLineBreak) { |
| 2935 | case StyleLineBreak::Auto: |
| 2936 | mLineBreaker.SetStrictness(LineBreakRule::Auto); |
| 2937 | break; |
| 2938 | case StyleLineBreak::Normal: |
| 2939 | mLineBreaker.SetStrictness(LineBreakRule::Normal); |
| 2940 | break; |
| 2941 | case StyleLineBreak::Loose: |
| 2942 | mLineBreaker.SetStrictness(LineBreakRule::Loose); |
| 2943 | break; |
| 2944 | case StyleLineBreak::Strict: |
| 2945 | mLineBreaker.SetStrictness(LineBreakRule::Strict); |
| 2946 | break; |
| 2947 | case StyleLineBreak::Anywhere: |
| 2948 | mLineBreaker.SetStrictness(LineBreakRule::Anywhere); |
| 2949 | break; |
| 2950 | } |
| 2951 | |
| 2952 | uint32_t offset = iter.GetSkippedOffset(); |
| 2953 | gfxSkipCharsIterator iterNext = iter; |
| 2954 | iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() - |
| 2955 | mappedFlow->mStartFrame->GetContentOffset()); |
| 2956 | |
| 2957 | UniquePtr<BreakSink>* breakSink = mBreakSinks.AppendElement( |
| 2958 | MakeUnique<BreakSink>(aTextRun, mDrawTarget, offset)); |
| 2959 | |
| 2960 | uint32_t length = iterNext.GetSkippedOffset() - offset; |
| 2961 | uint32_t flags = 0; |
| 2962 | nsIFrame* initialBreakController = |
| 2963 | mappedFlow->mAncestorControllingInitialBreak; |
| 2964 | if (!initialBreakController) { |
| 2965 | initialBreakController = mLineContainer; |
| 2966 | } |
| 2967 | if (!initialBreakController->StyleText()->WhiteSpaceCanWrap( |
| 2968 | initialBreakController)) { |
| 2969 | flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL; |
| 2970 | } |
| 2971 | nsTextFrame* startFrame = mappedFlow->mStartFrame; |
| 2972 | const nsStyleText* textStyle = startFrame->StyleText(); |
| 2973 | if (!textStyle->WhiteSpaceCanWrap(startFrame)) { |
| 2974 | flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE; |
| 2975 | } |
| 2976 | if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::NoBreaks) { |
| 2977 | flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS; |
| 2978 | } |
| 2979 | if (textStyle->mTextTransform & StyleTextTransform::CAPITALIZE) { |
| 2980 | flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION; |
| 2981 | } |
| 2982 | if (textStyle->mHyphens == StyleHyphens::Auto && |
| 2983 | textStyle->mLineBreak != StyleLineBreak::Anywhere) { |
| 2984 | flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION; |
| 2985 | } |
| 2986 | |
| 2987 | if (HasCompressedLeadingWhitespace(startFrame, textStyle, |
| 2988 | mappedFlow->GetContentEnd(), iter)) { |
| 2989 | mLineBreaker.AppendInvisibleWhitespace(flags); |
| 2990 | } |
| 2991 | |
| 2992 | if (length > 0) { |
| 2993 | BreakSink* sink = mSkipIncompleteTextRuns ? nullptr : (*breakSink).get(); |
| 2994 | if (mDoubleByteText) { |
| 2995 | const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr); |
| 2996 | mLineBreaker.AppendText(hyphenationLanguage, text + offset, length, |
| 2997 | flags, sink); |
| 2998 | } else { |
| 2999 | const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr); |
| 3000 | mLineBreaker.AppendText(hyphenationLanguage, text + offset, length, |
| 3001 | flags, sink); |
| 3002 | } |
| 3003 | } |
| 3004 | |
| 3005 | iter = iterNext; |
| 3006 | } |
| 3007 | } |
| 3008 | |
| 3009 | static bool MayCharacterHaveEmphasisMark(uint32_t aCh) { |
| 3010 | // Punctuation characters that *can* take emphasis marks (exceptions to the |
| 3011 | // rule that characters with GeneralCategory=P* do not take emphasis), as per |
| 3012 | // https://drafts.csswg.org/css-text-decor/#text-emphasis-style-property |
| 3013 | // There are no non-BMP codepoints in the punctuation exceptions, so we can |
| 3014 | // just use a 16-bit string to list & check them. |
| 3015 | constexpr nsLiteralString kPunctuationAcceptsEmphasis = |
| 3016 | u"\x0023" // # NUMBER SIGN |
| 3017 | u"\x0025" // % PERCENT SIGN |
| 3018 | u"\x0026" // & AMPERSAND |
| 3019 | u"\x0040" // @ COMMERCIAL AT |
| 3020 | u"\x00A7" // § SECTION SIGN |
| 3021 | u"\x00B6" // ¶ PILCROW SIGN |
| 3022 | u"\x0609" // ؉ ARABIC-INDIC PER MILLE SIGN |
| 3023 | u"\x060A" // ؊ ARABIC-INDIC PER TEN THOUSAND SIGN |
| 3024 | u"\x066A" // ٪ ARABIC PERCENT SIGN |
| 3025 | u"\x2030" // ‰ PER MILLE SIGN |
| 3026 | u"\x2031" // ‱ PER TEN THOUSAND SIGN |
| 3027 | u"\x204A" // ⁊ TIRONIAN SIGN ET |
| 3028 | u"\x204B" // ⁋ REVERSED PILCROW SIGN |
| 3029 | u"\x2053" // ⁓ SWUNG DASH |
| 3030 | u"\x303D" // 〽️ PART ALTERNATION MARK |
| 3031 | // Characters that are NFKD-equivalent to the above, extracted from |
| 3032 | // UnicodeData.txt. |
| 3033 | u"\xFE5F" // SMALL NUMBER SIGN;Po;0;ET;<small> 0023;;;;N;;;;; |
| 3034 | u"\xFE60" // SMALL AMPERSAND;Po;0;ON;<small> 0026;;;;N;;;;; |
| 3035 | u"\xFE6A" // SMALL PERCENT SIGN;Po;0;ET;<small> 0025;;;;N;;;;; |
| 3036 | u"\xFE6B" // SMALL COMMERCIAL AT;Po;0;ON;<small> 0040;;;;N;;;;; |
| 3037 | u"\xFF03" // FULLWIDTH NUMBER SIGN;Po;0;ET;<wide> 0023;;;;N;;;;; |
| 3038 | u"\xFF05" // FULLWIDTH PERCENT SIGN;Po;0;ET;<wide> 0025;;;;N;;;;; |
| 3039 | u"\xFF06" // FULLWIDTH AMPERSAND;Po;0;ON;<wide> 0026;;;;N;;;;; |
| 3040 | u"\xFF20"_ns; // FULLWIDTH COMMERCIAL AT;Po;0;ON;<wide> 0040;;;;N;;;;; |
| 3041 | |
| 3042 | switch (unicode::GetGenCategory(aCh)) { |
| 3043 | case nsUGenCategory::kSeparator: // whitespace, line- & para-separators |
| 3044 | return false; |
| 3045 | case nsUGenCategory::kOther: // control categories |
| 3046 | return false; |
| 3047 | case nsUGenCategory::kPunctuation: |
| 3048 | return aCh <= 0xffff && |
| 3049 | kPunctuationAcceptsEmphasis.Contains(char16_t(aCh)); |
| 3050 | default: |
| 3051 | return true; |
| 3052 | } |
| 3053 | } |
| 3054 | |
| 3055 | void BuildTextRunsScanner::SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, |
| 3056 | const void* aTextPtr) { |
| 3057 | if (!mDoubleByteText) { |
| 3058 | auto text = reinterpret_cast<const uint8_t*>(aTextPtr); |
| 3059 | for (auto i : IntegerRange(aTextRun->GetLength())) { |
| 3060 | if (!MayCharacterHaveEmphasisMark(text[i])) { |
| 3061 | aTextRun->SetNoEmphasisMark(i); |
| 3062 | } |
| 3063 | } |
| 3064 | } else { |
| 3065 | auto text = reinterpret_cast<const char16_t*>(aTextPtr); |
| 3066 | auto length = aTextRun->GetLength(); |
| 3067 | for (size_t i = 0; i < length; ++i) { |
| 3068 | if (i + 1 < length && IsSurrogatePair(text[i], text[i + 1])) { |
| 3069 | uint32_t ch = SurrogateToUCS4(text[i], text[i + 1]); |
| 3070 | if (!MayCharacterHaveEmphasisMark(ch)) { |
| 3071 | aTextRun->SetNoEmphasisMark(i); |
| 3072 | aTextRun->SetNoEmphasisMark(i + 1); |
| 3073 | } |
| 3074 | ++i; |
| 3075 | } else { |
| 3076 | if (!MayCharacterHaveEmphasisMark(uint32_t(text[i]))) { |
| 3077 | aTextRun->SetNoEmphasisMark(i); |
| 3078 | } |
| 3079 | } |
| 3080 | } |
| 3081 | } |
| 3082 | } |
| 3083 | |
| 3084 | // Find the flow corresponding to aContent in aUserData |
| 3085 | static inline TextRunMappedFlow* FindFlowForContent( |
| 3086 | TextRunUserData* aUserData, nsIContent* aContent, |
| 3087 | TextRunMappedFlow* userMappedFlows) { |
| 3088 | // Find the flow that contains us |
| 3089 | int32_t i = aUserData->mLastFlowIndex; |
| 3090 | int32_t delta = 1; |
| 3091 | int32_t sign = 1; |
| 3092 | // Search starting at the current position and examine close-by |
| 3093 | // positions first, moving further and further away as we go. |
| 3094 | while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) { |
| 3095 | TextRunMappedFlow* flow = &userMappedFlows[i]; |
| 3096 | if (flow->mStartFrame->GetContent() == aContent) { |
| 3097 | return flow; |
| 3098 | } |
| 3099 | |
| 3100 | i += delta; |
| 3101 | sign = -sign; |
| 3102 | delta = -delta + sign; |
| 3103 | } |
| 3104 | |
| 3105 | // We ran into an array edge. Add |delta| to |i| once more to get |
| 3106 | // back to the side where we still need to search, then step in |
| 3107 | // the |sign| direction. |
| 3108 | i += delta; |
| 3109 | if (sign > 0) { |
| 3110 | for (; i < int32_t(aUserData->mMappedFlowCount); ++i) { |
| 3111 | TextRunMappedFlow* flow = &userMappedFlows[i]; |
| 3112 | if (flow->mStartFrame->GetContent() == aContent) { |
| 3113 | return flow; |
| 3114 | } |
| 3115 | } |
| 3116 | } else { |
| 3117 | for (; i >= 0; --i) { |
| 3118 | TextRunMappedFlow* flow = &userMappedFlows[i]; |
| 3119 | if (flow->mStartFrame->GetContent() == aContent) { |
| 3120 | return flow; |
| 3121 | } |
| 3122 | } |
| 3123 | } |
| 3124 | |
| 3125 | return nullptr; |
| 3126 | } |
| 3127 | |
| 3128 | void BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun, |
| 3129 | float aInflation) { |
| 3130 | for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { |
| 3131 | MappedFlow* mappedFlow = &mMappedFlows[i]; |
| 3132 | nsTextFrame* startFrame = mappedFlow->mStartFrame; |
| 3133 | nsTextFrame* endFrame = mappedFlow->mEndFrame; |
| 3134 | nsTextFrame* f; |
| 3135 | for (f = startFrame; f != endFrame; f = f->GetNextContinuation()) { |
| 3136 | #ifdef DEBUG_roc |
| 3137 | if (f->GetTextRun(mWhichTextRun)) { |
| 3138 | gfxTextRun* textRun = f->GetTextRun(mWhichTextRun); |
| 3139 | if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { |
| 3140 | if (mMappedFlows[0].mStartFrame != GetFrameForSimpleFlow(textRun)) { |
| 3141 | NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!")NS_DebugBreak(NS_DEBUG_WARNING, "REASSIGNING SIMPLE FLOW TEXT RUN!" , nullptr, "./../../../layout/generic/nsTextFrame.cpp", 3141); |
| 3142 | } |
| 3143 | } else { |
| 3144 | auto userData = |
| 3145 | static_cast<TextRunUserData*>(aTextRun->GetUserData()); |
| 3146 | TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun); |
| 3147 | if (userData->mMappedFlowCount >= mMappedFlows.Length() || |
| 3148 | userMappedFlows[userData->mMappedFlowCount - 1].mStartFrame != |
| 3149 | mMappedFlows[userdata->mMappedFlowCount - 1].mStartFrame) { |
| 3150 | NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!")NS_DebugBreak(NS_DEBUG_WARNING, "REASSIGNING MULTIFLOW TEXT RUN (not append)!" , nullptr, "./../../../layout/generic/nsTextFrame.cpp", 3150); |
| 3151 | } |
| 3152 | } |
| 3153 | } |
| 3154 | #endif |
| 3155 | |
| 3156 | gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun); |
| 3157 | if (oldTextRun) { |
| 3158 | nsTextFrame* firstFrame = nullptr; |
| 3159 | uint32_t startOffset = 0; |
| 3160 | if (oldTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { |
| 3161 | firstFrame = GetFrameForSimpleFlow(oldTextRun); |
| 3162 | } else { |
| 3163 | auto userData = |
| 3164 | static_cast<TextRunUserData*>(oldTextRun->GetUserData()); |
| 3165 | TextRunMappedFlow* userMappedFlows = GetMappedFlows(oldTextRun); |
| 3166 | firstFrame = userMappedFlows[0].mStartFrame; |
| 3167 | if (MOZ_UNLIKELY(f != firstFrame)(__builtin_expect(!!(f != firstFrame), 0))) { |
| 3168 | TextRunMappedFlow* flow = |
| 3169 | FindFlowForContent(userData, f->GetContent(), userMappedFlows); |
| 3170 | if (flow) { |
| 3171 | startOffset = flow->mDOMOffsetToBeforeTransformOffset; |
| 3172 | } else { |
| 3173 | NS_ERROR("Can't find flow containing frame 'f'")do { NS_DebugBreak(NS_DEBUG_ASSERTION, "Can't find flow containing frame 'f'" , "Error", "./../../../layout/generic/nsTextFrame.cpp", 3173) ; MOZ_PretendNoReturn(); } while (0); |
| 3174 | } |
| 3175 | } |
| 3176 | } |
| 3177 | |
| 3178 | // Optimization: if |f| is the first frame in the flow then there are no |
| 3179 | // prev-continuations that use |oldTextRun|. |
| 3180 | nsTextFrame* clearFrom = nullptr; |
| 3181 | if (MOZ_UNLIKELY(f != firstFrame)(__builtin_expect(!!(f != firstFrame), 0))) { |
| 3182 | // If all the frames in the mapped flow starting at |f| (inclusive) |
| 3183 | // are empty then we let the prev-continuations keep the old text run. |
| 3184 | gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset, |
| 3185 | f->GetContentOffset()); |
| 3186 | uint32_t textRunOffset = |
| 3187 | iter.ConvertOriginalToSkipped(f->GetContentOffset()); |
| 3188 | clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr; |
| 3189 | } |
| 3190 | f->ClearTextRun(clearFrom, mWhichTextRun); |
| 3191 | |
| 3192 | #ifdef DEBUG1 |
| 3193 | if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) { |
| 3194 | // oldTextRun was destroyed - assert that we don't reference it. |
| 3195 | for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) { |
| 3196 | NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun,do { if (!(oldTextRun != mBreakSinks[j]->mTextRun)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "destroyed text run is still in use", "oldTextRun != mBreakSinks[j]->mTextRun" , "./../../../layout/generic/nsTextFrame.cpp", 3197); MOZ_PretendNoReturn (); } } while (0) |
| 3197 | "destroyed text run is still in use")do { if (!(oldTextRun != mBreakSinks[j]->mTextRun)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "destroyed text run is still in use", "oldTextRun != mBreakSinks[j]->mTextRun" , "./../../../layout/generic/nsTextFrame.cpp", 3197); MOZ_PretendNoReturn (); } } while (0); |
| 3198 | } |
| 3199 | } |
| 3200 | #endif |
| 3201 | } |
| 3202 | f->SetTextRun(aTextRun, mWhichTextRun, aInflation); |
| 3203 | } |
| 3204 | // Set this bit now; we can't set it any earlier because |
| 3205 | // f->ClearTextRun() might clear it out. |
| 3206 | nsFrameState whichTextRunState = |
| 3207 | startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun |
| 3208 | ? TEXT_IN_TEXTRUN_USER_DATA |
| 3209 | : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA; |
| 3210 | startFrame->AddStateBits(whichTextRunState); |
| 3211 | } |
| 3212 | } |
| 3213 | |
| 3214 | NS_QUERYFRAME_HEAD(nsTextFrame)void* nsTextFrame ::QueryFrame(FrameIID id) const { switch (id ) { |
| 3215 | NS_QUERYFRAME_ENTRY(nsTextFrame)case nsTextFrame ::kFrameIID: { static_assert( std::is_same_v <nsTextFrame, nsTextFrame ::Has_NS_DECL_QUERYFRAME_TARGET> , "nsTextFrame" " must declare itself as a queryframe target" ); return const_cast<nsTextFrame*>(static_cast<const nsTextFrame*>(this)); } |
| 3216 | NS_QUERYFRAME_TAIL_INHERITING(nsIFrame)default: break; } return nsIFrame ::QueryFrame(id); } |
| 3217 | |
| 3218 | gfxSkipCharsIterator nsTextFrame::EnsureTextRun( |
| 3219 | TextRunType aWhichTextRun, DrawTarget* aRefDrawTarget, |
| 3220 | nsIFrame* aLineContainer, const nsLineList::iterator* aLine, |
| 3221 | uint32_t* aFlowEndInTextRun) { |
| 3222 | gfxTextRun* textRun = GetTextRun(aWhichTextRun); |
| 3223 | if (!textRun || (aLine && (*aLine)->GetInvalidateTextRuns())) { |
| 3224 | RefPtr<DrawTarget> refDT = aRefDrawTarget; |
| 3225 | if (!refDT) { |
| 3226 | refDT = CreateReferenceDrawTarget(this); |
| 3227 | } |
| 3228 | if (refDT) { |
| 3229 | BuildTextRuns(refDT, this, aLineContainer, aLine, aWhichTextRun); |
| 3230 | } |
| 3231 | textRun = GetTextRun(aWhichTextRun); |
| 3232 | if (!textRun) { |
| 3233 | // A text run was not constructed for this frame. This is bad. The caller |
| 3234 | // will check mTextRun. |
| 3235 | return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(), |
| 3236 | 0); |
| 3237 | } |
| 3238 | TabWidthStore* tabWidths = GetProperty(TabWidthProperty()); |
| 3239 | if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) { |
| 3240 | RemoveProperty(TabWidthProperty()); |
| 3241 | } |
| 3242 | } |
| 3243 | |
| 3244 | if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { |
| 3245 | if (aFlowEndInTextRun) { |
| 3246 | *aFlowEndInTextRun = textRun->GetLength(); |
| 3247 | } |
| 3248 | return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset); |
| 3249 | } |
| 3250 | |
| 3251 | auto userData = static_cast<TextRunUserData*>(textRun->GetUserData()); |
| 3252 | TextRunMappedFlow* userMappedFlows = GetMappedFlows(textRun); |
| 3253 | TextRunMappedFlow* flow = |
| 3254 | FindFlowForContent(userData, mContent, userMappedFlows); |
| 3255 | if (flow) { |
| 3256 | // Since textruns can only contain one flow for a given content element, |
| 3257 | // this must be our flow. |
| 3258 | uint32_t flowIndex = flow - userMappedFlows; |
| 3259 | userData->mLastFlowIndex = flowIndex; |
| 3260 | gfxSkipCharsIterator iter(textRun->GetSkipChars(), |
| 3261 | flow->mDOMOffsetToBeforeTransformOffset, |
| 3262 | mContentOffset); |
| 3263 | if (aFlowEndInTextRun) { |
| 3264 | if (flowIndex + 1 < userData->mMappedFlowCount) { |
| 3265 | gfxSkipCharsIterator end(textRun->GetSkipChars()); |
| 3266 | *aFlowEndInTextRun = end.ConvertOriginalToSkipped( |
| 3267 | flow[1].mStartFrame->GetContentOffset() + |
| 3268 | flow[1].mDOMOffsetToBeforeTransformOffset); |
| 3269 | } else { |
| 3270 | *aFlowEndInTextRun = textRun->GetLength(); |
| 3271 | } |
| 3272 | } |
| 3273 | return iter; |
| 3274 | } |
| 3275 | |
| 3276 | NS_ERROR("Can't find flow containing this frame???")do { NS_DebugBreak(NS_DEBUG_ASSERTION, "Can't find flow containing this frame???" , "Error", "./../../../layout/generic/nsTextFrame.cpp", 3276) ; MOZ_PretendNoReturn(); } while (0); |
| 3277 | return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(), 0); |
| 3278 | } |
| 3279 | |
| 3280 | static uint32_t GetEndOfTrimmedText(const CharacterDataBuffer& aBuffer, |
| 3281 | const nsStyleText* aStyleText, |
| 3282 | uint32_t aStart, uint32_t aEnd, |
| 3283 | gfxSkipCharsIterator* aIterator, |
| 3284 | bool aAllowHangingWS = false) { |
| 3285 | aIterator->SetSkippedOffset(aEnd); |
| 3286 | while (aIterator->GetSkippedOffset() > aStart) { |
| 3287 | aIterator->AdvanceSkipped(-1); |
| 3288 | if (!IsTrimmableSpace(aBuffer, aIterator->GetOriginalOffset(), aStyleText, |
| 3289 | aAllowHangingWS)) { |
| 3290 | return aIterator->GetSkippedOffset() + 1; |
| 3291 | } |
| 3292 | } |
| 3293 | return aStart; |
| 3294 | } |
| 3295 | |
| 3296 | nsTextFrame::TrimmedOffsets nsTextFrame::GetTrimmedOffsets( |
| 3297 | const class CharacterDataBuffer& aBuffer, TrimmedOffsetFlags aFlags) const { |
| 3298 | NS_ASSERTION(mTextRun, "Need textrun here")do { if (!(mTextRun)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Need textrun here" , "mTextRun", "./../../../layout/generic/nsTextFrame.cpp", 3298 ); MOZ_PretendNoReturn(); } } while (0); |
| 3299 | if (!(aFlags & TrimmedOffsetFlags::NotPostReflow)) { |
| 3300 | // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS |
| 3301 | // to be set correctly. If our parent wasn't reflowed due to the frame |
| 3302 | // tree being too deep then the return value doesn't matter. |
| 3303 | NS_ASSERTION(do { if (!(!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent ()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE))) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Can only call this on frames that have been reflowed" , "!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)" , "./../../../layout/generic/nsTextFrame.cpp", 3306); MOZ_PretendNoReturn (); } } while (0) |
| 3304 | !HasAnyStateBits(NS_FRAME_FIRST_REFLOW) ||do { if (!(!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent ()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE))) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Can only call this on frames that have been reflowed" , "!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)" , "./../../../layout/generic/nsTextFrame.cpp", 3306); MOZ_PretendNoReturn (); } } while (0) |
| 3305 | GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE),do { if (!(!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent ()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE))) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Can only call this on frames that have been reflowed" , "!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)" , "./../../../layout/generic/nsTextFrame.cpp", 3306); MOZ_PretendNoReturn (); } } while (0) |
| 3306 | "Can only call this on frames that have been reflowed")do { if (!(!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent ()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE))) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Can only call this on frames that have been reflowed" , "!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE)" , "./../../../layout/generic/nsTextFrame.cpp", 3306); MOZ_PretendNoReturn (); } } while (0); |
| 3307 | NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IN_REFLOW),do { if (!(!HasAnyStateBits(NS_FRAME_IN_REFLOW))) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Can only call this on frames that are not being reflowed" , "!HasAnyStateBits(NS_FRAME_IN_REFLOW)", "./../../../layout/generic/nsTextFrame.cpp" , 3308); MOZ_PretendNoReturn(); } } while (0) |
| 3308 | "Can only call this on frames that are not being reflowed")do { if (!(!HasAnyStateBits(NS_FRAME_IN_REFLOW))) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Can only call this on frames that are not being reflowed" , "!HasAnyStateBits(NS_FRAME_IN_REFLOW)", "./../../../layout/generic/nsTextFrame.cpp" , 3308); MOZ_PretendNoReturn(); } } while (0); |
| 3309 | } |
| 3310 | |
| 3311 | TrimmedOffsets offsets = {GetContentOffset(), GetContentLength()}; |
| 3312 | const nsStyleText* textStyle = StyleText(); |
| 3313 | // Note that pre-line newlines should still allow us to trim spaces |
| 3314 | // for display |
| 3315 | if (textStyle->WhiteSpaceIsSignificant()) { |
| 3316 | return offsets; |
| 3317 | } |
| 3318 | |
| 3319 | if (!(aFlags & TrimmedOffsetFlags::NoTrimBefore) && |
| 3320 | ((aFlags & TrimmedOffsetFlags::NotPostReflow) || |
| 3321 | HasAnyStateBits(TEXT_START_OF_LINE))) { |
| 3322 | int32_t whitespaceCount = GetTrimmableWhitespaceCount( |
| 3323 | aBuffer, offsets.mStart, offsets.mLength, 1); |
| 3324 | offsets.mStart += whitespaceCount; |
| 3325 | offsets.mLength -= whitespaceCount; |
| 3326 | } |
| 3327 | |
| 3328 | if (!(aFlags & TrimmedOffsetFlags::NoTrimAfter) && |
| 3329 | ((aFlags & TrimmedOffsetFlags::NotPostReflow) || |
| 3330 | HasAnyStateBits(TEXT_END_OF_LINE))) { |
| 3331 | // This treats a trailing 'pre-line' newline as trimmable. That's fine, |
| 3332 | // it's actually what we want since we want whitespace before it to |
| 3333 | // be trimmed. |
| 3334 | int32_t whitespaceCount = GetTrimmableWhitespaceCount( |
| 3335 | aBuffer, offsets.GetEnd() - 1, offsets.mLength, -1); |
| 3336 | offsets.mLength -= whitespaceCount; |
| 3337 | } |
| 3338 | return offsets; |
| 3339 | } |
| 3340 | |
| 3341 | static bool IsJustifiableCharacter(const nsStyleText* aTextStyle, |
| 3342 | const CharacterDataBuffer& aBuffer, |
| 3343 | int32_t aPos, bool aLangIsCJ) { |
| 3344 | NS_ASSERTION(aPos >= 0, "negative position?!")do { if (!(aPos >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "negative position?!", "aPos >= 0", "./../../../layout/generic/nsTextFrame.cpp" , 3344); MOZ_PretendNoReturn(); } } while (0); |
| 3345 | |
| 3346 | StyleTextJustify justifyStyle = aTextStyle->mTextJustify; |
| 3347 | if (justifyStyle == StyleTextJustify::None) { |
| 3348 | return false; |
| 3349 | } |
| 3350 | |
| 3351 | const char16_t ch = aBuffer.CharAt(AssertedCast<uint32_t>(aPos)); |
| 3352 | if (ch == '\n' || ch == '\t' || ch == '\r') { |
| 3353 | return !aTextStyle->WhiteSpaceIsSignificant(); |
| 3354 | } |
| 3355 | if (ch == ' ' || ch == CH_NBSP160) { |
| 3356 | // Don't justify spaces that are combined with diacriticals |
| 3357 | if (!aBuffer.Is2b()) { |
| 3358 | return true; |
| 3359 | } |
| 3360 | return !nsTextFrameUtils::IsSpaceCombiningSequenceTail( |
| 3361 | aBuffer.Get2b() + aPos + 1, aBuffer.GetLength() - (aPos + 1)); |
| 3362 | } |
| 3363 | |
| 3364 | if (justifyStyle == StyleTextJustify::InterCharacter) { |
| 3365 | char32_t u = aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aPos)); |
| 3366 | if (intl::UnicodeProperties::IsCursiveScript(u)) { |
| 3367 | return false; |
| 3368 | } |
| 3369 | return true; |
| 3370 | } else if (justifyStyle == StyleTextJustify::InterWord) { |
| 3371 | return false; |
| 3372 | } |
| 3373 | |
| 3374 | // text-justify: auto |
| 3375 | if (ch < 0x2150u) { |
| 3376 | return false; |
| 3377 | } |
| 3378 | if (aLangIsCJ) { |
| 3379 | if ( // Number Forms, Arrows, Mathematical Operators |
| 3380 | (0x2150u <= ch && ch <= 0x22ffu) || |
| 3381 | // Enclosed Alphanumerics |
| 3382 | (0x2460u <= ch && ch <= 0x24ffu) || |
| 3383 | // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats |
| 3384 | (0x2580u <= ch && ch <= 0x27bfu) || |
| 3385 | // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B, |
| 3386 | // Miscellaneous Mathematical Symbols-B, |
| 3387 | // Supplemental Mathematical Operators, Miscellaneous Symbols and Arrows |
| 3388 | (0x27f0u <= ch && ch <= 0x2bffu) || |
| 3389 | // CJK Radicals Supplement, CJK Radicals Supplement, Ideographic |
| 3390 | // Description Characters, CJK Symbols and Punctuation, Hiragana, |
| 3391 | // Katakana, Bopomofo |
| 3392 | (0x2e80u <= ch && ch <= 0x312fu) || |
| 3393 | // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions, |
| 3394 | // Enclosed CJK Letters and Months, CJK Compatibility, |
| 3395 | // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols, |
| 3396 | // CJK Unified Ideographs, Yi Syllables, Yi Radicals |
| 3397 | (0x3190u <= ch && ch <= 0xabffu) || |
| 3398 | // CJK Compatibility Ideographs |
| 3399 | (0xf900u <= ch && ch <= 0xfaffu) || |
| 3400 | // Halfwidth and Fullwidth Forms (a part) |
| 3401 | (0xff5eu <= ch && ch <= 0xff9fu)) { |
| 3402 | return true; |
| 3403 | } |
| 3404 | if (IsHighSurrogate(ch)) { |
| 3405 | if (char32_t u = aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aPos))) { |
| 3406 | // CJK Unified Ideographs Extension B, |
| 3407 | // CJK Unified Ideographs Extension C, |
| 3408 | // CJK Unified Ideographs Extension D, |
| 3409 | // CJK Compatibility Ideographs Supplement |
| 3410 | if (0x20000u <= u && u <= 0x2ffffu) { |
| 3411 | return true; |
| 3412 | } |
| 3413 | } |
| 3414 | } |
| 3415 | } |
| 3416 | return false; |
| 3417 | } |
| 3418 | |
| 3419 | void nsTextFrame::ClearMetrics(ReflowOutput& aMetrics) { |
| 3420 | aMetrics.ClearSize(); |
| 3421 | aMetrics.SetBlockStartAscent(0); |
| 3422 | mAscent = 0; |
| 3423 | |
| 3424 | AddStateBits(TEXT_NO_RENDERED_GLYPHS); |
| 3425 | } |
| 3426 | |
| 3427 | static int32_t FindChar(const CharacterDataBuffer& characterDataBuffer, |
| 3428 | int32_t aOffset, int32_t aLength, char16_t ch) { |
| 3429 | int32_t i = 0; |
| 3430 | if (characterDataBuffer.Is2b()) { |
| 3431 | const char16_t* str = characterDataBuffer.Get2b() + aOffset; |
| 3432 | for (; i < aLength; ++i) { |
| 3433 | if (*str == ch) { |
| 3434 | return i + aOffset; |
| 3435 | } |
| 3436 | ++str; |
| 3437 | } |
| 3438 | } else { |
| 3439 | if (uint16_t(ch) <= 0xFF) { |
| 3440 | const char* str = characterDataBuffer.Get1b() + aOffset; |
| 3441 | const void* p = memchr(str, ch, aLength); |
| 3442 | if (p) { |
| 3443 | return (static_cast<const char*>(p) - str) + aOffset; |
| 3444 | } |
| 3445 | } |
| 3446 | } |
| 3447 | return -1; |
| 3448 | } |
| 3449 | |
| 3450 | static bool IsChineseOrJapanese(const nsTextFrame* aFrame) { |
| 3451 | if (aFrame->ShouldSuppressLineBreak()) { |
| 3452 | // Always treat ruby as CJ language so that those characters can |
| 3453 | // be expanded properly even when surrounded by other language. |
| 3454 | return true; |
| 3455 | } |
| 3456 | |
| 3457 | nsAtom* language = aFrame->StyleFont()->GetLangAtom(); |
| 3458 | if (!language) { |
| 3459 | return false; |
| 3460 | } |
| 3461 | return nsStyleUtil::MatchesLanguagePrefix(language, u"ja") || |
| 3462 | nsStyleUtil::MatchesLanguagePrefix(language, u"zh"); |
| 3463 | } |
| 3464 | |
| 3465 | #ifdef DEBUG1 |
| 3466 | static bool IsInBounds(const gfxSkipCharsIterator& aStart, |
| 3467 | int32_t aContentLength, gfxTextRun::Range aRange) { |
| 3468 | if (aStart.GetSkippedOffset() > aRange.start) { |
| 3469 | return false; |
| 3470 | } |
| 3471 | if (aContentLength == INT32_MAX(2147483647)) { |
| 3472 | return true; |
| 3473 | } |
| 3474 | gfxSkipCharsIterator iter(aStart); |
| 3475 | iter.AdvanceOriginal(aContentLength); |
| 3476 | return iter.GetSkippedOffset() >= aRange.end; |
| 3477 | } |
| 3478 | #endif |
| 3479 | |
| 3480 | nsTextFrame::PropertyProvider::PropertyProvider( |
| 3481 | gfxTextRun* aTextRun, const nsStyleText* aTextStyle, |
| 3482 | const class CharacterDataBuffer& aBuffer, nsTextFrame* aFrame, |
| 3483 | const gfxSkipCharsIterator& aStart, int32_t aLength, |
| 3484 | nsIFrame* aLineContainer, nscoord aOffsetFromBlockOriginForTabs, |
| 3485 | nsTextFrame::TextRunType aWhichTextRun, bool aAtStartOfLine) |
| 3486 | : mTextRun(aTextRun), |
| 3487 | mFontGroup(nullptr), |
| 3488 | mTextStyle(aTextStyle), |
| 3489 | mCharacterDataBuffer(aBuffer), |
| 3490 | mLineContainer(aLineContainer), |
| 3491 | mFrame(aFrame), |
| 3492 | mStart(aStart), |
| 3493 | mTempIterator(aStart), |
| 3494 | mTabWidths(nullptr), |
| 3495 | mTabWidthsAnalyzedLimit(0), |
| 3496 | mLength(aLength), |
| 3497 | mWordSpacing(ResolveWordSpacing(aFrame, *aTextStyle)), |
| 3498 | mLetterSpacing(ResolveLetterSpacing(aFrame, *aTextStyle)), |
| 3499 | mMinTabAdvance(-1.0), |
| 3500 | mHyphenWidth(-1), |
| 3501 | mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs), |
| 3502 | mJustificationArrayStart(0), |
| 3503 | mReflowing(true), |
| 3504 | mWhichTextRun(aWhichTextRun) { |
| 3505 | NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?")do { if (!(mStart.IsInitialized())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Start not initialized?", "mStart.IsInitialized()", "./../../../layout/generic/nsTextFrame.cpp" , 3505); MOZ_PretendNoReturn(); } } while (0); |
| 3506 | if (aAtStartOfLine) { |
| 3507 | mStartOfLineOffset = mStart.GetSkippedOffset(); |
| 3508 | } |
| 3509 | InitTextAutospace(); |
| 3510 | } |
| 3511 | |
| 3512 | nsTextFrame::PropertyProvider::PropertyProvider( |
| 3513 | nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart, |
| 3514 | nsTextFrame::TextRunType aWhichTextRun, nsFontMetrics* aFontMetrics) |
| 3515 | : mTextRun(aFrame->GetTextRun(aWhichTextRun)), |
| 3516 | mFontGroup(nullptr), |
| 3517 | mFontMetrics(aFontMetrics), |
| 3518 | mTextStyle(aFrame->StyleText()), |
| 3519 | mCharacterDataBuffer(aFrame->CharacterDataBuffer()), |
| 3520 | mLineContainer(nullptr), |
| 3521 | mFrame(aFrame), |
| 3522 | mStart(aStart), |
| 3523 | mTempIterator(aStart), |
| 3524 | mTabWidths(nullptr), |
| 3525 | mTabWidthsAnalyzedLimit(0), |
| 3526 | mLength(aFrame->GetContentLength()), |
| 3527 | mWordSpacing(ResolveWordSpacing(aFrame, *mTextStyle)), |
| 3528 | mLetterSpacing(ResolveLetterSpacing(aFrame, *mTextStyle)), |
| 3529 | mMinTabAdvance(-1.0), |
| 3530 | mHyphenWidth(-1), |
| 3531 | mOffsetFromBlockOriginForTabs(0), |
| 3532 | mJustificationArrayStart(0), |
| 3533 | mReflowing(false), |
| 3534 | mWhichTextRun(aWhichTextRun) { |
| 3535 | NS_ASSERTION(mTextRun, "Textrun not initialized!")do { if (!(mTextRun)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Textrun not initialized!" , "mTextRun", "./../../../layout/generic/nsTextFrame.cpp", 3535 ); MOZ_PretendNoReturn(); } } while (0); |
| 3536 | InitTextAutospace(); |
| 3537 | } |
| 3538 | |
| 3539 | gfx::ShapedTextFlags nsTextFrame::PropertyProvider::GetShapedTextFlags() const { |
| 3540 | return nsLayoutUtils::GetTextRunOrientFlagsForStyle(mFrame->Style()); |
| 3541 | } |
| 3542 | |
| 3543 | already_AddRefed<DrawTarget> nsTextFrame::PropertyProvider::GetDrawTarget() |
| 3544 | const { |
| 3545 | return CreateReferenceDrawTarget(GetFrame()); |
| 3546 | } |
| 3547 | |
| 3548 | gfxFloat nsTextFrame::PropertyProvider::MinTabAdvance() const { |
| 3549 | if (mMinTabAdvance < 0.0) { |
| 3550 | mMinTabAdvance = GetMinTabAdvanceAppUnits(mTextRun); |
| 3551 | } |
| 3552 | return mMinTabAdvance; |
| 3553 | } |
| 3554 | |
| 3555 | /** |
| 3556 | * Finds the offset of the first character of the cluster containing aPos |
| 3557 | */ |
| 3558 | static void FindClusterStart(const gfxTextRun* aTextRun, int32_t aOriginalStart, |
| 3559 | gfxSkipCharsIterator* aPos) { |
| 3560 | while (aPos->GetOriginalOffset() > aOriginalStart) { |
| 3561 | if (aPos->IsOriginalCharSkipped() || |
| 3562 | aTextRun->IsClusterStart(aPos->GetSkippedOffset())) { |
| 3563 | break; |
| 3564 | } |
| 3565 | aPos->AdvanceOriginal(-1); |
| 3566 | } |
| 3567 | } |
| 3568 | |
| 3569 | /** |
| 3570 | * Finds the offset of the last character of the cluster containing aPos. |
| 3571 | * If aAllowSplitLigature is false, we also check for a ligature-group |
| 3572 | * start. |
| 3573 | */ |
| 3574 | static void FindClusterEnd(const gfxTextRun* aTextRun, int32_t aOriginalEnd, |
| 3575 | gfxSkipCharsIterator* aPos, |
| 3576 | bool aAllowSplitLigature = true) { |
| 3577 | MOZ_ASSERT(aPos->GetOriginalOffset() < aOriginalEnd,do { static_assert( mozilla::detail::AssertionConditionType< decltype(aPos->GetOriginalOffset() < aOriginalEnd)>:: isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(aPos->GetOriginalOffset() < aOriginalEnd))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("aPos->GetOriginalOffset() < aOriginalEnd" " (" "character outside string" ")", "./../../../layout/generic/nsTextFrame.cpp" , 3578); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aPos->GetOriginalOffset() < aOriginalEnd" ") (" "character outside string" ")"); do { MOZ_CrashSequence (__null, 3578); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 3578 | "character outside string")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aPos->GetOriginalOffset() < aOriginalEnd)>:: isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(aPos->GetOriginalOffset() < aOriginalEnd))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("aPos->GetOriginalOffset() < aOriginalEnd" " (" "character outside string" ")", "./../../../layout/generic/nsTextFrame.cpp" , 3578); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aPos->GetOriginalOffset() < aOriginalEnd" ") (" "character outside string" ")"); do { MOZ_CrashSequence (__null, 3578); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 3579 | |
| 3580 | aPos->AdvanceOriginal(1); |
| 3581 | while (aPos->GetOriginalOffset() < aOriginalEnd) { |
| 3582 | if (aPos->IsOriginalCharSkipped() || |
| 3583 | (aTextRun->IsClusterStart(aPos->GetSkippedOffset()) && |
| 3584 | (aAllowSplitLigature || |
| 3585 | aTextRun->IsLigatureGroupStart(aPos->GetSkippedOffset())))) { |
| 3586 | break; |
| 3587 | } |
| 3588 | aPos->AdvanceOriginal(1); |
| 3589 | } |
| 3590 | aPos->AdvanceOriginal(-1); |
| 3591 | } |
| 3592 | |
| 3593 | // Get the line number of aFrame in the lines referenced by aLineIter, if |
| 3594 | // known (returning -1 if we don't find it). |
| 3595 | static int32_t GetFrameLineNum(nsIFrame* aFrame, nsILineIterator* aLineIter) { |
| 3596 | if (!aLineIter) { |
| 3597 | return -1; |
| 3598 | } |
| 3599 | // If we don't find the frame directly, but its parent is an inline or other |
| 3600 | // "line participant" (e.g. nsFirstLineFrame), we want the line that the |
| 3601 | // inline ancestor is on. |
| 3602 | do { |
| 3603 | int32_t n = aLineIter->FindLineContaining(aFrame); |
| 3604 | if (n >= 0) { |
| 3605 | return n; |
| 3606 | } |
| 3607 | aFrame = aFrame->GetParent(); |
| 3608 | } while (aFrame && aFrame->IsLineParticipant()); |
| 3609 | return -1; |
| 3610 | } |
| 3611 | |
| 3612 | // Get the position of the first preserved newline in aFrame, if any, |
| 3613 | // returning -1 if none. |
| 3614 | static int32_t FindFirstNewlinePosition(const nsTextFrame* aFrame) { |
| 3615 | MOZ_ASSERT(aFrame->StyleText()->NewlineIsSignificantStyle(),do { static_assert( mozilla::detail::AssertionConditionType< decltype(aFrame->StyleText()->NewlineIsSignificantStyle ())>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(aFrame->StyleText()->NewlineIsSignificantStyle ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("aFrame->StyleText()->NewlineIsSignificantStyle()" " (" "how did the HasNewline flag get set?" ")", "./../../../layout/generic/nsTextFrame.cpp" , 3616); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFrame->StyleText()->NewlineIsSignificantStyle()" ") (" "how did the HasNewline flag get set?" ")"); do { MOZ_CrashSequence (__null, 3616); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 3616 | "how did the HasNewline flag get set?")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aFrame->StyleText()->NewlineIsSignificantStyle ())>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(aFrame->StyleText()->NewlineIsSignificantStyle ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("aFrame->StyleText()->NewlineIsSignificantStyle()" " (" "how did the HasNewline flag get set?" ")", "./../../../layout/generic/nsTextFrame.cpp" , 3616); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFrame->StyleText()->NewlineIsSignificantStyle()" ") (" "how did the HasNewline flag get set?" ")"); do { MOZ_CrashSequence (__null, 3616); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 3617 | const auto& characterDataBuffer = aFrame->CharacterDataBuffer(); |
| 3618 | for (auto i = aFrame->GetContentOffset(); i < aFrame->GetContentEnd(); ++i) { |
| 3619 | if (characterDataBuffer.CharAt(i) == '\n') { |
| 3620 | return i; |
| 3621 | } |
| 3622 | } |
| 3623 | return -1; |
| 3624 | } |
| 3625 | |
| 3626 | // Get the position of the last preserved tab in aFrame that is before the |
| 3627 | // preserved newline at aNewlinePos. |
| 3628 | // Passing -1 for aNewlinePos means there is no preserved newline, so we look |
| 3629 | // for the last preserved tab in the whole content. |
| 3630 | // Returns -1 if no such preserved tab is present. |
| 3631 | static int32_t FindLastTabPositionBeforeNewline(const nsTextFrame* aFrame, |
| 3632 | int32_t aNewlinePos) { |
| 3633 | // We only call this if white-space is not being collapsed. |
| 3634 | MOZ_ASSERT(aFrame->StyleText()->WhiteSpaceIsSignificant(),do { static_assert( mozilla::detail::AssertionConditionType< decltype(aFrame->StyleText()->WhiteSpaceIsSignificant() )>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(aFrame->StyleText()->WhiteSpaceIsSignificant() ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "aFrame->StyleText()->WhiteSpaceIsSignificant()" " (" "how did the HasTab flag get set?" ")", "./../../../layout/generic/nsTextFrame.cpp", 3635); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aFrame->StyleText()->WhiteSpaceIsSignificant()" ") (" "how did the HasTab flag get set?" ")"); do { MOZ_CrashSequence (__null, 3635); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 3635 | "how did the HasTab flag get set?")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aFrame->StyleText()->WhiteSpaceIsSignificant() )>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(aFrame->StyleText()->WhiteSpaceIsSignificant() ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "aFrame->StyleText()->WhiteSpaceIsSignificant()" " (" "how did the HasTab flag get set?" ")", "./../../../layout/generic/nsTextFrame.cpp", 3635); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aFrame->StyleText()->WhiteSpaceIsSignificant()" ") (" "how did the HasTab flag get set?" ")"); do { MOZ_CrashSequence (__null, 3635); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 3636 | const auto& characterDataBuffer = aFrame->CharacterDataBuffer(); |
| 3637 | // If a non-negative newline position was given, we only need to search the |
| 3638 | // text before that offset. |
| 3639 | for (auto i = aNewlinePos < 0 ? aFrame->GetContentEnd() : aNewlinePos; |
| 3640 | i > aFrame->GetContentOffset(); --i) { |
| 3641 | if (characterDataBuffer.CharAt(i - 1) == '\t') { |
| 3642 | return i; |
| 3643 | } |
| 3644 | } |
| 3645 | return -1; |
| 3646 | } |
| 3647 | |
| 3648 | // Look for preserved tab or newline in the given frame or its following |
| 3649 | // siblings on the same line, to determine whether justification should be |
| 3650 | // suppressed in order to avoid disrupting tab-stop positions. |
| 3651 | // Returns the first such preserved whitespace char, or 0 if none found. |
| 3652 | static char NextPreservedWhiteSpaceOnLine(nsIFrame* aSibling, |
| 3653 | nsILineIterator* aLineIter, |
| 3654 | int32_t aLineNum) { |
| 3655 | while (aSibling) { |
| 3656 | // If we find a <br>, treat it like a newline. |
| 3657 | if (aSibling->IsBrFrame()) { |
| 3658 | return '\n'; |
| 3659 | } |
| 3660 | // If we've moved on to a later line, stop searching. |
| 3661 | if (GetFrameLineNum(aSibling, aLineIter) > aLineNum) { |
| 3662 | return 0; |
| 3663 | } |
| 3664 | // If we encounter an inline frame, recurse into it. |
| 3665 | if (aSibling->IsInlineFrame()) { |
| 3666 | auto* child = aSibling->PrincipalChildList().FirstChild(); |
| 3667 | char result = NextPreservedWhiteSpaceOnLine(child, aLineIter, aLineNum); |
| 3668 | if (result) { |
| 3669 | return result; |
| 3670 | } |
| 3671 | } |
| 3672 | // If we have a text frame, and whitespace is not collapsed, we need to |
| 3673 | // check its contents. |
| 3674 | if (aSibling->IsTextFrame()) { |
| 3675 | const auto* textStyle = aSibling->StyleText(); |
| 3676 | if (textStyle->WhiteSpaceOrNewlineIsSignificant()) { |
| 3677 | const auto* textFrame = static_cast<nsTextFrame*>(aSibling); |
| 3678 | const auto& characterDataBuffer = textFrame->CharacterDataBuffer(); |
| 3679 | for (auto i = textFrame->GetContentOffset(); |
| 3680 | i < textFrame->GetContentEnd(); ++i) { |
| 3681 | const char16_t ch = characterDataBuffer.CharAt(i); |
| 3682 | if (ch == '\n' && textStyle->NewlineIsSignificantStyle()) { |
| 3683 | return '\n'; |
| 3684 | } |
| 3685 | if (ch == '\t' && textStyle->WhiteSpaceIsSignificant()) { |
| 3686 | return '\t'; |
| 3687 | } |
| 3688 | } |
| 3689 | } |
| 3690 | } |
| 3691 | aSibling = aSibling->GetNextSibling(); |
| 3692 | } |
| 3693 | return 0; |
| 3694 | } |
| 3695 | |
| 3696 | static bool HasPreservedTabInFollowingSiblingOnLine(nsTextFrame* aFrame) { |
| 3697 | bool foundTab = false; |
| 3698 | |
| 3699 | nsIFrame* lineContainer = FindLineContainer(aFrame); |
| 3700 | nsILineIterator* iter = lineContainer->GetLineIterator(); |
| 3701 | int32_t line = GetFrameLineNum(aFrame, iter); |
| 3702 | char ws = NextPreservedWhiteSpaceOnLine(aFrame->GetNextSibling(), iter, line); |
| 3703 | if (ws == '\t') { |
| 3704 | foundTab = true; |
| 3705 | } else if (!ws) { |
| 3706 | // Didn't find a preserved tab or newline in our siblings; if our parent |
| 3707 | // (and its parent, etc) is an inline, we need to look at their following |
| 3708 | // siblings, too, as long as they're on the same line. |
| 3709 | const nsIFrame* maybeInline = aFrame->GetParent(); |
| 3710 | while (maybeInline && maybeInline->IsInlineFrame()) { |
| 3711 | ws = NextPreservedWhiteSpaceOnLine(maybeInline->GetNextSibling(), iter, |
| 3712 | line); |
| 3713 | if (ws == '\t') { |
| 3714 | foundTab = true; |
| 3715 | break; |
| 3716 | } |
| 3717 | if (ws == '\n') { |
| 3718 | break; |
| 3719 | } |
| 3720 | maybeInline = maybeInline->GetParent(); |
| 3721 | } |
| 3722 | } |
| 3723 | |
| 3724 | // We called lineContainer->GetLineIterator() above, but we mustn't |
| 3725 | // allow a block frame to retain this iterator if we're currently in |
| 3726 | // reflow, as it will become invalid as the line list is reflowed. |
| 3727 | if (lineContainer->HasAnyStateBits(NS_FRAME_IN_REFLOW) && |
| 3728 | lineContainer->IsBlockFrameOrSubclass()) { |
| 3729 | static_cast<nsBlockFrame*>(lineContainer)->ClearLineIterator(); |
| 3730 | } |
| 3731 | |
| 3732 | return foundTab; |
| 3733 | } |
| 3734 | |
| 3735 | JustificationInfo nsTextFrame::PropertyProvider::ComputeJustification( |
| 3736 | Range aRange, nsTArray<JustificationAssignment>* aAssignments) { |
| 3737 | JustificationInfo info; |
| 3738 | |
| 3739 | // Horizontal-in-vertical frame is orthogonal to the line, so it |
| 3740 | // doesn't actually include any justification opportunity inside. |
| 3741 | // The spec says such frame should be treated as a U+FFFC. Since we |
| 3742 | // do not insert justification opportunities on the sides of that |
| 3743 | // character, the sides of this frame are not justifiable either. |
| 3744 | if (mFrame->Style()->IsTextCombined()) { |
| 3745 | return info; |
| 3746 | } |
| 3747 | |
| 3748 | int32_t lastTab = -1; |
| 3749 | if (StaticPrefs::layout_css_text_align_justify_only_after_last_tab()) { |
| 3750 | // If there is a preserved tab on the line, we don't apply justification |
| 3751 | // until we're past its position. |
| 3752 | if (mTextStyle->WhiteSpaceIsSignificant()) { |
| 3753 | // If there is a preserved newline within the text, we don't need to look |
| 3754 | // beyond this frame, as following frames will not be on the same line. |
| 3755 | int32_t newlinePos = |
| 3756 | (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasNewline) |
| 3757 | ? FindFirstNewlinePosition(mFrame) |
| 3758 | : -1; |
| 3759 | if (newlinePos < 0) { |
| 3760 | // There's no preserved newline within this frame; if there's a tab |
| 3761 | // in a later sibling frame on the same line, we won't apply any |
| 3762 | // justification to this one. |
| 3763 | if (HasPreservedTabInFollowingSiblingOnLine(mFrame)) { |
| 3764 | return info; |
| 3765 | } |
| 3766 | } |
| 3767 | |
| 3768 | if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab) { |
| 3769 | // Find last tab character in the content; we won't justify anything |
| 3770 | // before that position, so that tab alignment remains correct. |
| 3771 | lastTab = FindLastTabPositionBeforeNewline(mFrame, newlinePos); |
| 3772 | } |
| 3773 | } |
| 3774 | } |
| 3775 | |
| 3776 | bool isCJ = IsChineseOrJapanese(mFrame); |
| 3777 | nsSkipCharsRunIterator run( |
| 3778 | mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aRange.Length()); |
| 3779 | run.SetOriginalOffset(aRange.start); |
| 3780 | mJustificationArrayStart = run.GetSkippedOffset(); |
| 3781 | |
| 3782 | nsTArray<JustificationAssignment> assignments; |
| 3783 | assignments.SetCapacity(aRange.Length()); |
| 3784 | while (run.NextRun()) { |
| 3785 | uint32_t originalOffset = run.GetOriginalOffset(); |
| 3786 | uint32_t skippedOffset = run.GetSkippedOffset(); |
| 3787 | uint32_t length = run.GetRunLength(); |
| 3788 | assignments.SetLength(skippedOffset + length - mJustificationArrayStart); |
| 3789 | |
| 3790 | gfxSkipCharsIterator iter = run.GetPos(); |
| 3791 | for (uint32_t i = 0; i < length; ++i) { |
| 3792 | uint32_t offset = originalOffset + i; |
| 3793 | if (!IsJustifiableCharacter(mTextStyle, mCharacterDataBuffer, offset, |
| 3794 | isCJ) || |
| 3795 | (lastTab >= 0 && offset <= uint32_t(lastTab))) { |
| 3796 | continue; |
| 3797 | } |
| 3798 | |
| 3799 | iter.SetOriginalOffset(offset); |
| 3800 | |
| 3801 | FindClusterStart(mTextRun, originalOffset, &iter); |
| 3802 | uint32_t firstCharOffset = iter.GetSkippedOffset(); |
| 3803 | uint32_t firstChar = firstCharOffset > mJustificationArrayStart |
| 3804 | ? firstCharOffset - mJustificationArrayStart |
| 3805 | : 0; |
| 3806 | if (!firstChar) { |
| 3807 | info.mIsStartJustifiable = true; |
| 3808 | } else { |
| 3809 | auto& assign = assignments[firstChar]; |
| 3810 | auto& prevAssign = assignments[firstChar - 1]; |
| 3811 | if (prevAssign.mGapsAtEnd) { |
| 3812 | prevAssign.mGapsAtEnd = 1; |
| 3813 | assign.mGapsAtStart = 1; |
| 3814 | } else { |
| 3815 | assign.mGapsAtStart = 2; |
| 3816 | info.mInnerOpportunities++; |
| 3817 | } |
| 3818 | } |
| 3819 | |
| 3820 | FindClusterEnd(mTextRun, originalOffset + length, &iter); |
| 3821 | uint32_t lastChar = iter.GetSkippedOffset() - mJustificationArrayStart; |
| 3822 | // Assign the two gaps temporary to the last char. If the next cluster is |
| 3823 | // justifiable as well, one of the gaps will be removed by code above. |
| 3824 | assignments[lastChar].mGapsAtEnd = 2; |
| 3825 | info.mInnerOpportunities++; |
| 3826 | |
| 3827 | // Skip the whole cluster |
| 3828 | i = iter.GetOriginalOffset() - originalOffset; |
| 3829 | } |
| 3830 | } |
| 3831 | |
| 3832 | if (!assignments.IsEmpty() && assignments.LastElement().mGapsAtEnd) { |
| 3833 | // We counted the expansion opportunity after the last character, |
| 3834 | // but it is not an inner opportunity. |
| 3835 | MOZ_ASSERT(info.mInnerOpportunities > 0)do { static_assert( mozilla::detail::AssertionConditionType< decltype(info.mInnerOpportunities > 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(info.mInnerOpportunities > 0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("info.mInnerOpportunities > 0", "./../../../layout/generic/nsTextFrame.cpp" , 3835); AnnotateMozCrashReason("MOZ_ASSERT" "(" "info.mInnerOpportunities > 0" ")"); do { MOZ_CrashSequence(__null, 3835); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 3836 | info.mInnerOpportunities--; |
| 3837 | info.mIsEndJustifiable = true; |
| 3838 | } |
| 3839 | |
| 3840 | if (aAssignments) { |
| 3841 | *aAssignments = std::move(assignments); |
| 3842 | } |
| 3843 | return info; |
| 3844 | } |
| 3845 | |
| 3846 | // aStart, aLength in transformed string offsets. |
| 3847 | // Returns false if no non-standard spacing was required. |
| 3848 | bool nsTextFrame::PropertyProvider::GetSpacing(Range aRange, |
| 3849 | Spacing* aSpacing) const { |
| 3850 | return GetSpacingInternal( |
| 3851 | aRange, aSpacing, |
| 3852 | !(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab)); |
| 3853 | } |
| 3854 | |
| 3855 | static bool CanAddSpacingBefore(const gfxTextRun* aTextRun, uint32_t aOffset, |
| 3856 | bool aNewlineIsSignificant) { |
| 3857 | const auto* g = aTextRun->GetCharacterGlyphs(); |
| 3858 | MOZ_ASSERT(aOffset < aTextRun->GetLength())do { static_assert( mozilla::detail::AssertionConditionType< decltype(aOffset < aTextRun->GetLength())>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(! !(aOffset < aTextRun->GetLength()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aOffset < aTextRun->GetLength()" , "./../../../layout/generic/nsTextFrame.cpp", 3858); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aOffset < aTextRun->GetLength()" ")" ); do { MOZ_CrashSequence(__null, 3858); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 3859 | if (aNewlineIsSignificant && g[aOffset].CharIsNewline()) { |
| 3860 | return false; |
| 3861 | } |
| 3862 | if (!aOffset) { |
| 3863 | return true; |
| 3864 | } |
| 3865 | return g[aOffset].IsClusterStart() && g[aOffset].IsLigatureGroupStart() && |
| 3866 | !g[aOffset - 1].CharIsFormattingControl() && !g[aOffset].CharIsTab(); |
| 3867 | } |
| 3868 | |
| 3869 | static bool CanAddSpacingAfter(const gfxTextRun* aTextRun, uint32_t aOffset, |
| 3870 | bool aNewlineIsSignificant) { |
| 3871 | const auto* g = aTextRun->GetCharacterGlyphs(); |
| 3872 | MOZ_ASSERT(aOffset < aTextRun->GetLength())do { static_assert( mozilla::detail::AssertionConditionType< decltype(aOffset < aTextRun->GetLength())>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(! !(aOffset < aTextRun->GetLength()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aOffset < aTextRun->GetLength()" , "./../../../layout/generic/nsTextFrame.cpp", 3872); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aOffset < aTextRun->GetLength()" ")" ); do { MOZ_CrashSequence(__null, 3872); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 3873 | if (aNewlineIsSignificant && g[aOffset].CharIsNewline()) { |
| 3874 | return false; |
| 3875 | } |
| 3876 | if (aOffset + 1 >= aTextRun->GetLength()) { |
| 3877 | return true; |
| 3878 | } |
| 3879 | return g[aOffset + 1].IsClusterStart() && |
| 3880 | g[aOffset + 1].IsLigatureGroupStart() && |
| 3881 | !g[aOffset].CharIsFormattingControl() && !g[aOffset].CharIsTab(); |
| 3882 | } |
| 3883 | |
| 3884 | static gfxFloat ComputeTabWidthAppUnits(const nsIFrame* aFrame) { |
| 3885 | const auto& tabSize = aFrame->StyleText()->mTabSize; |
| 3886 | if (tabSize.IsLength()) { |
| 3887 | nscoord w = tabSize.length._0.ToAppUnits(); |
| 3888 | MOZ_ASSERT(w >= 0)do { static_assert( mozilla::detail::AssertionConditionType< decltype(w >= 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(w >= 0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("w >= 0", "./../../../layout/generic/nsTextFrame.cpp" , 3888); AnnotateMozCrashReason("MOZ_ASSERT" "(" "w >= 0" ")" ); do { MOZ_CrashSequence(__null, 3888); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 3889 | return w; |
| 3890 | } |
| 3891 | |
| 3892 | MOZ_ASSERT(tabSize.IsNumber())do { static_assert( mozilla::detail::AssertionConditionType< decltype(tabSize.IsNumber())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(tabSize.IsNumber()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("tabSize.IsNumber()" , "./../../../layout/generic/nsTextFrame.cpp", 3892); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "tabSize.IsNumber()" ")"); do { MOZ_CrashSequence (__null, 3892); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 3893 | gfxFloat spaces = tabSize.number._0; |
| 3894 | MOZ_ASSERT(spaces >= 0)do { static_assert( mozilla::detail::AssertionConditionType< decltype(spaces >= 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(spaces >= 0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("spaces >= 0" , "./../../../layout/generic/nsTextFrame.cpp", 3894); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "spaces >= 0" ")"); do { MOZ_CrashSequence (__null, 3894); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 3895 | |
| 3896 | const nsIFrame* cb = aFrame->GetContainingBlock(0, aFrame->StyleDisplay()); |
| 3897 | const auto* styleText = cb->StyleText(); |
| 3898 | |
| 3899 | // Round the space width when converting to appunits the same way textruns do. |
| 3900 | // We don't use GetFirstFontMetrics here because that may return a font that |
| 3901 | // does not actually have the <space> character, yet is considered the "first |
| 3902 | // available font" per CSS Fonts. Here, we want the font that would be used |
| 3903 | // to render <space>, even if that means looking further down the font-family |
| 3904 | // list. |
| 3905 | RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(cb, 1.0f); |
| 3906 | bool vertical = cb->GetWritingMode().IsCentralBaseline(); |
| 3907 | RefPtr font = fm->GetThebesFontGroup()->GetFirstValidFont(' '); |
| 3908 | auto metrics = font->GetMetrics(vertical ? nsFontMetrics::eVertical |
| 3909 | : nsFontMetrics::eHorizontal); |
| 3910 | nscoord spaceWidth = NSToCoordRound(metrics.spaceWidth * |
| 3911 | cb->PresContext()->AppUnitsPerDevPixel()); |
| 3912 | return spaces * |
| 3913 | (spaceWidth + styleText->mLetterSpacing.Resolve(fm->EmHeight()) + |
| 3914 | styleText->mWordSpacing.Resolve(spaceWidth)); |
| 3915 | } |
| 3916 | |
| 3917 | // Walk backward from aIter to prior cluster starts (within the same textframe's |
| 3918 | // content) and return the first non-mark autospace class. |
| 3919 | // |
| 3920 | // @param aContentOffsetAtFrameStart the original content offset at the start of |
| 3921 | // the textframe. |
| 3922 | static Maybe<TextAutospace::CharClass> LastNonMarkCharClass( |
| 3923 | gfxSkipCharsIterator& aIter, int32_t aContentOffsetAtFrameStart, |
| 3924 | const gfxTextRun* aTextRun, const CharacterDataBuffer& aBuffer) { |
| 3925 | while (aIter.GetOriginalOffset() > aContentOffsetAtFrameStart) { |
| 3926 | aIter.AdvanceOriginal(-1); |
| 3927 | FindClusterStart(aTextRun, aContentOffsetAtFrameStart, &aIter); |
| 3928 | const char32_t ch = aBuffer.ScalarValueAt(aIter.GetOriginalOffset()); |
| 3929 | auto cls = TextAutospace::GetCharClass(ch); |
| 3930 | if (cls != TextAutospace::CharClass::CombiningMark) { |
| 3931 | return Some(cls); |
| 3932 | } |
| 3933 | } |
| 3934 | return Nothing(); |
| 3935 | } |
| 3936 | |
| 3937 | // Walk backward through the frame's content and return the first non-mark |
| 3938 | // autospace class. (Unlike the function above, this is usable when the frame |
| 3939 | // does not currently have a textrun.) |
| 3940 | static Maybe<TextAutospace::CharClass> LastNonMarkCharClass( |
| 3941 | const nsTextFrame* aFrame) { |
| 3942 | using CharClass = TextAutospace::CharClass; |
| 3943 | bool trimSpace = aFrame->HasAnyStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE); |
| 3944 | const auto& buffer = aFrame->CharacterDataBuffer(); |
| 3945 | const uint32_t startOffset = aFrame->GetContentOffset(); |
| 3946 | uint32_t i = aFrame->GetContentEnd(); |
| 3947 | while (i > startOffset) { |
| 3948 | // Get trailing character, decoding surrogate pair if necessary. |
| 3949 | char32_t ch = buffer.CharAt(--i); |
| 3950 | if (IsLowSurrogate(ch) && i > startOffset) { |
| 3951 | // Get potential high surrogate, and decode. |
| 3952 | char32 hi = buffer.CharAt(i - 1); |
| 3953 | if (IsHighSurrogate(hi)) { |
| 3954 | ch = SurrogateToUCS4(hi, ch); |
| 3955 | --i; |
| 3956 | } |
| 3957 | } |
| 3958 | // Skip over trailing whitespace if the frame was trimmed. |
| 3959 | if (trimSpace) { |
| 3960 | if (IsTrimmableSpace(ch)) { |
| 3961 | continue; |
| 3962 | } |
| 3963 | trimSpace = false; |
| 3964 | } |
| 3965 | // If it has a non-CombiningMark class, return it. |
| 3966 | auto cls = TextAutospace::GetCharClass(ch); |
| 3967 | if (cls != CharClass::CombiningMark) { |
| 3968 | return Some(cls); |
| 3969 | } |
| 3970 | } |
| 3971 | // No (non-mark, non-trimmed) characters were found. |
| 3972 | return Nothing(); |
| 3973 | } |
| 3974 | |
| 3975 | // Return the first non-mark autospace class from the end of content in aFrame. |
| 3976 | static Maybe<TextAutospace::CharClass> LastNonMarkCharClassInFrame( |
| 3977 | nsTextFrame* aFrame) { |
| 3978 | using CharClass = TextAutospace::CharClass; |
| 3979 | if (!aFrame->GetContentLength()) { |
| 3980 | return Nothing(); |
| 3981 | } |
| 3982 | Maybe<CharClass> prevClass; |
| 3983 | if (aFrame->GetTextRun(nsTextFrame::eInflated)) { |
| 3984 | // If the frame has a textrun, we can use that to find the last cluster |
| 3985 | // start character and return its class. |
| 3986 | gfxSkipCharsIterator iter = aFrame->EnsureTextRun(nsTextFrame::eInflated); |
| 3987 | iter.SetOriginalOffset(aFrame->GetContentEnd()); |
| 3988 | prevClass = LastNonMarkCharClass(iter, aFrame->GetContentOffset(), |
| 3989 | aFrame->GetTextRun(nsTextFrame::eInflated), |
| 3990 | aFrame->CharacterDataBuffer()); |
| 3991 | } else { |
| 3992 | // We can't call EnsureTextRun if it would build new textruns, because that |
| 3993 | // could destroy the glyph runs that we're currently iterating over. So |
| 3994 | // instead we fall back to inspecting the content directly. This means we |
| 3995 | // ignore CSS whitespace-collapsing, which could mean some of the content |
| 3996 | // should be skipped, but in practice none of the characters that are |
| 3997 | // relevant for autospace classes would be affected. |
| 3998 | prevClass = LastNonMarkCharClass(aFrame); |
| 3999 | } |
| 4000 | if (prevClass) { |
| 4001 | return prevClass; |
| 4002 | } |
| 4003 | if (aFrame->GetPrevInFlow()) { |
| 4004 | // If aFrame has a prev-in-flow, it is after a line-break, so autospace does |
| 4005 | // not apply here; just return Other. |
| 4006 | return Some(CharClass::Other); |
| 4007 | } |
| 4008 | return Nothing(); |
| 4009 | } |
| 4010 | |
| 4011 | // Look for the autospace class of the content preceding the given aFrame |
| 4012 | // in the mapped flows of the current textrun. |
| 4013 | static Maybe<TextAutospace::CharClass> GetPrecedingCharClassFromMappedFlows( |
| 4014 | const nsTextFrame* aFrame, const gfxTextRun* aTextRun) { |
| 4015 | using CharClass = TextAutospace::CharClass; |
| 4016 | |
| 4017 | if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) { |
| 4018 | return Nothing(); |
| 4019 | } |
| 4020 | |
| 4021 | auto data = static_cast<TextRunUserData*>(aTextRun->GetUserData()); |
| 4022 | if (!data) { |
| 4023 | return Nothing(); |
| 4024 | } |
| 4025 | TextRunMappedFlow* mappedFlows = GetMappedFlows(aTextRun); |
| 4026 | |
| 4027 | // Search for aFrame in the mapped flows. |
| 4028 | uint32_t i = 0; |
| 4029 | for (; i < data->mMappedFlowCount; ++i) { |
| 4030 | if (mappedFlows[i].mStartFrame == aFrame) { |
| 4031 | break; |
| 4032 | } |
| 4033 | } |
| 4034 | MOZ_ASSERT(mappedFlows[i].mStartFrame == aFrame,do { static_assert( mozilla::detail::AssertionConditionType< decltype(mappedFlows[i].mStartFrame == aFrame)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mappedFlows[i].mStartFrame == aFrame))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("mappedFlows[i].mStartFrame == aFrame" " (" "aFrame not found in mapped flows!" ")", "./../../../layout/generic/nsTextFrame.cpp", 4035); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "mappedFlows[i].mStartFrame == aFrame" ") (" "aFrame not found in mapped flows!" ")"); do { MOZ_CrashSequence (__null, 4035); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 4035 | "aFrame not found in mapped flows!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(mappedFlows[i].mStartFrame == aFrame)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mappedFlows[i].mStartFrame == aFrame))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("mappedFlows[i].mStartFrame == aFrame" " (" "aFrame not found in mapped flows!" ")", "./../../../layout/generic/nsTextFrame.cpp", 4035); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "mappedFlows[i].mStartFrame == aFrame" ") (" "aFrame not found in mapped flows!" ")"); do { MOZ_CrashSequence (__null, 4035); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 4036 | |
| 4037 | while (i > 0) { |
| 4038 | nsTextFrame* f = mappedFlows[--i].mStartFrame->LastInFlow(); |
| 4039 | if (Maybe<CharClass> prevClass = LastNonMarkCharClassInFrame(f)) { |
| 4040 | return prevClass; |
| 4041 | } |
| 4042 | } |
| 4043 | return Nothing(); |
| 4044 | } |
| 4045 | |
| 4046 | // Look for the autospace class of content preceding the given frame. |
| 4047 | static Maybe<TextAutospace::CharClass> GetPrecedingCharClassFromFrameTree( |
| 4048 | nsIFrame* aFrame) { |
| 4049 | using CharClass = TextAutospace::CharClass; |
| 4050 | while (!aFrame->GetPrevSibling() && aFrame->GetParent()->IsInlineFrame()) { |
| 4051 | // If this is the first child of an inline container, we want to ascend to |
| 4052 | // the parent and look at what precedes it. |
| 4053 | aFrame = aFrame->GetParent(); |
| 4054 | } |
| 4055 | aFrame = aFrame->GetPrevSibling(); |
| 4056 | while (aFrame) { |
| 4057 | if (aFrame->IsPlaceholderFrame()) { |
| 4058 | // Skip over out-of-flow placeholders. |
| 4059 | aFrame = aFrame->GetPrevSibling(); |
| 4060 | continue; |
| 4061 | } |
| 4062 | if (aFrame->IsInlineFrame()) { |
| 4063 | // Descend into inline containers and go backwards through their content. |
| 4064 | aFrame = aFrame->PrincipalChildList().LastChild(); |
| 4065 | continue; |
| 4066 | } |
| 4067 | if (nsTextFrame* f = do_QueryFrame(aFrame)) { |
| 4068 | // Look for the class of the last character in the textframe. |
| 4069 | Maybe<CharClass> prevClass = LastNonMarkCharClassInFrame(f); |
| 4070 | if (prevClass) { |
| 4071 | if ((*prevClass == CharClass::NonIdeographicLetter || |
| 4072 | *prevClass == CharClass::NonIdeographicNumeral) && |
| 4073 | TextAutospace::ShouldSuppressLetterNumeralSpacing(f)) { |
| 4074 | return Some(CharClass::Other); |
| 4075 | } |
| 4076 | return prevClass; |
| 4077 | } |
| 4078 | aFrame = aFrame->GetPrevSibling(); |
| 4079 | continue; |
| 4080 | } |
| 4081 | return Nothing(); |
| 4082 | } |
| 4083 | return Nothing(); |
| 4084 | } |
| 4085 | |
| 4086 | static bool HasCJKGlyphRun(const gfxTextRun* aTextRun) { |
| 4087 | uint32_t numGlyphRuns; |
| 4088 | const gfxTextRun::GlyphRun* run = aTextRun->GetGlyphRuns(&numGlyphRuns); |
| 4089 | while (numGlyphRuns-- > 0) { |
| 4090 | if (run->mIsCJK) { |
| 4091 | return true; |
| 4092 | } |
| 4093 | run++; |
| 4094 | } |
| 4095 | return false; |
| 4096 | } |
| 4097 | |
| 4098 | bool nsTextFrame::PropertyProvider::GetSpacingInternal(Range aRange, |
| 4099 | Spacing* aSpacing, |
| 4100 | bool aIgnoreTabs) const { |
| 4101 | MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds")do { static_assert( mozilla::detail::AssertionConditionType< decltype(IsInBounds(mStart, mLength, aRange))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(IsInBounds(mStart, mLength, aRange )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("IsInBounds(mStart, mLength, aRange)" " (" "Range out of bounds" ")", "./../../../layout/generic/nsTextFrame.cpp", 4101); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "IsInBounds(mStart, mLength, aRange)" ") (" "Range out of bounds" ")"); do { MOZ_CrashSequence(__null, 4101 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 4102 | |
| 4103 | std::memset(aSpacing, 0, aRange.Length() * sizeof(*aSpacing)); |
| 4104 | |
| 4105 | if (mFrame->Style()->IsTextCombined()) { |
| 4106 | return false; |
| 4107 | } |
| 4108 | |
| 4109 | // Track whether any non-standard spacing is actually present in this range. |
| 4110 | // If letter-spacing is non-zero this will always be true, but for the |
| 4111 | /// word-spacing and text-autospace cases it will depend on the actual text. |
| 4112 | bool spacingPresent = mLetterSpacing; |
| 4113 | |
| 4114 | // First, compute the word spacing, letter spacing, and text-autospace |
| 4115 | // spacing. |
| 4116 | if (mWordSpacing || mLetterSpacing || mTextAutospace) { |
| 4117 | bool newlineIsSignificant = mTextStyle->NewlineIsSignificant(mFrame); |
| 4118 | // Which letter-spacing model are we using? |
| 4119 | // 0 - Gecko legacy model, spacing added to trailing side of letter |
| 4120 | // 1 - WebKit/Blink-compatible, spacing added to right-hand side |
| 4121 | // 2 - Symmetrical spacing, half added to each side |
| 4122 | nscoord before, after; |
| 4123 | switch (StaticPrefs::layout_css_letter_spacing_model()) { |
| 4124 | default: // use Gecko legacy behavior if pref value is unknown |
| 4125 | case 0: |
| 4126 | before = 0; |
| 4127 | after = mLetterSpacing; |
| 4128 | break; |
| 4129 | case 1: |
| 4130 | if (mTextRun->IsRightToLeft()) { |
| 4131 | before = mLetterSpacing; |
| 4132 | after = 0; |
| 4133 | } else { |
| 4134 | before = 0; |
| 4135 | after = mLetterSpacing; |
| 4136 | } |
| 4137 | break; |
| 4138 | case 2: |
| 4139 | before = NSToCoordRound(mLetterSpacing * 0.5); |
| 4140 | after = mLetterSpacing - before; |
| 4141 | break; |
| 4142 | } |
| 4143 | |
| 4144 | // Find our offset into the original+transformed string |
| 4145 | gfxSkipCharsIterator start(mStart); |
| 4146 | start.SetSkippedOffset(aRange.start); |
| 4147 | bool atStart = mStartOfLineOffset == start.GetSkippedOffset() && |
| 4148 | !mFrame->IsInSVGTextSubtree(); |
| 4149 | |
| 4150 | using CharClass = TextAutospace::CharClass; |
| 4151 | // The non-mark class of a previous character at a cluster start (if any). |
| 4152 | Maybe<CharClass> prevClass; |
| 4153 | |
| 4154 | // Initialization of prevClass at start-of-frame may be a bit expensive, |
| 4155 | // and we don't always need that initial value, so we encapsulate it in a |
| 4156 | // helper to be called on-demand. |
| 4157 | auto findPrecedingClass = [&]() -> CharClass { |
| 4158 | // Get the class of the character immediately before the current aRange. |
| 4159 | Maybe<CharClass> prevClass; |
| 4160 | if (aRange.start > 0) { |
| 4161 | gfxSkipCharsIterator iter = start; |
| 4162 | prevClass = LastNonMarkCharClass(iter, mFrame->GetContentOffset(), |
| 4163 | mTextRun, mCharacterDataBuffer); |
| 4164 | } |
| 4165 | // If no class was found, we need to look at the preceding content (if |
| 4166 | // any) to see what it ended with. |
| 4167 | if (!prevClass) { |
| 4168 | // If we have a prev-in-flow, we're after a line-break, so autospace |
| 4169 | // does not apply here; just set prevClass to Other. |
| 4170 | if (mFrame->GetPrevInFlow()) { |
| 4171 | prevClass = Some(CharClass::Other); |
| 4172 | } else { |
| 4173 | // If the textrun is mapping multiple content flows, we may be able |
| 4174 | // to find preceding content from there (without having to walk the |
| 4175 | // potentially more complex frame tree). |
| 4176 | prevClass = GetPrecedingCharClassFromMappedFlows(mFrame, mTextRun); |
| 4177 | // If we couldn't get it from an earlier flow covered by the textrun, |
| 4178 | // we'll have to delve into the frame tree to see what preceded this. |
| 4179 | if (!prevClass) { |
| 4180 | prevClass = GetPrecedingCharClassFromFrameTree(mFrame); |
| 4181 | } |
| 4182 | } |
| 4183 | } |
| 4184 | // If no valid class was found, return `Other`, which never participates |
| 4185 | // in autospacing rules. |
| 4186 | return prevClass.valueOr(CharClass::Other); |
| 4187 | }; |
| 4188 | |
| 4189 | // If text-autospace is enabled, we may be able to skip some processing if |
| 4190 | // there are no CJK glyphs in the textrun, so check for their presence. |
| 4191 | bool textIncludesCJK = mTextAutospace && mCharacterDataBuffer.Is2b() && |
| 4192 | HasCJKGlyphRun(mTextRun); |
| 4193 | |
| 4194 | // Iterate over non-skipped characters |
| 4195 | nsSkipCharsRunIterator run( |
| 4196 | start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length()); |
| 4197 | while (run.NextRun()) { |
| 4198 | uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start; |
| 4199 | gfxSkipCharsIterator iter = run.GetPos(); |
| 4200 | for (int32_t i = 0; i < run.GetRunLength(); ++i) { |
| 4201 | auto currScalar = [&]() -> char32_t { |
| 4202 | iter.SetSkippedOffset(run.GetSkippedOffset() + i); |
| 4203 | return mCharacterDataBuffer.ScalarValueAt(iter.GetOriginalOffset()); |
| 4204 | }; |
| 4205 | if (!atStart && before != 0 && |
| 4206 | CanAddSpacingBefore(mTextRun, run.GetSkippedOffset() + i, |
| 4207 | newlineIsSignificant) && |
| 4208 | !intl::UnicodeProperties::IsCursiveScript(currScalar())) { |
| 4209 | aSpacing[runOffsetInSubstring + i].mBefore += before; |
| 4210 | } |
| 4211 | if (after != 0 && |
| 4212 | CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i, |
| 4213 | newlineIsSignificant)) { |
| 4214 | // End of a cluster, not in a ligature: put letter-spacing after it, |
| 4215 | // unless the base char of the cluster belonged to a cursive script. |
| 4216 | iter.SetSkippedOffset(run.GetSkippedOffset() + i); |
| 4217 | FindClusterStart(mTextRun, run.GetOriginalOffset(), &iter); |
| 4218 | char32_t baseChar = |
| 4219 | mCharacterDataBuffer.ScalarValueAt(iter.GetOriginalOffset()); |
| 4220 | if (!intl::UnicodeProperties::IsCursiveScript(baseChar)) { |
| 4221 | aSpacing[runOffsetInSubstring + i].mAfter += after; |
| 4222 | } |
| 4223 | } |
| 4224 | if (mWordSpacing && IsCSSWordSpacingSpace(mCharacterDataBuffer, |
| 4225 | i + run.GetOriginalOffset(), |
| 4226 | mFrame, mTextStyle)) { |
| 4227 | // It kinda sucks, but space characters can be part of clusters, |
| 4228 | // and even still be whitespace (I think!) |
| 4229 | iter.SetSkippedOffset(run.GetSkippedOffset() + i); |
| 4230 | FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(), |
| 4231 | &iter); |
| 4232 | uint32_t runOffset = iter.GetSkippedOffset() - aRange.start; |
| 4233 | aSpacing[runOffset].mAfter += mWordSpacing; |
| 4234 | spacingPresent = true; |
| 4235 | } |
| 4236 | // Add text-autospace spacing only at cluster starts. Always check the |
| 4237 | // character classes if the textrun includes CJK; otherwise, check only |
| 4238 | // at the frame start (as preceding content might be an ideograph |
| 4239 | // requiring autospacing). |
| 4240 | if (mTextAutospace && |
| 4241 | (textIncludesCJK || |
| 4242 | run.GetOriginalOffset() + i == mFrame->GetContentOffset()) && |
| 4243 | mTextRun->IsClusterStart(run.GetSkippedOffset() + i)) { |
| 4244 | const auto currClass = TextAutospace::GetCharClass(currScalar()); |
| 4245 | |
| 4246 | // It is rare for the current class to be a combining mark, as |
| 4247 | // combining marks are not cluster starts. We still check in case a |
| 4248 | // stray mark appears at the start of a frame. |
| 4249 | if (currClass != CharClass::CombiningMark) { |
| 4250 | // We don't need to do anything if at start of line, or if the |
| 4251 | // current class is `Other`, which never participates in spacing. |
| 4252 | if (!atStart && currClass != CharClass::Other && |
| 4253 | mTextAutospace->ShouldApplySpacing( |
| 4254 | prevClass.valueOrFrom(findPrecedingClass), currClass)) { |
| 4255 | aSpacing[runOffsetInSubstring + i].mBefore += |
| 4256 | mTextAutospace->InterScriptSpacing(); |
| 4257 | spacingPresent = true; |
| 4258 | } |
| 4259 | // Even if we didn't actually need to check spacing rules here, we |
| 4260 | // record the new prevClass. (Incidentally, this ensure that we'll |
| 4261 | // only call the findPrecedingClass() helper once.) |
| 4262 | prevClass = Some(currClass); |
| 4263 | } |
| 4264 | } |
| 4265 | atStart = false; |
| 4266 | } |
| 4267 | } |
| 4268 | } |
| 4269 | |
| 4270 | // Now add tab spacing, if there is any |
| 4271 | if (!aIgnoreTabs) { |
| 4272 | gfxFloat tabWidth = ComputeTabWidthAppUnits(mFrame); |
| 4273 | if (tabWidth > 0) { |
| 4274 | CalcTabWidths(aRange, tabWidth); |
| 4275 | if (mTabWidths) { |
| 4276 | mTabWidths->ApplySpacing(aSpacing, |
| 4277 | aRange.start - mStart.GetSkippedOffset(), |
| 4278 | aRange.Length()); |
| 4279 | spacingPresent = true; |
| 4280 | } |
| 4281 | } |
| 4282 | } |
| 4283 | |
| 4284 | // Now add in justification spacing |
| 4285 | if (mJustificationSpacings.Length() > 0) { |
| 4286 | // If there is any spaces trimmed at the end, aStart + aLength may |
| 4287 | // be larger than the flags array. When that happens, we can simply |
| 4288 | // ignore those spaces. |
| 4289 | auto arrayEnd = mJustificationArrayStart + |
| 4290 | static_cast<uint32_t>(mJustificationSpacings.Length()); |
| 4291 | auto end = std::min(aRange.end, arrayEnd); |
| 4292 | MOZ_ASSERT(aRange.start >= mJustificationArrayStart)do { static_assert( mozilla::detail::AssertionConditionType< decltype(aRange.start >= mJustificationArrayStart)>::isValid , "invalid assertion condition"); if ((__builtin_expect(!!(!( !!(aRange.start >= mJustificationArrayStart))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aRange.start >= mJustificationArrayStart" , "./../../../layout/generic/nsTextFrame.cpp", 4292); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aRange.start >= mJustificationArrayStart" ")"); do { MOZ_CrashSequence(__null, 4292); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 4293 | for (auto i = aRange.start; i < end; i++) { |
| 4294 | const auto& spacing = |
| 4295 | mJustificationSpacings[i - mJustificationArrayStart]; |
| 4296 | uint32_t offset = i - aRange.start; |
| 4297 | aSpacing[offset].mBefore += spacing.mBefore; |
| 4298 | aSpacing[offset].mAfter += spacing.mAfter; |
| 4299 | } |
| 4300 | spacingPresent = true; |
| 4301 | } |
| 4302 | |
| 4303 | return spacingPresent; |
| 4304 | } |
| 4305 | |
| 4306 | // aX and the result are in whole appunits. |
| 4307 | static gfxFloat AdvanceToNextTab(gfxFloat aX, gfxFloat aTabWidth, |
| 4308 | gfxFloat aMinAdvance) { |
| 4309 | // Advance aX to the next multiple of aTabWidth. We must advance |
| 4310 | // by at least aMinAdvance. |
| 4311 | gfxFloat nextPos = aX + aMinAdvance; |
| 4312 | return aTabWidth > 0.0 ? ceil(nextPos / aTabWidth) * aTabWidth : nextPos; |
| 4313 | } |
| 4314 | |
| 4315 | void nsTextFrame::PropertyProvider::CalcTabWidths(Range aRange, |
| 4316 | gfxFloat aTabWidth) const { |
| 4317 | MOZ_ASSERT(aTabWidth > 0)do { static_assert( mozilla::detail::AssertionConditionType< decltype(aTabWidth > 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aTabWidth > 0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aTabWidth > 0" , "./../../../layout/generic/nsTextFrame.cpp", 4317); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aTabWidth > 0" ")"); do { MOZ_CrashSequence (__null, 4317); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 4318 | |
| 4319 | if (!mTabWidths) { |
| 4320 | if (mReflowing && !mLineContainer) { |
| 4321 | // Intrinsic width computation does its own tab processing. We |
| 4322 | // just don't do anything here. |
| 4323 | return; |
| 4324 | } |
| 4325 | if (!mReflowing) { |
| 4326 | mTabWidths = mFrame->GetProperty(TabWidthProperty()); |
| 4327 | #ifdef DEBUG1 |
| 4328 | // If we're not reflowing, we should have already computed the |
| 4329 | // tab widths; check that they're available as far as the last |
| 4330 | // tab character present (if any) |
| 4331 | for (uint32_t i = aRange.end; i > aRange.start; --i) { |
| 4332 | if (mTextRun->CharIsTab(i - 1)) { |
| 4333 | uint32_t startOffset = mStart.GetSkippedOffset(); |
| 4334 | NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i,do { if (!(mTabWidths && mTabWidths->mLimit + startOffset >= i)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Precomputed tab widths are missing!" , "mTabWidths && mTabWidths->mLimit + startOffset >= i" , "./../../../layout/generic/nsTextFrame.cpp", 4335); MOZ_PretendNoReturn (); } } while (0) |
| 4335 | "Precomputed tab widths are missing!")do { if (!(mTabWidths && mTabWidths->mLimit + startOffset >= i)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Precomputed tab widths are missing!" , "mTabWidths && mTabWidths->mLimit + startOffset >= i" , "./../../../layout/generic/nsTextFrame.cpp", 4335); MOZ_PretendNoReturn (); } } while (0); |
| 4336 | break; |
| 4337 | } |
| 4338 | } |
| 4339 | #endif |
| 4340 | return; |
| 4341 | } |
| 4342 | } |
| 4343 | |
| 4344 | uint32_t startOffset = mStart.GetSkippedOffset(); |
| 4345 | MOZ_ASSERT(aRange.start >= startOffset, "wrong start offset")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aRange.start >= startOffset)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aRange.start >= startOffset ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "aRange.start >= startOffset" " (" "wrong start offset" ")" , "./../../../layout/generic/nsTextFrame.cpp", 4345); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aRange.start >= startOffset" ") (" "wrong start offset" ")"); do { MOZ_CrashSequence(__null, 4345); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 4346 | MOZ_ASSERT(aRange.end <= startOffset + mLength, "beyond the end")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aRange.end <= startOffset + mLength)>::isValid , "invalid assertion condition"); if ((__builtin_expect(!!(!( !!(aRange.end <= startOffset + mLength))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aRange.end <= startOffset + mLength" " (" "beyond the end" ")", "./../../../layout/generic/nsTextFrame.cpp" , 4346); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aRange.end <= startOffset + mLength" ") (" "beyond the end" ")"); do { MOZ_CrashSequence(__null, 4346 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 4347 | uint32_t tabsEnd = |
| 4348 | (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset; |
| 4349 | if (tabsEnd < aRange.end) { |
| 4350 | NS_ASSERTION(mReflowing,do { if (!(mReflowing)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "We need precomputed tab widths, but don't have enough." , "mReflowing", "./../../../layout/generic/nsTextFrame.cpp", 4351 ); MOZ_PretendNoReturn(); } } while (0) |
| 4351 | "We need precomputed tab widths, but don't have enough.")do { if (!(mReflowing)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "We need precomputed tab widths, but don't have enough." , "mReflowing", "./../../../layout/generic/nsTextFrame.cpp", 4351 ); MOZ_PretendNoReturn(); } } while (0); |
| 4352 | |
| 4353 | for (uint32_t i = tabsEnd; i < aRange.end; ++i) { |
| 4354 | Spacing spacing; |
| 4355 | GetSpacingInternal(Range(i, i + 1), &spacing, true); |
| 4356 | mOffsetFromBlockOriginForTabs += spacing.mBefore; |
| 4357 | |
| 4358 | if (!mTextRun->CharIsTab(i)) { |
| 4359 | if (mTextRun->IsClusterStart(i)) { |
| 4360 | uint32_t clusterEnd = i + 1; |
| 4361 | while (clusterEnd < mTextRun->GetLength() && |
| 4362 | !mTextRun->IsClusterStart(clusterEnd)) { |
| 4363 | ++clusterEnd; |
| 4364 | } |
| 4365 | mOffsetFromBlockOriginForTabs += |
| 4366 | mTextRun->GetAdvanceWidth(Range(i, clusterEnd), nullptr); |
| 4367 | } |
| 4368 | } else { |
| 4369 | if (!mTabWidths) { |
| 4370 | mTabWidths = new TabWidthStore(mFrame->GetContentOffset()); |
| 4371 | mFrame->SetProperty(TabWidthProperty(), mTabWidths); |
| 4372 | } |
| 4373 | double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs, |
| 4374 | aTabWidth, MinTabAdvance()); |
| 4375 | mTabWidths->mWidths.AppendElement( |
| 4376 | TabWidth(i - startOffset, |
| 4377 | NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs))); |
| 4378 | mOffsetFromBlockOriginForTabs = nextTab; |
| 4379 | } |
| 4380 | |
| 4381 | mOffsetFromBlockOriginForTabs += spacing.mAfter; |
| 4382 | } |
| 4383 | |
| 4384 | if (mTabWidths) { |
| 4385 | mTabWidths->mLimit = aRange.end - startOffset; |
| 4386 | } |
| 4387 | } |
| 4388 | |
| 4389 | if (!mTabWidths) { |
| 4390 | // Delete any stale property that may be left on the frame |
| 4391 | mFrame->RemoveProperty(TabWidthProperty()); |
| 4392 | mTabWidthsAnalyzedLimit = |
| 4393 | std::max(mTabWidthsAnalyzedLimit, aRange.end - startOffset); |
| 4394 | } |
| 4395 | } |
| 4396 | |
| 4397 | gfxFloat nsTextFrame::PropertyProvider::GetHyphenWidth() const { |
| 4398 | if (mHyphenWidth < 0) { |
| 4399 | const auto& hyphenateChar = mTextStyle->mHyphenateCharacter; |
| 4400 | if (hyphenateChar.IsAuto()) { |
| 4401 | mHyphenWidth = GetFontGroup()->GetHyphenWidth(this); |
| 4402 | } else { |
| 4403 | RefPtr<gfxTextRun> hyphRun = GetHyphenTextRun(mFrame, nullptr); |
| 4404 | mHyphenWidth = hyphRun ? hyphRun->GetAdvanceWidth() : 0; |
| 4405 | } |
| 4406 | } |
| 4407 | return mHyphenWidth + mLetterSpacing; |
| 4408 | } |
| 4409 | |
| 4410 | static inline bool IS_HYPHEN(char16_t u) { |
| 4411 | return u == char16_t('-') || // HYPHEN-MINUS |
| 4412 | u == 0x058A || // ARMENIAN HYPHEN |
| 4413 | u == 0x2010 || // HYPHEN |
| 4414 | u == 0x2012 || // FIGURE DASH |
| 4415 | u == 0x2013; // EN DASH |
| 4416 | } |
| 4417 | |
| 4418 | void nsTextFrame::PropertyProvider::GetHyphenationBreaks( |
| 4419 | Range aRange, HyphenType* aBreakBefore) const { |
| 4420 | MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds")do { static_assert( mozilla::detail::AssertionConditionType< decltype(IsInBounds(mStart, mLength, aRange))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(IsInBounds(mStart, mLength, aRange )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("IsInBounds(mStart, mLength, aRange)" " (" "Range out of bounds" ")", "./../../../layout/generic/nsTextFrame.cpp", 4420); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "IsInBounds(mStart, mLength, aRange)" ") (" "Range out of bounds" ")"); do { MOZ_CrashSequence(__null, 4420 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 4421 | MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length")do { static_assert( mozilla::detail::AssertionConditionType< decltype(mLength != (2147483647))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mLength != (2147483647)))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("mLength != (2147483647)" " (" "Can't call this with undefined length" ")", "./../../../layout/generic/nsTextFrame.cpp" , 4421); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mLength != (2147483647)" ") (" "Can't call this with undefined length" ")"); do { MOZ_CrashSequence (__null, 4421); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 4422 | |
| 4423 | if (!mTextStyle->WhiteSpaceCanWrap(mFrame) || |
| 4424 | mTextStyle->mHyphens == StyleHyphens::None) { |
| 4425 | memset(aBreakBefore, static_cast<uint8_t>(HyphenType::None), |
| 4426 | aRange.Length() * sizeof(HyphenType)); |
| 4427 | return; |
| 4428 | } |
| 4429 | |
| 4430 | // Iterate through the original-string character runs |
| 4431 | nsSkipCharsRunIterator run( |
| 4432 | mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length()); |
| 4433 | run.SetSkippedOffset(aRange.start); |
| 4434 | // We need to visit skipped characters so that we can detect SHY |
| 4435 | run.SetVisitSkipped(); |
| 4436 | |
| 4437 | int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1; |
| 4438 | bool allowHyphenBreakBeforeNextChar = |
| 4439 | prevTrailingCharOffset >= mStart.GetOriginalOffset() && |
| 4440 | prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength && |
| 4441 | mCharacterDataBuffer.CharAt( |
| 4442 | AssertedCast<uint32_t>(prevTrailingCharOffset)) == CH_SHY173; |
| 4443 | |
| 4444 | while (run.NextRun()) { |
| 4445 | NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs")do { if (!(run.GetRunLength() > 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Shouldn't return zero-length runs", "run.GetRunLength() > 0" , "./../../../layout/generic/nsTextFrame.cpp", 4445); MOZ_PretendNoReturn (); } } while (0); |
| 4446 | if (run.IsSkipped()) { |
| 4447 | // Check if there's a soft hyphen which would let us hyphenate before |
| 4448 | // the next non-skipped character. Don't look at soft hyphens followed |
| 4449 | // by other skipped characters, we won't use them. |
| 4450 | allowHyphenBreakBeforeNextChar = |
| 4451 | mCharacterDataBuffer.CharAt(AssertedCast<uint32_t>( |
| 4452 | run.GetOriginalOffset() + run.GetRunLength() - 1)) == CH_SHY173; |
| 4453 | } else { |
| 4454 | int32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start; |
| 4455 | memset(aBreakBefore + runOffsetInSubstring, |
| 4456 | static_cast<uint8_t>(HyphenType::None), |
| 4457 | run.GetRunLength() * sizeof(HyphenType)); |
| 4458 | // Don't allow hyphen breaks at the start of the line |
| 4459 | aBreakBefore[runOffsetInSubstring] = |
| 4460 | allowHyphenBreakBeforeNextChar && |
| 4461 | (!mFrame->HasAnyStateBits(TEXT_START_OF_LINE) || |
| 4462 | run.GetSkippedOffset() > mStart.GetSkippedOffset()) |
| 4463 | ? HyphenType::Soft |
| 4464 | : HyphenType::None; |
| 4465 | allowHyphenBreakBeforeNextChar = false; |
| 4466 | } |
| 4467 | } |
| 4468 | |
| 4469 | if (mTextStyle->mHyphens == StyleHyphens::Auto) { |
| 4470 | gfxSkipCharsIterator skipIter(mStart); |
| 4471 | for (uint32_t i = 0; i < aRange.Length(); ++i) { |
| 4472 | if (IS_HYPHEN(mCharacterDataBuffer.CharAt(AssertedCast<uint32_t>( |
| 4473 | skipIter.ConvertSkippedToOriginal(aRange.start + i))))) { |
| 4474 | if (i < aRange.Length() - 1) { |
| 4475 | aBreakBefore[i + 1] = HyphenType::Explicit; |
| 4476 | } |
| 4477 | continue; |
| 4478 | } |
| 4479 | |
| 4480 | if (mTextRun->CanHyphenateBefore(aRange.start + i) && |
| 4481 | aBreakBefore[i] == HyphenType::None) { |
| 4482 | aBreakBefore[i] = HyphenType::AutoWithoutManualInSameWord; |
| 4483 | } |
| 4484 | } |
| 4485 | } |
| 4486 | } |
| 4487 | |
| 4488 | bool nsTextFrame::PropertyProvider::GetSourceText(Range aRange, |
| 4489 | nsAString& aText) const { |
| 4490 | MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds")do { static_assert( mozilla::detail::AssertionConditionType< decltype(IsInBounds(mStart, mLength, aRange))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(IsInBounds(mStart, mLength, aRange )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("IsInBounds(mStart, mLength, aRange)" " (" "Range out of bounds" ")", "./../../../layout/generic/nsTextFrame.cpp", 4490); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "IsInBounds(mStart, mLength, aRange)" ") (" "Range out of bounds" ")"); do { MOZ_CrashSequence(__null, 4490 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 4491 | MOZ_ASSERT(aText.IsEmpty())do { static_assert( mozilla::detail::AssertionConditionType< decltype(aText.IsEmpty())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aText.IsEmpty()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aText.IsEmpty()" , "./../../../layout/generic/nsTextFrame.cpp", 4491); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aText.IsEmpty()" ")"); do { MOZ_CrashSequence (__null, 4491); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 4492 | |
| 4493 | const bool isTransformed = |
| 4494 | !!(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed); |
| 4495 | const nsTransformedTextRun* transformedTextRun = |
| 4496 | isTransformed ? static_cast<const nsTransformedTextRun*>(mTextRun.get()) |
| 4497 | : nullptr; |
| 4498 | gfxSkipCharsIterator skipIter(mStart); |
| 4499 | |
| 4500 | // text-transform must not affect a plain text copy, so we always map back to |
| 4501 | // the DOM text, even for a transformed textrun. The DOM text is never masked, |
| 4502 | // so we must explicitly return false for masked password characters. |
| 4503 | aText.SetCapacity(aRange.Length()); |
| 4504 | for (uint32_t i = aRange.start; i < aRange.end; ++i) { |
| 4505 | if (transformedTextRun && transformedTextRun->mStyles[i]->mMaskPassword) { |
| 4506 | aText.Truncate(); |
| 4507 | return false; |
| 4508 | } |
| 4509 | aText.Append(mCharacterDataBuffer.CharAt( |
| 4510 | AssertedCast<uint32_t>(skipIter.ConvertSkippedToOriginal(i)))); |
| 4511 | } |
| 4512 | return true; |
| 4513 | } |
| 4514 | |
| 4515 | void nsTextFrame::PropertyProvider::InitializeForDisplay(bool aTrimAfter) { |
| 4516 | nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets( |
| 4517 | mCharacterDataBuffer, |
| 4518 | (aTrimAfter ? nsTextFrame::TrimmedOffsetFlags::Default |
| 4519 | : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter)); |
| 4520 | mStart.SetOriginalOffset(trimmed.mStart); |
| 4521 | mLength = trimmed.mLength; |
| 4522 | if (mFrame->HasAnyStateBits(TEXT_START_OF_LINE)) { |
| 4523 | mStartOfLineOffset = mStart.GetSkippedOffset(); |
| 4524 | } |
| 4525 | SetupJustificationSpacing(true); |
| 4526 | } |
| 4527 | |
| 4528 | void nsTextFrame::PropertyProvider::InitializeForMeasure() { |
| 4529 | nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets( |
| 4530 | mCharacterDataBuffer, nsTextFrame::TrimmedOffsetFlags::NotPostReflow); |
| 4531 | mStart.SetOriginalOffset(trimmed.mStart); |
| 4532 | mLength = trimmed.mLength; |
| 4533 | if (mFrame->HasAnyStateBits(TEXT_START_OF_LINE)) { |
| 4534 | mStartOfLineOffset = mStart.GetSkippedOffset(); |
| 4535 | } |
| 4536 | SetupJustificationSpacing(false); |
| 4537 | } |
| 4538 | |
| 4539 | void nsTextFrame::PropertyProvider::SetupJustificationSpacing( |
| 4540 | bool aPostReflow) { |
| 4541 | MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length")do { static_assert( mozilla::detail::AssertionConditionType< decltype(mLength != (2147483647))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mLength != (2147483647)))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("mLength != (2147483647)" " (" "Can't call this with undefined length" ")", "./../../../layout/generic/nsTextFrame.cpp" , 4541); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mLength != (2147483647)" ") (" "Can't call this with undefined length" ")"); do { MOZ_CrashSequence (__null, 4541); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 4542 | |
| 4543 | if (!mFrame->HasAnyStateBits(TEXT_JUSTIFICATION_ENABLED)) { |
| 4544 | return; |
| 4545 | } |
| 4546 | |
| 4547 | gfxSkipCharsIterator start(mStart), end(mStart); |
| 4548 | // We can't just use our mLength here; when InitializeForDisplay is |
| 4549 | // called with false for aTrimAfter, we still shouldn't be assigning |
| 4550 | // justification space to any trailing whitespace. |
| 4551 | nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets( |
| 4552 | mCharacterDataBuffer, |
| 4553 | (aPostReflow ? nsTextFrame::TrimmedOffsetFlags::Default |
| 4554 | : nsTextFrame::TrimmedOffsetFlags::NotPostReflow)); |
| 4555 | end.AdvanceOriginal(trimmed.mLength); |
| 4556 | gfxSkipCharsIterator realEnd(end); |
| 4557 | |
| 4558 | Range range(uint32_t(start.GetOriginalOffset()), |
| 4559 | uint32_t(end.GetOriginalOffset())); |
| 4560 | nsTArray<JustificationAssignment> assignments; |
| 4561 | JustificationInfo info = ComputeJustification(range, &assignments); |
| 4562 | |
| 4563 | auto assign = mFrame->GetJustificationAssignment(); |
| 4564 | auto totalGaps = JustificationUtils::CountGaps(info, assign); |
| 4565 | if (!totalGaps || assignments.IsEmpty()) { |
| 4566 | // Nothing to do, nothing is justifiable and we shouldn't have any |
| 4567 | // justification space assigned |
| 4568 | return; |
| 4569 | } |
| 4570 | |
| 4571 | // Remember that textrun measurements are in the run's orientation, |
| 4572 | // so its advance "width" is actually a height in vertical writing modes, |
| 4573 | // corresponding to the inline-direction of the frame. |
| 4574 | gfxFloat naturalWidth = mTextRun->GetAdvanceWidth( |
| 4575 | Range(mStart.GetSkippedOffset(), realEnd.GetSkippedOffset()), this); |
| 4576 | if (mFrame->HasAnyStateBits(TEXT_HYPHEN_BREAK)) { |
| 4577 | naturalWidth += GetHyphenWidth(); |
| 4578 | } |
| 4579 | nscoord totalSpacing = mFrame->ISize() - naturalWidth; |
| 4580 | if (totalSpacing <= 0) { |
| 4581 | // No space available |
| 4582 | return; |
| 4583 | } |
| 4584 | |
| 4585 | assignments[0].mGapsAtStart = assign.mGapsAtStart; |
| 4586 | assignments.LastElement().mGapsAtEnd = assign.mGapsAtEnd; |
| 4587 | |
| 4588 | MOZ_ASSERT(mJustificationSpacings.IsEmpty())do { static_assert( mozilla::detail::AssertionConditionType< decltype(mJustificationSpacings.IsEmpty())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mJustificationSpacings.IsEmpty ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("mJustificationSpacings.IsEmpty()", "./../../../layout/generic/nsTextFrame.cpp" , 4588); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mJustificationSpacings.IsEmpty()" ")"); do { MOZ_CrashSequence(__null, 4588); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 4589 | JustificationApplicationState state(totalGaps, totalSpacing); |
| 4590 | mJustificationSpacings.SetCapacity(assignments.Length()); |
| 4591 | for (const JustificationAssignment& assign : assignments) { |
| 4592 | Spacing* spacing = mJustificationSpacings.AppendElement(); |
| 4593 | spacing->mBefore = state.Consume(assign.mGapsAtStart); |
| 4594 | spacing->mAfter = state.Consume(assign.mGapsAtEnd); |
| 4595 | } |
| 4596 | } |
| 4597 | |
| 4598 | void nsTextFrame::PropertyProvider::InitFontGroupAndFontMetrics() const { |
| 4599 | if (!mFontMetrics) { |
| 4600 | if (mWhichTextRun == nsTextFrame::eInflated) { |
| 4601 | mFontMetrics = mFrame->InflatedFontMetrics(); |
| 4602 | } else { |
| 4603 | mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(mFrame, 1.0f); |
| 4604 | } |
| 4605 | } |
| 4606 | mFontGroup = mFontMetrics->GetThebesFontGroup(); |
| 4607 | } |
| 4608 | |
| 4609 | void nsTextFrame::PropertyProvider::InitTextAutospace() { |
| 4610 | const auto styleTextAutospace = mTextStyle->EffectiveTextAutospace(); |
| 4611 | if (TextAutospace::Enabled(styleTextAutospace, mFrame)) { |
| 4612 | mTextAutospace.emplace(styleTextAutospace, |
| 4613 | GetFontMetrics()->InterScriptSpacingWidth()); |
| 4614 | } |
| 4615 | } |
| 4616 | |
| 4617 | #ifdef ACCESSIBILITY1 |
| 4618 | a11y::AccType nsTextFrame::AccessibleType() { |
| 4619 | if (IsEmpty()) { |
| 4620 | RenderedText text = |
| 4621 | GetRenderedText(0, UINT32_MAX(4294967295U), TextOffsetType::OffsetsInContentText, |
| 4622 | TrailingWhitespace::DontTrim); |
| 4623 | if (text.mString.IsEmpty()) { |
| 4624 | return a11y::eNoType; |
| 4625 | } |
| 4626 | } |
| 4627 | |
| 4628 | return a11y::eTextLeafType; |
| 4629 | } |
| 4630 | #endif |
| 4631 | |
| 4632 | //----------------------------------------------------------------------------- |
| 4633 | void nsTextFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, |
| 4634 | nsIFrame* aPrevInFlow) { |
| 4635 | NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!")do { if (!(!aPrevInFlow)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Can't be a continuation!", "!aPrevInFlow", "./../../../layout/generic/nsTextFrame.cpp" , 4635); MOZ_PretendNoReturn(); } } while (0); |
| 4636 | MOZ_ASSERT(aContent->IsText(), "Bogus content!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aContent->IsText())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aContent->IsText()))), 0) )) { do { } while (false); MOZ_ReportAssertionFailure("aContent->IsText()" " (" "Bogus content!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 4636); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aContent->IsText()" ") (" "Bogus content!" ")"); do { MOZ_CrashSequence(__null, 4636 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 4637 | |
| 4638 | // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they |
| 4639 | // might be invalid if the content was modified while there was no frame |
| 4640 | if (aContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) { |
| 4641 | aContent->RemoveProperty(nsGkAtoms::newline); |
| 4642 | aContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY); |
| 4643 | } |
| 4644 | if (aContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) { |
| 4645 | aContent->RemoveProperty(nsGkAtoms::flowlength); |
| 4646 | aContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY); |
| 4647 | } |
| 4648 | |
| 4649 | // Since our content has a frame now, this flag is no longer needed. |
| 4650 | aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE); |
| 4651 | |
| 4652 | // We're not a continuing frame. |
| 4653 | // mContentOffset = 0; not necessary since we get zeroed out at init |
| 4654 | nsIFrame::Init(aContent, aParent, aPrevInFlow); |
| 4655 | } |
| 4656 | |
| 4657 | void nsTextFrame::ClearFrameOffsetCache() { |
| 4658 | // See if we need to remove ourselves from the offset cache |
| 4659 | if (HasAnyStateBits(TEXT_IN_OFFSET_CACHE)) { |
| 4660 | nsIFrame* primaryFrame = mContent->GetPrimaryFrame(); |
| 4661 | if (primaryFrame) { |
| 4662 | // The primary frame might be null here. For example, |
| 4663 | // nsLineBox::DeleteLineList just destroys the frames in order, which |
| 4664 | // means that the primary frame is already dead if we're a continuing text |
| 4665 | // frame, in which case, all of its properties are gone, and we don't need |
| 4666 | // to worry about deleting this property here. |
| 4667 | primaryFrame->RemoveProperty(OffsetToFrameProperty()); |
| 4668 | } |
| 4669 | RemoveStateBits(TEXT_IN_OFFSET_CACHE); |
| 4670 | } |
| 4671 | } |
| 4672 | |
| 4673 | void nsTextFrame::Destroy(DestroyContext& aContext) { |
| 4674 | ClearFrameOffsetCache(); |
| 4675 | |
| 4676 | // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or |
| 4677 | // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame |
| 4678 | // type might be changing. Not clear whether it's worth it. |
| 4679 | ClearTextRuns(); |
| 4680 | if (mNextContinuation) { |
| 4681 | mNextContinuation->SetPrevInFlow(nullptr); |
| 4682 | } |
| 4683 | // Let the base class destroy the frame |
| 4684 | nsIFrame::Destroy(aContext); |
| 4685 | } |
| 4686 | |
| 4687 | nsTArray<nsTextFrame*>* nsTextFrame::GetContinuations() { |
| 4688 | MOZ_ASSERT(NS_IsMainThread())do { static_assert( mozilla::detail::AssertionConditionType< decltype(NS_IsMainThread())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(NS_IsMainThread()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("NS_IsMainThread()" , "./../../../layout/generic/nsTextFrame.cpp", 4688); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "NS_IsMainThread()" ")"); do { MOZ_CrashSequence (__null, 4688); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 4689 | // Only for use on the primary frame, which has no prev-continuation. |
| 4690 | MOZ_ASSERT(!GetPrevContinuation())do { static_assert( mozilla::detail::AssertionConditionType< decltype(!GetPrevContinuation())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!GetPrevContinuation()))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!GetPrevContinuation()" , "./../../../layout/generic/nsTextFrame.cpp", 4690); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "!GetPrevContinuation()" ")"); do { MOZ_CrashSequence (__null, 4690); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 4691 | if (!mNextContinuation) { |
| 4692 | return nullptr; |
| 4693 | } |
| 4694 | if (mPropertyFlags & PropertyFlags::Continuations) { |
| 4695 | return GetProperty(ContinuationsProperty()); |
| 4696 | } |
| 4697 | size_t count = 0; |
| 4698 | for (nsIFrame* f = this; f; f = f->GetNextContinuation()) { |
| 4699 | ++count; |
| 4700 | } |
| 4701 | auto* continuations = new nsTArray<nsTextFrame*>; |
| 4702 | if (continuations->SetCapacity(count, fallible)) { |
| 4703 | for (nsTextFrame* f = this; f; |
| 4704 | f = static_cast<nsTextFrame*>(f->GetNextContinuation())) { |
| 4705 | continuations->AppendElement(f); |
| 4706 | } |
| 4707 | } else { |
| 4708 | delete continuations; |
| 4709 | continuations = nullptr; |
| 4710 | } |
| 4711 | AddProperty(ContinuationsProperty(), continuations); |
| 4712 | mPropertyFlags |= PropertyFlags::Continuations; |
| 4713 | return continuations; |
| 4714 | } |
| 4715 | |
| 4716 | class nsContinuingTextFrame final : public nsTextFrame { |
| 4717 | public: |
| 4718 | NS_DECL_FRAMEARENA_HELPERS(nsContinuingTextFrame)static const nsQueryFrame::FrameIID kFrameIID = nsQueryFrame:: nsContinuingTextFrame_id; typedef nsContinuingTextFrame Has_NS_DECL_QUERYFRAME_TARGET ; static constexpr nsIFrame::ClassID kClassID = nsIFrame::ClassID ::nsContinuingTextFrame_id; void* operator new(size_t, mozilla ::PresShell*) ; nsQueryFrame::FrameIID GetFrameId() const override { return nsQueryFrame::nsContinuingTextFrame_id; } |
| 4719 | |
| 4720 | friend nsIFrame* NS_NewContinuingTextFrame(mozilla::PresShell* aPresShell, |
| 4721 | ComputedStyle* aStyle); |
| 4722 | |
| 4723 | void Init(nsIContent* aContent, nsContainerFrame* aParent, |
| 4724 | nsIFrame* aPrevInFlow) final; |
| 4725 | |
| 4726 | void Destroy(DestroyContext&) override; |
| 4727 | |
| 4728 | nsTextFrame* GetPrevContinuation() const final { return mPrevContinuation; } |
| 4729 | |
| 4730 | void SetPrevContinuation(nsIFrame* aPrevContinuation) final { |
| 4731 | NS_ASSERTION(!aPrevContinuation || Type() == aPrevContinuation->Type(),do { if (!(!aPrevContinuation || Type() == aPrevContinuation-> Type())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "setting a prev continuation with incorrect type!" , "!aPrevContinuation || Type() == aPrevContinuation->Type()" , "./../../../layout/generic/nsTextFrame.cpp", 4732); MOZ_PretendNoReturn (); } } while (0) |
| 4732 | "setting a prev continuation with incorrect type!")do { if (!(!aPrevContinuation || Type() == aPrevContinuation-> Type())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "setting a prev continuation with incorrect type!" , "!aPrevContinuation || Type() == aPrevContinuation->Type()" , "./../../../layout/generic/nsTextFrame.cpp", 4732); MOZ_PretendNoReturn (); } } while (0); |
| 4733 | NS_ASSERTION(do { if (!(!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation , this))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "creating a loop in continuation chain!" , "!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this)" , "./../../../layout/generic/nsTextFrame.cpp", 4735); MOZ_PretendNoReturn (); } } while (0) |
| 4734 | !nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this),do { if (!(!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation , this))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "creating a loop in continuation chain!" , "!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this)" , "./../../../layout/generic/nsTextFrame.cpp", 4735); MOZ_PretendNoReturn (); } } while (0) |
| 4735 | "creating a loop in continuation chain!")do { if (!(!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation , this))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "creating a loop in continuation chain!" , "!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this)" , "./../../../layout/generic/nsTextFrame.cpp", 4735); MOZ_PretendNoReturn (); } } while (0); |
| 4736 | mPrevContinuation = static_cast<nsTextFrame*>(aPrevContinuation); |
| 4737 | RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION); |
| 4738 | UpdateCachedContinuations(); |
| 4739 | } |
| 4740 | |
| 4741 | nsTextFrame* GetPrevInFlow() const final { |
| 4742 | return HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation |
| 4743 | : nullptr; |
| 4744 | } |
| 4745 | |
| 4746 | void SetPrevInFlow(nsIFrame* aPrevInFlow) final { |
| 4747 | NS_ASSERTION(!aPrevInFlow || Type() == aPrevInFlow->Type(),do { if (!(!aPrevInFlow || Type() == aPrevInFlow->Type())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "setting a prev in flow with incorrect type!" , "!aPrevInFlow || Type() == aPrevInFlow->Type()", "./../../../layout/generic/nsTextFrame.cpp" , 4748); MOZ_PretendNoReturn(); } } while (0) |
| 4748 | "setting a prev in flow with incorrect type!")do { if (!(!aPrevInFlow || Type() == aPrevInFlow->Type())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "setting a prev in flow with incorrect type!" , "!aPrevInFlow || Type() == aPrevInFlow->Type()", "./../../../layout/generic/nsTextFrame.cpp" , 4748); MOZ_PretendNoReturn(); } } while (0); |
| 4749 | NS_ASSERTION(do { if (!(!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow , this))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "creating a loop in continuation chain!" , "!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this)" , "./../../../layout/generic/nsTextFrame.cpp", 4751); MOZ_PretendNoReturn (); } } while (0) |
| 4750 | !nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this),do { if (!(!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow , this))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "creating a loop in continuation chain!" , "!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this)" , "./../../../layout/generic/nsTextFrame.cpp", 4751); MOZ_PretendNoReturn (); } } while (0) |
| 4751 | "creating a loop in continuation chain!")do { if (!(!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow , this))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "creating a loop in continuation chain!" , "!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this)" , "./../../../layout/generic/nsTextFrame.cpp", 4751); MOZ_PretendNoReturn (); } } while (0); |
| 4752 | mPrevContinuation = static_cast<nsTextFrame*>(aPrevInFlow); |
| 4753 | AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION); |
| 4754 | UpdateCachedContinuations(); |
| 4755 | } |
| 4756 | |
| 4757 | // Call this helper to update cache after mPrevContinuation is changed. |
| 4758 | void UpdateCachedContinuations() { |
| 4759 | nsTextFrame* prevFirst = mFirstContinuation; |
| 4760 | if (mPrevContinuation) { |
| 4761 | mFirstContinuation = mPrevContinuation->FirstContinuation(); |
| 4762 | if (mFirstContinuation) { |
| 4763 | mFirstContinuation->ClearCachedContinuations(); |
| 4764 | } |
| 4765 | } else { |
| 4766 | mFirstContinuation = nullptr; |
| 4767 | } |
| 4768 | if (mFirstContinuation != prevFirst) { |
| 4769 | if (prevFirst) { |
| 4770 | prevFirst->ClearCachedContinuations(); |
| 4771 | } |
| 4772 | auto* f = static_cast<nsContinuingTextFrame*>(mNextContinuation); |
| 4773 | while (f) { |
| 4774 | f->mFirstContinuation = mFirstContinuation; |
| 4775 | f = static_cast<nsContinuingTextFrame*>(f->mNextContinuation); |
| 4776 | } |
| 4777 | } |
| 4778 | } |
| 4779 | |
| 4780 | nsIFrame* FirstInFlow() const final; |
| 4781 | nsTextFrame* FirstContinuation() const final { |
| 4782 | #if DEBUG1 |
| 4783 | // If we have a prev-continuation pointer, then our first-continuation |
| 4784 | // must be the same as that frame's. |
| 4785 | if (mPrevContinuation) { |
| 4786 | // If there's a prev-prev, then we can safely cast mPrevContinuation to |
| 4787 | // an nsContinuingTextFrame and access its mFirstContinuation pointer |
| 4788 | // directly, to avoid recursively calling FirstContinuation(), leading |
| 4789 | // to exponentially-slow behavior in the assertion. |
| 4790 | if (mPrevContinuation->GetPrevContinuation()) { |
| 4791 | auto* prev = static_cast<nsContinuingTextFrame*>(mPrevContinuation); |
| 4792 | MOZ_ASSERT(mFirstContinuation == prev->mFirstContinuation)do { static_assert( mozilla::detail::AssertionConditionType< decltype(mFirstContinuation == prev->mFirstContinuation)> ::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(mFirstContinuation == prev->mFirstContinuation))) , 0))) { do { } while (false); MOZ_ReportAssertionFailure("mFirstContinuation == prev->mFirstContinuation" , "./../../../layout/generic/nsTextFrame.cpp", 4792); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "mFirstContinuation == prev->mFirstContinuation" ")"); do { MOZ_CrashSequence(__null, 4792); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 4793 | } else { |
| 4794 | MOZ_ASSERT(mFirstContinuation ==do { static_assert( mozilla::detail::AssertionConditionType< decltype(mFirstContinuation == mPrevContinuation->FirstContinuation ())>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(mFirstContinuation == mPrevContinuation->FirstContinuation ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("mFirstContinuation == mPrevContinuation->FirstContinuation()" , "./../../../layout/generic/nsTextFrame.cpp", 4795); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "mFirstContinuation == mPrevContinuation->FirstContinuation()" ")"); do { MOZ_CrashSequence(__null, 4795); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
| 4795 | mPrevContinuation->FirstContinuation())do { static_assert( mozilla::detail::AssertionConditionType< decltype(mFirstContinuation == mPrevContinuation->FirstContinuation ())>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(mFirstContinuation == mPrevContinuation->FirstContinuation ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("mFirstContinuation == mPrevContinuation->FirstContinuation()" , "./../../../layout/generic/nsTextFrame.cpp", 4795); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "mFirstContinuation == mPrevContinuation->FirstContinuation()" ")"); do { MOZ_CrashSequence(__null, 4795); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 4796 | } |
| 4797 | } else { |
| 4798 | MOZ_ASSERT(!mFirstContinuation)do { static_assert( mozilla::detail::AssertionConditionType< decltype(!mFirstContinuation)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!mFirstContinuation))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!mFirstContinuation" , "./../../../layout/generic/nsTextFrame.cpp", 4798); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "!mFirstContinuation" ")"); do { MOZ_CrashSequence (__null, 4798); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 4799 | } |
| 4800 | #endif |
| 4801 | return mFirstContinuation; |
| 4802 | }; |
| 4803 | |
| 4804 | void AddInlineMinISize(const IntrinsicSizeInput& aInput, |
| 4805 | InlineMinISizeData* aData) final { |
| 4806 | // Do nothing, since the first-in-flow accounts for everything. |
| 4807 | } |
| 4808 | void AddInlinePrefISize(const IntrinsicSizeInput& aInput, |
| 4809 | InlinePrefISizeData* aData) final { |
| 4810 | // Do nothing, since the first-in-flow accounts for everything. |
| 4811 | } |
| 4812 | |
| 4813 | protected: |
| 4814 | explicit nsContinuingTextFrame(ComputedStyle* aStyle, |
| 4815 | nsPresContext* aPresContext) |
| 4816 | : nsTextFrame(aStyle, aPresContext, kClassID) {} |
| 4817 | |
| 4818 | nsTextFrame* mPrevContinuation = nullptr; |
| 4819 | nsTextFrame* mFirstContinuation = nullptr; |
| 4820 | }; |
| 4821 | |
| 4822 | void nsContinuingTextFrame::Init(nsIContent* aContent, |
| 4823 | nsContainerFrame* aParent, |
| 4824 | nsIFrame* aPrevInFlow) { |
| 4825 | NS_ASSERTION(aPrevInFlow, "Must be a continuation!")do { if (!(aPrevInFlow)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Must be a continuation!" , "aPrevInFlow", "./../../../layout/generic/nsTextFrame.cpp", 4825); MOZ_PretendNoReturn(); } } while (0); |
| 4826 | |
| 4827 | // Hook the frame into the flow |
| 4828 | nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow); |
| 4829 | nsTextFrame* nextContinuation = prev->GetNextContinuation(); |
| 4830 | SetPrevInFlow(aPrevInFlow); |
| 4831 | aPrevInFlow->SetNextInFlow(this); |
| 4832 | |
| 4833 | // NOTE: bypassing nsTextFrame::Init!!! |
| 4834 | nsIFrame::Init(aContent, aParent, aPrevInFlow); |
| 4835 | |
| 4836 | mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint(); |
| 4837 | NS_ASSERTION(do { if (!(mContentOffset < int32_t(aContent->GetCharacterDataBuffer ()->GetLength()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Creating ContinuingTextFrame, but there is no more content" , "mContentOffset < int32_t(aContent->GetCharacterDataBuffer()->GetLength())" , "./../../../layout/generic/nsTextFrame.cpp", 4839); MOZ_PretendNoReturn (); } } while (0) |
| 4838 | mContentOffset < int32_t(aContent->GetCharacterDataBuffer()->GetLength()),do { if (!(mContentOffset < int32_t(aContent->GetCharacterDataBuffer ()->GetLength()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Creating ContinuingTextFrame, but there is no more content" , "mContentOffset < int32_t(aContent->GetCharacterDataBuffer()->GetLength())" , "./../../../layout/generic/nsTextFrame.cpp", 4839); MOZ_PretendNoReturn (); } } while (0) |
| 4839 | "Creating ContinuingTextFrame, but there is no more content")do { if (!(mContentOffset < int32_t(aContent->GetCharacterDataBuffer ()->GetLength()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Creating ContinuingTextFrame, but there is no more content" , "mContentOffset < int32_t(aContent->GetCharacterDataBuffer()->GetLength())" , "./../../../layout/generic/nsTextFrame.cpp", 4839); MOZ_PretendNoReturn (); } } while (0); |
| 4840 | if (prev->Style() != Style()) { |
| 4841 | // We're taking part of prev's text, and its style may be different |
| 4842 | // so clear its textrun which may no longer be valid (and don't set ours) |
| 4843 | prev->ClearTextRuns(); |
| 4844 | } else { |
| 4845 | float inflation = prev->GetFontSizeInflation(); |
| 4846 | SetFontSizeInflation(inflation); |
| 4847 | mTextRun = prev->GetTextRun(nsTextFrame::eInflated); |
| 4848 | if (inflation != 1.0f) { |
| 4849 | gfxTextRun* uninflatedTextRun = |
| 4850 | prev->GetTextRun(nsTextFrame::eNotInflated); |
| 4851 | if (uninflatedTextRun) { |
| 4852 | SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f); |
| 4853 | } |
| 4854 | } |
| 4855 | } |
| 4856 | if (aPrevInFlow->HasAnyStateBits(NS_FRAME_IS_BIDI)) { |
| 4857 | FrameBidiData bidiData = aPrevInFlow->GetBidiData(); |
| 4858 | bidiData.precedingControl = kBidiLevelNone; |
| 4859 | SetProperty(BidiDataProperty(), bidiData); |
| 4860 | |
| 4861 | if (nextContinuation) { |
| 4862 | SetNextContinuation(nextContinuation); |
| 4863 | nextContinuation->SetPrevContinuation(this); |
| 4864 | // Adjust next-continuations' content offset as needed. |
| 4865 | while (nextContinuation && |
| 4866 | nextContinuation->GetContentOffset() < mContentOffset) { |
| 4867 | #ifdef DEBUG1 |
| 4868 | FrameBidiData nextBidiData = nextContinuation->GetBidiData(); |
| 4869 | NS_ASSERTION(bidiData.embeddingLevel == nextBidiData.embeddingLevel &&do { if (!(bidiData.embeddingLevel == nextBidiData.embeddingLevel && bidiData.baseLevel == nextBidiData.baseLevel)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "stealing text from different type of BIDI continuation" , "bidiData.embeddingLevel == nextBidiData.embeddingLevel && bidiData.baseLevel == nextBidiData.baseLevel" , "./../../../layout/generic/nsTextFrame.cpp", 4871); MOZ_PretendNoReturn (); } } while (0) |
| 4870 | bidiData.baseLevel == nextBidiData.baseLevel,do { if (!(bidiData.embeddingLevel == nextBidiData.embeddingLevel && bidiData.baseLevel == nextBidiData.baseLevel)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "stealing text from different type of BIDI continuation" , "bidiData.embeddingLevel == nextBidiData.embeddingLevel && bidiData.baseLevel == nextBidiData.baseLevel" , "./../../../layout/generic/nsTextFrame.cpp", 4871); MOZ_PretendNoReturn (); } } while (0) |
| 4871 | "stealing text from different type of BIDI continuation")do { if (!(bidiData.embeddingLevel == nextBidiData.embeddingLevel && bidiData.baseLevel == nextBidiData.baseLevel)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "stealing text from different type of BIDI continuation" , "bidiData.embeddingLevel == nextBidiData.embeddingLevel && bidiData.baseLevel == nextBidiData.baseLevel" , "./../../../layout/generic/nsTextFrame.cpp", 4871); MOZ_PretendNoReturn (); } } while (0); |
| 4872 | MOZ_ASSERT(nextBidiData.precedingControl == kBidiLevelNone,do { static_assert( mozilla::detail::AssertionConditionType< decltype(nextBidiData.precedingControl == kBidiLevelNone)> ::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(nextBidiData.precedingControl == kBidiLevelNone))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("nextBidiData.precedingControl == kBidiLevelNone" " (" "There shouldn't be any virtual bidi formatting character " "between continuations" ")", "./../../../layout/generic/nsTextFrame.cpp" , 4874); AnnotateMozCrashReason("MOZ_ASSERT" "(" "nextBidiData.precedingControl == kBidiLevelNone" ") (" "There shouldn't be any virtual bidi formatting character " "between continuations" ")"); do { MOZ_CrashSequence(__null, 4874); __attribute__((nomerge)) ::abort(); } while (false); } } while (false) |
| 4873 | "There shouldn't be any virtual bidi formatting character "do { static_assert( mozilla::detail::AssertionConditionType< decltype(nextBidiData.precedingControl == kBidiLevelNone)> ::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(nextBidiData.precedingControl == kBidiLevelNone))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("nextBidiData.precedingControl == kBidiLevelNone" " (" "There shouldn't be any virtual bidi formatting character " "between continuations" ")", "./../../../layout/generic/nsTextFrame.cpp" , 4874); AnnotateMozCrashReason("MOZ_ASSERT" "(" "nextBidiData.precedingControl == kBidiLevelNone" ") (" "There shouldn't be any virtual bidi formatting character " "between continuations" ")"); do { MOZ_CrashSequence(__null, 4874); __attribute__((nomerge)) ::abort(); } while (false); } } while (false) |
| 4874 | "between continuations")do { static_assert( mozilla::detail::AssertionConditionType< decltype(nextBidiData.precedingControl == kBidiLevelNone)> ::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(nextBidiData.precedingControl == kBidiLevelNone))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("nextBidiData.precedingControl == kBidiLevelNone" " (" "There shouldn't be any virtual bidi formatting character " "between continuations" ")", "./../../../layout/generic/nsTextFrame.cpp" , 4874); AnnotateMozCrashReason("MOZ_ASSERT" "(" "nextBidiData.precedingControl == kBidiLevelNone" ") (" "There shouldn't be any virtual bidi formatting character " "between continuations" ")"); do { MOZ_CrashSequence(__null, 4874); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 4875 | #endif |
| 4876 | nextContinuation->mContentOffset = mContentOffset; |
| 4877 | nextContinuation = nextContinuation->GetNextContinuation(); |
| 4878 | } |
| 4879 | } |
| 4880 | AddStateBits(NS_FRAME_IS_BIDI); |
| 4881 | } // prev frame is bidi |
| 4882 | } |
| 4883 | |
| 4884 | void nsContinuingTextFrame::Destroy(DestroyContext& aContext) { |
| 4885 | ClearFrameOffsetCache(); |
| 4886 | |
| 4887 | // The text associated with this frame will become associated with our |
| 4888 | // prev-continuation. If that means the text has changed style, then |
| 4889 | // we need to wipe out the text run for the text. |
| 4890 | // Note that mPrevContinuation can be null if we're destroying the whole |
| 4891 | // frame chain from the start to the end. |
| 4892 | // If this frame is mentioned in the userData for a textrun (say |
| 4893 | // because there's a direction change at the start of this frame), then |
| 4894 | // we have to clear the textrun because we're going away and the |
| 4895 | // textrun had better not keep a dangling reference to us. |
| 4896 | if (IsInTextRunUserData() || |
| 4897 | (mPrevContinuation && mPrevContinuation->Style() != Style())) { |
| 4898 | ClearTextRuns(); |
| 4899 | // Clear the previous continuation's text run also, so that it can rebuild |
| 4900 | // the text run to include our text. |
| 4901 | if (mPrevContinuation) { |
| 4902 | mPrevContinuation->ClearTextRuns(); |
| 4903 | } |
| 4904 | } |
| 4905 | nsSplittableFrame::RemoveFromFlow(this); |
| 4906 | // Let the base class destroy the frame |
| 4907 | nsIFrame::Destroy(aContext); |
| 4908 | } |
| 4909 | |
| 4910 | nsIFrame* nsContinuingTextFrame::FirstInFlow() const { |
| 4911 | // Can't cast to |nsContinuingTextFrame*| because the first one isn't. |
| 4912 | nsIFrame *firstInFlow, |
| 4913 | *previous = const_cast<nsIFrame*>(static_cast<const nsIFrame*>(this)); |
| 4914 | do { |
| 4915 | firstInFlow = previous; |
| 4916 | previous = firstInFlow->GetPrevInFlow(); |
| 4917 | } while (previous); |
| 4918 | MOZ_ASSERT(firstInFlow, "post-condition failed")do { static_assert( mozilla::detail::AssertionConditionType< decltype(firstInFlow)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(firstInFlow))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("firstInFlow" " (" "post-condition failed" ")", "./../../../layout/generic/nsTextFrame.cpp" , 4918); AnnotateMozCrashReason("MOZ_ASSERT" "(" "firstInFlow" ") (" "post-condition failed" ")"); do { MOZ_CrashSequence(__null , 4918); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 4919 | return firstInFlow; |
| 4920 | } |
| 4921 | |
| 4922 | // XXX Do we want to do all the work for the first-in-flow or do the |
| 4923 | // work for each part? (Be careful of first-letter / first-line, though, |
| 4924 | // especially first-line!) Doing all the work on the first-in-flow has |
| 4925 | // the advantage of avoiding the potential for incremental reflow bugs, |
| 4926 | // but depends on our maintining the frame tree in reasonable ways even |
| 4927 | // for edge cases (block-within-inline splits, nextBidi, etc.) |
| 4928 | |
| 4929 | // XXX We really need to make :first-letter happen during frame |
| 4930 | // construction. |
| 4931 | |
| 4932 | nscoord nsTextFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, |
| 4933 | IntrinsicISizeType aType) { |
| 4934 | return IntrinsicISizeFromInline(aInput, aType); |
| 4935 | } |
| 4936 | |
| 4937 | //---------------------------------------------------------------------- |
| 4938 | |
| 4939 | #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky) |
| 4940 | static void VerifyNotDirty(nsFrameState state) { |
| 4941 | bool isZero = state & NS_FRAME_FIRST_REFLOW; |
| 4942 | bool isDirty = state & NS_FRAME_IS_DIRTY; |
| 4943 | if (!isZero && isDirty) { |
| 4944 | NS_WARNING("internal offsets may be out-of-sync")NS_DebugBreak(NS_DEBUG_WARNING, "internal offsets may be out-of-sync" , nullptr, "./../../../layout/generic/nsTextFrame.cpp", 4944); |
| 4945 | } |
| 4946 | } |
| 4947 | # define DEBUG_VERIFY_NOT_DIRTY(state) VerifyNotDirty(state) |
| 4948 | #else |
| 4949 | # define DEBUG_VERIFY_NOT_DIRTY(state) |
| 4950 | #endif |
| 4951 | |
| 4952 | nsIFrame* NS_NewTextFrame(PresShell* aPresShell, ComputedStyle* aStyle) { |
| 4953 | return new (aPresShell) nsTextFrame(aStyle, aPresShell->GetPresContext()); |
| 4954 | } |
| 4955 | |
| 4956 | NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)void* nsTextFrame ::operator new(size_t sz, mozilla::PresShell * aShell) { return aShell->AllocateFrame(nsQueryFrame::nsTextFrame_id , sz); } |
| 4957 | |
| 4958 | nsIFrame* NS_NewContinuingTextFrame(PresShell* aPresShell, |
| 4959 | ComputedStyle* aStyle) { |
| 4960 | return new (aPresShell) |
| 4961 | nsContinuingTextFrame(aStyle, aPresShell->GetPresContext()); |
| 4962 | } |
| 4963 | |
| 4964 | NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)void* nsContinuingTextFrame ::operator new(size_t sz, mozilla ::PresShell * aShell) { return aShell->AllocateFrame(nsQueryFrame ::nsContinuingTextFrame_id, sz); } |
| 4965 | |
| 4966 | nsTextFrame::~nsTextFrame() = default; |
| 4967 | |
| 4968 | nsIFrame::Cursor nsTextFrame::GetCursor(const nsPoint& aPoint) { |
| 4969 | StyleCursorKind kind = StyleUI()->Cursor().keyword; |
| 4970 | if (kind == StyleCursorKind::Auto) { |
| 4971 | if (!IsSelectable()) { |
| 4972 | kind = StyleCursorKind::Default; |
| 4973 | } else { |
| 4974 | kind = GetWritingMode().IsVertical() ? StyleCursorKind::VerticalText |
| 4975 | : StyleCursorKind::Text; |
| 4976 | } |
| 4977 | } |
| 4978 | return Cursor{kind, AllowCustomCursorImage::Yes}; |
| 4979 | } |
| 4980 | |
| 4981 | nsTextFrame* nsTextFrame::LastInFlow() const { |
| 4982 | nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this); |
| 4983 | while (lastInFlow->GetNextInFlow()) { |
| 4984 | lastInFlow = lastInFlow->GetNextInFlow(); |
| 4985 | } |
| 4986 | MOZ_ASSERT(lastInFlow, "post-condition failed")do { static_assert( mozilla::detail::AssertionConditionType< decltype(lastInFlow)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(lastInFlow))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("lastInFlow" " (" "post-condition failed" ")", "./../../../layout/generic/nsTextFrame.cpp", 4986); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "lastInFlow" ") (" "post-condition failed" ")" ); do { MOZ_CrashSequence(__null, 4986); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 4987 | return lastInFlow; |
| 4988 | } |
| 4989 | |
| 4990 | nsTextFrame* nsTextFrame::LastContinuation() const { |
| 4991 | nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this); |
| 4992 | while (lastContinuation->mNextContinuation) { |
| 4993 | lastContinuation = lastContinuation->mNextContinuation; |
| 4994 | } |
| 4995 | MOZ_ASSERT(lastContinuation, "post-condition failed")do { static_assert( mozilla::detail::AssertionConditionType< decltype(lastContinuation)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(lastContinuation))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("lastContinuation" " (" "post-condition failed" ")", "./../../../layout/generic/nsTextFrame.cpp" , 4995); AnnotateMozCrashReason("MOZ_ASSERT" "(" "lastContinuation" ") (" "post-condition failed" ")"); do { MOZ_CrashSequence(__null , 4995); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 4996 | return lastContinuation; |
| 4997 | } |
| 4998 | |
| 4999 | bool nsTextFrame::ShouldSuppressLineBreak() const { |
| 5000 | // If the parent frame of the text frame is ruby content box, it must |
| 5001 | // suppress line break inside. This check is necessary, because when |
| 5002 | // a whitespace is only contained by pseudo ruby frames, its style |
| 5003 | // context won't have SuppressLineBreak bit set. |
| 5004 | if (mozilla::RubyUtils::IsRubyContentBox(GetParent()->Type())) { |
| 5005 | return true; |
| 5006 | } |
| 5007 | return Style()->ShouldSuppressLineBreak(); |
| 5008 | } |
| 5009 | |
| 5010 | void nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey, |
| 5011 | bool aRebuildDisplayItems) { |
| 5012 | InvalidateSelectionState(); |
| 5013 | |
| 5014 | if (IsInSVGTextSubtree()) { |
| 5015 | nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType( |
| 5016 | GetParent(), LayoutFrameType::SVGText); |
| 5017 | svgTextFrame->InvalidateFrame(); |
| 5018 | return; |
| 5019 | } |
| 5020 | nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); |
| 5021 | } |
| 5022 | |
| 5023 | void nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect, |
| 5024 | uint32_t aDisplayItemKey, |
| 5025 | bool aRebuildDisplayItems) { |
| 5026 | InvalidateSelectionState(); |
| 5027 | |
| 5028 | if (IsInSVGTextSubtree()) { |
| 5029 | nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType( |
| 5030 | GetParent(), LayoutFrameType::SVGText); |
| 5031 | svgTextFrame->InvalidateFrame(); |
| 5032 | return; |
| 5033 | } |
| 5034 | nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, |
| 5035 | aRebuildDisplayItems); |
| 5036 | } |
| 5037 | |
| 5038 | gfxTextRun* nsTextFrame::GetUninflatedTextRun() const { |
| 5039 | return GetProperty(UninflatedTextRunProperty()); |
| 5040 | } |
| 5041 | |
| 5042 | void nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun, |
| 5043 | float aInflation) { |
| 5044 | NS_ASSERTION(aTextRun, "must have text run")do { if (!(aTextRun)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "must have text run" , "aTextRun", "./../../../layout/generic/nsTextFrame.cpp", 5044 ); MOZ_PretendNoReturn(); } } while (0); |
| 5045 | |
| 5046 | // Our inflated text run is always stored in mTextRun. In the cases |
| 5047 | // where our current inflation is not 1.0, however, we store two text |
| 5048 | // runs, and the uninflated one goes in a frame property. We never |
| 5049 | // store a single text run in both. |
| 5050 | if (aWhichTextRun == eInflated) { |
| 5051 | if (HasFontSizeInflation() && aInflation == 1.0f) { |
| 5052 | // FIXME: Probably shouldn't do this within each SetTextRun |
| 5053 | // method, but it doesn't hurt. |
| 5054 | ClearTextRun(nullptr, nsTextFrame::eNotInflated); |
| 5055 | } |
| 5056 | SetFontSizeInflation(aInflation); |
| 5057 | } else { |
| 5058 | MOZ_ASSERT(aInflation == 1.0f, "unexpected inflation")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aInflation == 1.0f)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aInflation == 1.0f))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aInflation == 1.0f" " (" "unexpected inflation" ")", "./../../../layout/generic/nsTextFrame.cpp" , 5058); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aInflation == 1.0f" ") (" "unexpected inflation" ")"); do { MOZ_CrashSequence(__null , 5058); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 5059 | if (HasFontSizeInflation()) { |
| 5060 | // Setting the property will not automatically increment the textrun's |
| 5061 | // reference count, so we need to do it here. |
| 5062 | aTextRun->AddRef(); |
| 5063 | SetProperty(UninflatedTextRunProperty(), aTextRun); |
| 5064 | return; |
| 5065 | } |
| 5066 | // fall through to setting mTextRun |
| 5067 | } |
| 5068 | |
| 5069 | mTextRun = aTextRun; |
| 5070 | |
| 5071 | // FIXME: Add assertions testing the relationship between |
| 5072 | // GetFontSizeInflation() and whether we have an uninflated text run |
| 5073 | // (but be aware that text runs can go away). |
| 5074 | } |
| 5075 | |
| 5076 | bool nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun) { |
| 5077 | if (aTextRun == mTextRun) { |
| 5078 | mTextRun = nullptr; |
| 5079 | mFontMetrics = nullptr; |
| 5080 | return true; |
| 5081 | } |
| 5082 | if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION) && |
| 5083 | GetProperty(UninflatedTextRunProperty()) == aTextRun) { |
| 5084 | RemoveProperty(UninflatedTextRunProperty()); |
| 5085 | return true; |
| 5086 | } |
| 5087 | return false; |
| 5088 | } |
| 5089 | |
| 5090 | void nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation, |
| 5091 | TextRunType aWhichTextRun) { |
| 5092 | RefPtr<gfxTextRun> textRun = GetTextRun(aWhichTextRun); |
| 5093 | if (!textRun) { |
| 5094 | return; |
| 5095 | } |
| 5096 | |
| 5097 | if (aWhichTextRun == nsTextFrame::eInflated) { |
| 5098 | mFontMetrics = nullptr; |
| 5099 | } |
| 5100 | |
| 5101 | DebugOnly<bool> checkmTextrun = textRun == mTextRun; |
| 5102 | UnhookTextRunFromFrames(textRun, aStartContinuation); |
| 5103 | MOZ_ASSERT(checkmTextrun ? !mTextRundo { static_assert( mozilla::detail::AssertionConditionType< decltype(checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty ()))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty ())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty())" , "./../../../layout/generic/nsTextFrame.cpp", 5104); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty())" ")"); do { MOZ_CrashSequence(__null, 5104); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
| 5104 | : !GetProperty(UninflatedTextRunProperty()))do { static_assert( mozilla::detail::AssertionConditionType< decltype(checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty ()))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty ())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty())" , "./../../../layout/generic/nsTextFrame.cpp", 5104); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "checkmTextrun ? !mTextRun : !GetProperty(UninflatedTextRunProperty())" ")"); do { MOZ_CrashSequence(__null, 5104); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 5105 | } |
| 5106 | |
| 5107 | void nsTextFrame::DisconnectTextRuns() { |
| 5108 | MOZ_ASSERT(!IsInTextRunUserData(),do { static_assert( mozilla::detail::AssertionConditionType< decltype(!IsInTextRunUserData())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!IsInTextRunUserData()))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!IsInTextRunUserData()" " (" "Textrun mentions this frame in its user data so we can't just " "disconnect" ")", "./../../../layout/generic/nsTextFrame.cpp" , 5110); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!IsInTextRunUserData()" ") (" "Textrun mentions this frame in its user data so we can't just " "disconnect" ")"); do { MOZ_CrashSequence(__null, 5110); __attribute__ ((nomerge)) ::abort(); } while (false); } } while (false) |
| 5109 | "Textrun mentions this frame in its user data so we can't just "do { static_assert( mozilla::detail::AssertionConditionType< decltype(!IsInTextRunUserData())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!IsInTextRunUserData()))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!IsInTextRunUserData()" " (" "Textrun mentions this frame in its user data so we can't just " "disconnect" ")", "./../../../layout/generic/nsTextFrame.cpp" , 5110); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!IsInTextRunUserData()" ") (" "Textrun mentions this frame in its user data so we can't just " "disconnect" ")"); do { MOZ_CrashSequence(__null, 5110); __attribute__ ((nomerge)) ::abort(); } while (false); } } while (false) |
| 5110 | "disconnect")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!IsInTextRunUserData())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!IsInTextRunUserData()))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!IsInTextRunUserData()" " (" "Textrun mentions this frame in its user data so we can't just " "disconnect" ")", "./../../../layout/generic/nsTextFrame.cpp" , 5110); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!IsInTextRunUserData()" ") (" "Textrun mentions this frame in its user data so we can't just " "disconnect" ")"); do { MOZ_CrashSequence(__null, 5110); __attribute__ ((nomerge)) ::abort(); } while (false); } } while (false); |
| 5111 | mTextRun = nullptr; |
| 5112 | if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION)) { |
| 5113 | RemoveProperty(UninflatedTextRunProperty()); |
| 5114 | } |
| 5115 | } |
| 5116 | |
| 5117 | void nsTextFrame::NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength) { |
| 5118 | MOZ_ASSERT(mContent->IsInNativeAnonymousSubtree())do { static_assert( mozilla::detail::AssertionConditionType< decltype(mContent->IsInNativeAnonymousSubtree())>::isValid , "invalid assertion condition"); if ((__builtin_expect(!!(!( !!(mContent->IsInNativeAnonymousSubtree()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mContent->IsInNativeAnonymousSubtree()" , "./../../../layout/generic/nsTextFrame.cpp", 5118); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "mContent->IsInNativeAnonymousSubtree()" ")"); do { MOZ_CrashSequence(__null, 5118); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 5119 | |
| 5120 | MarkIntrinsicISizesDirty(); |
| 5121 | |
| 5122 | // This is to avoid making a new Reflow request in CharacterDataChanged: |
| 5123 | for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) { |
| 5124 | f->MarkSubtreeDirty(); |
| 5125 | f->mReflowRequestedForCharDataChange = true; |
| 5126 | } |
| 5127 | |
| 5128 | // Pretend that all the text changed. |
| 5129 | CharacterDataChangeInfo info; |
| 5130 | info.mAppend = false; |
| 5131 | info.mChangeStart = 0; |
| 5132 | info.mChangeEnd = aOldLength; |
| 5133 | info.mReplaceLength = GetContent()->TextLength(); |
| 5134 | CharacterDataChanged(info); |
| 5135 | } |
| 5136 | |
| 5137 | nsresult nsTextFrame::CharacterDataChanged( |
| 5138 | const CharacterDataChangeInfo& aInfo) { |
| 5139 | if (mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) { |
| 5140 | mContent->RemoveProperty(nsGkAtoms::newline); |
| 5141 | mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY); |
| 5142 | } |
| 5143 | if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) { |
| 5144 | mContent->RemoveProperty(nsGkAtoms::flowlength); |
| 5145 | mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY); |
| 5146 | } |
| 5147 | |
| 5148 | // Find the first frame whose text has changed. Frames that are entirely |
| 5149 | // before the text change are completely unaffected. |
| 5150 | nsTextFrame* next; |
| 5151 | nsTextFrame* textFrame = this; |
| 5152 | while (true) { |
| 5153 | next = textFrame->GetNextContinuation(); |
| 5154 | if (!next || next->GetContentOffset() > int32_t(aInfo.mChangeStart)) { |
| 5155 | break; |
| 5156 | } |
| 5157 | textFrame = next; |
| 5158 | } |
| 5159 | |
| 5160 | int32_t endOfChangedText = aInfo.mChangeStart + aInfo.mReplaceLength; |
| 5161 | |
| 5162 | // Parent of the last frame that we passed to FrameNeedsReflow (or noticed |
| 5163 | // had already received an earlier FrameNeedsReflow call). |
| 5164 | // (For subsequent frames with this same parent, we can just set their |
| 5165 | // dirty bit without bothering to call FrameNeedsReflow again.) |
| 5166 | nsIFrame* lastDirtiedFrameParent = nullptr; |
| 5167 | |
| 5168 | mozilla::PresShell* presShell = PresShell(); |
| 5169 | do { |
| 5170 | // textFrame contained deleted text (or the insertion point, |
| 5171 | // if this was a pure insertion). |
| 5172 | textFrame->RemoveStateBits(TEXT_WHITESPACE_FLAGS); |
| 5173 | textFrame->ClearTextRuns(); |
| 5174 | |
| 5175 | nsIFrame* parentOfTextFrame = textFrame->GetParent(); |
| 5176 | bool areAncestorsAwareOfReflowRequest = false; |
| 5177 | if (lastDirtiedFrameParent == parentOfTextFrame) { |
| 5178 | // An earlier iteration of this loop already called |
| 5179 | // FrameNeedsReflow for a sibling of |textFrame|. |
| 5180 | areAncestorsAwareOfReflowRequest = true; |
| 5181 | } else { |
| 5182 | lastDirtiedFrameParent = parentOfTextFrame; |
| 5183 | } |
| 5184 | |
| 5185 | if (textFrame->mReflowRequestedForCharDataChange) { |
| 5186 | // We already requested a reflow for this frame; nothing to do. |
| 5187 | MOZ_ASSERT(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY),do { static_assert( mozilla::detail::AssertionConditionType< decltype(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))> ::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)" " (" "mReflowRequestedForCharDataChange should only be set " "on dirty frames" ")", "./../../../layout/generic/nsTextFrame.cpp" , 5189); AnnotateMozCrashReason("MOZ_ASSERT" "(" "textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)" ") (" "mReflowRequestedForCharDataChange should only be set " "on dirty frames" ")"); do { MOZ_CrashSequence(__null, 5189) ; __attribute__((nomerge)) ::abort(); } while (false); } } while (false) |
| 5188 | "mReflowRequestedForCharDataChange should only be set "do { static_assert( mozilla::detail::AssertionConditionType< decltype(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))> ::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)" " (" "mReflowRequestedForCharDataChange should only be set " "on dirty frames" ")", "./../../../layout/generic/nsTextFrame.cpp" , 5189); AnnotateMozCrashReason("MOZ_ASSERT" "(" "textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)" ") (" "mReflowRequestedForCharDataChange should only be set " "on dirty frames" ")"); do { MOZ_CrashSequence(__null, 5189) ; __attribute__((nomerge)) ::abort(); } while (false); } } while (false) |
| 5189 | "on dirty frames")do { static_assert( mozilla::detail::AssertionConditionType< decltype(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))> ::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)" " (" "mReflowRequestedForCharDataChange should only be set " "on dirty frames" ")", "./../../../layout/generic/nsTextFrame.cpp" , 5189); AnnotateMozCrashReason("MOZ_ASSERT" "(" "textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)" ") (" "mReflowRequestedForCharDataChange should only be set " "on dirty frames" ")"); do { MOZ_CrashSequence(__null, 5189) ; __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 5190 | } else { |
| 5191 | // Make sure textFrame is queued up for a reflow. Also set a flag so we |
| 5192 | // don't waste time doing this again in repeated calls to this method. |
| 5193 | textFrame->mReflowRequestedForCharDataChange = true; |
| 5194 | if (!areAncestorsAwareOfReflowRequest) { |
| 5195 | // Ask the parent frame to reflow me. |
| 5196 | presShell->FrameNeedsReflow( |
| 5197 | textFrame, IntrinsicDirty::FrameAndAncestors, NS_FRAME_IS_DIRTY); |
| 5198 | } else { |
| 5199 | // We already called FrameNeedsReflow on behalf of an earlier sibling, |
| 5200 | // so we can just mark this frame as dirty and don't need to bother |
| 5201 | // telling its ancestors. |
| 5202 | // Note: if the parent is a block, we're cheating here because we should |
| 5203 | // be marking our line dirty, but we're not. nsTextFrame::SetLength will |
| 5204 | // do that when it gets called during reflow. |
| 5205 | textFrame->MarkSubtreeDirty(); |
| 5206 | } |
| 5207 | } |
| 5208 | textFrame->InvalidateFrame(); |
| 5209 | |
| 5210 | // Below, frames that start after the deleted text will be adjusted so that |
| 5211 | // their offsets move with the trailing unchanged text. If this change |
| 5212 | // deletes more text than it inserts, those frame offsets will decrease. |
| 5213 | // We need to maintain the invariant that mContentOffset is non-decreasing |
| 5214 | // along the continuation chain. So we need to ensure that frames that |
| 5215 | // started in the deleted text are all still starting before the |
| 5216 | // unchanged text. |
| 5217 | if (textFrame->mContentOffset > endOfChangedText) { |
| 5218 | textFrame->mContentOffset = endOfChangedText; |
| 5219 | } |
| 5220 | |
| 5221 | textFrame = textFrame->GetNextContinuation(); |
| 5222 | } while (textFrame && |
| 5223 | textFrame->GetContentOffset() < int32_t(aInfo.mChangeEnd)); |
| 5224 | |
| 5225 | // This is how much the length of the string changed by --- i.e., |
| 5226 | // how much the trailing unchanged text moved. |
| 5227 | int32_t sizeChange = |
| 5228 | aInfo.mChangeStart + aInfo.mReplaceLength - aInfo.mChangeEnd; |
| 5229 | |
| 5230 | if (sizeChange) { |
| 5231 | // Fix the offsets of the text frames that start in the trailing |
| 5232 | // unchanged text. |
| 5233 | while (textFrame) { |
| 5234 | textFrame->mContentOffset += sizeChange; |
| 5235 | // XXX we could rescue some text runs by adjusting their user data |
| 5236 | // to reflect the change in DOM offsets |
| 5237 | textFrame->ClearTextRuns(); |
| 5238 | textFrame = textFrame->GetNextContinuation(); |
| 5239 | } |
| 5240 | } |
| 5241 | |
| 5242 | return NS_OK; |
| 5243 | } |
| 5244 | |
| 5245 | struct TextCombineData { |
| 5246 | // Measured advance of the text before any text-combine scaling is applied. |
| 5247 | nscoord mNaturalWidth = 0; |
| 5248 | // Inline offset to place this text within the 1-em block of the upright- |
| 5249 | // combined cell. |
| 5250 | nscoord mOffset = 0; |
| 5251 | // Inline scaling factor to apply (always <= 1.0, as the text may be |
| 5252 | // compressed but is never expanded to fit the 1-em cell). |
| 5253 | float mScale = 1.0f; |
| 5254 | }; |
| 5255 | |
| 5256 | NS_DECLARE_FRAME_PROPERTY_DELETABLE(TextCombineDataProperty, TextCombineData)static const mozilla::FramePropertyDescriptor<TextCombineData >* TextCombineDataProperty() { static const auto descriptor = mozilla::FramePropertyDescriptor<TextCombineData>::NewWithDestructor <DeleteValue>(); return &descriptor; } |
| 5257 | |
| 5258 | float nsTextFrame::GetTextCombineScale() const { |
| 5259 | const auto* data = GetProperty(TextCombineDataProperty()); |
| 5260 | return data ? data->mScale : 1.0f; |
| 5261 | } |
| 5262 | |
| 5263 | std::pair<nscoord, float> nsTextFrame::GetTextCombineOffsetAndScale() const { |
| 5264 | const auto* data = GetProperty(TextCombineDataProperty()); |
| 5265 | return data ? std::pair(data->mOffset, data->mScale) : std::pair(0, 1.0f); |
| 5266 | } |
| 5267 | |
| 5268 | void nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
| 5269 | const nsDisplayListSet& aLists) { |
| 5270 | if (!IsVisibleForPainting()) { |
| 5271 | return; |
| 5272 | } |
| 5273 | |
| 5274 | DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame")do { if (!aBuilder->IsBackgroundOnly() && !aBuilder ->IsForEventDelivery() && PresShell()->IsPaintingFrameCounts ()) { aLists.Outlines()->AppendNewToTop<mozilla::nsDisplayReflowCount >( aBuilder, this, "nsTextFrame"); } } while (0); |
| 5275 | |
| 5276 | const nsStyleText* st = StyleText(); |
| 5277 | bool isTextTransparent = |
| 5278 | NS_GET_A(st->mWebkitTextFillColor.CalcColor(this))((uint8_t)(((st->mWebkitTextFillColor.CalcColor(this)) >> 24) & 0xff)) == 0 && |
| 5279 | NS_GET_A(st->mWebkitTextStrokeColor.CalcColor(this))((uint8_t)(((st->mWebkitTextStrokeColor.CalcColor(this)) >> 24) & 0xff)) == 0; |
| 5280 | if ((HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS) || |
| 5281 | (isTextTransparent && !StyleText()->HasTextShadow())) && |
| 5282 | aBuilder->IsForPainting() && !IsInSVGTextSubtree()) { |
| 5283 | if (!IsSelected()) { |
| 5284 | TextDecorations textDecs; |
| 5285 | GetTextDecorations(PresContext(), eResolvedColors, textDecs); |
| 5286 | if (!textDecs.HasDecorationLines()) { |
| 5287 | if (auto* currentPresContext = aBuilder->CurrentPresContext()) { |
| 5288 | currentPresContext->SetBuiltInvisibleText(); |
| 5289 | } |
| 5290 | return; |
| 5291 | } |
| 5292 | } |
| 5293 | } |
| 5294 | |
| 5295 | aLists.Content()->AppendNewToTop<nsDisplayText>(aBuilder, this); |
| 5296 | } |
| 5297 | |
| 5298 | UniquePtr<SelectionDetails> nsTextFrame::GetSelectionDetails() { |
| 5299 | const nsFrameSelection* frameSelection = GetConstFrameSelection(); |
| 5300 | if (frameSelection->IsInTableSelectionMode()) { |
| 5301 | return nullptr; |
| 5302 | } |
| 5303 | UniquePtr<SelectionDetails> details = frameSelection->LookUpSelection( |
| 5304 | mContent, GetContentOffset(), GetContentLength(), |
| 5305 | // We don't want to paint text as selected if this is not selectable. |
| 5306 | // Note if this is editable, this is always treated as selectable, i.e., |
| 5307 | // if `user-select` is specified to `none` so that we never stop painting |
| 5308 | // selections when there is IME composition which may need normal |
| 5309 | // selection as a part of it. |
| 5310 | ShouldPaintNormalSelection() |
| 5311 | ? nsFrameSelection::IgnoreNormalSelection::No |
| 5312 | : nsFrameSelection::IgnoreNormalSelection::Yes); |
| 5313 | for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) { |
| 5314 | sd->mStart += mContentOffset; |
| 5315 | sd->mEnd += mContentOffset; |
| 5316 | } |
| 5317 | return details; |
| 5318 | } |
| 5319 | |
| 5320 | static void PaintSelectionBackground( |
| 5321 | DrawTarget& aDrawTarget, nscolor aColor, const LayoutDeviceRect& aDirtyRect, |
| 5322 | const LayoutDeviceRect& aRect, nsTextFrame::DrawPathCallbacks* aCallbacks) { |
| 5323 | Rect rect = aRect.Intersect(aDirtyRect).ToUnknownRect(); |
| 5324 | MaybeSnapToDevicePixels(rect, aDrawTarget); |
| 5325 | |
| 5326 | if (aCallbacks) { |
| 5327 | aCallbacks->NotifySelectionBackgroundNeedsFill(rect, aColor, aDrawTarget); |
| 5328 | } else { |
| 5329 | ColorPattern color(ToDeviceColor(aColor)); |
| 5330 | aDrawTarget.FillRect(rect, color); |
| 5331 | } |
| 5332 | } |
| 5333 | |
| 5334 | // Attempt to get the LineBaselineOffset property of aChildFrame |
| 5335 | // If not set, calculate this value for all child frames of aBlockFrame |
| 5336 | static nscoord LazyGetLineBaselineOffset(nsIFrame* aChildFrame, |
| 5337 | nsBlockFrame* aBlockFrame) { |
| 5338 | bool offsetFound; |
| 5339 | nscoord offset = |
| 5340 | aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(), &offsetFound); |
| 5341 | |
| 5342 | if (!offsetFound) { |
| 5343 | for (const auto& line : aBlockFrame->Lines()) { |
| 5344 | if (line.IsInline()) { |
| 5345 | nscoord lineBaseline = line.BStart() + line.GetLogicalAscent(); |
| 5346 | for (nsIFrame* lineFrame : line.ChildFrames()) { |
| 5347 | offset = lineBaseline - lineFrame->GetNormalPosition().y; |
| 5348 | lineFrame->SetProperty(nsIFrame::LineBaselineOffset(), offset); |
| 5349 | } |
| 5350 | } |
| 5351 | } |
| 5352 | return aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(), |
| 5353 | &offsetFound); |
| 5354 | } else { |
| 5355 | return offset; |
| 5356 | } |
| 5357 | } |
| 5358 | |
| 5359 | static bool IsUnderlineRight(const ComputedStyle& aStyle) { |
| 5360 | // Check for 'left' or 'right' explicitly specified in the property; |
| 5361 | // if neither is there, we use auto positioning based on lang. |
| 5362 | const auto position = aStyle.StyleText()->mTextUnderlinePosition; |
| 5363 | if (position.IsLeft()) { |
| 5364 | return false; |
| 5365 | } |
| 5366 | if (position.IsRight()) { |
| 5367 | return true; |
| 5368 | } |
| 5369 | // If neither 'left' nor 'right' was specified, check the language. |
| 5370 | nsAtom* langAtom = aStyle.StyleFont()->GetLangAtom(); |
| 5371 | if (!langAtom) { |
| 5372 | return false; |
| 5373 | } |
| 5374 | return nsStyleUtil::MatchesLanguagePrefix(langAtom, u"ja") || |
| 5375 | nsStyleUtil::MatchesLanguagePrefix(langAtom, u"ko") || |
| 5376 | nsStyleUtil::MatchesLanguagePrefix(langAtom, u"mn"); |
| 5377 | } |
| 5378 | |
| 5379 | static bool FrameStopsLineDecorationPropagation(nsIFrame* aFrame, |
| 5380 | nsCompatibility aCompatMode) { |
| 5381 | // In all modes, if we're on an inline-block/table/grid/flex, we're done. |
| 5382 | // If we're on a ruby frame other than ruby text container, we |
| 5383 | // should continue. |
| 5384 | mozilla::StyleDisplay display = aFrame->GetDisplay(); |
| 5385 | if (!display.IsInlineFlow() && |
| 5386 | (!display.IsRuby() || |
| 5387 | display == mozilla::StyleDisplay::RubyTextContainer) && |
| 5388 | display.IsInlineOutside()) { |
| 5389 | return true; |
| 5390 | } |
| 5391 | // In quirks mode, if we're on an HTML table element, we're done. |
| 5392 | if (aCompatMode == eCompatibility_NavQuirks && |
| 5393 | aFrame->GetContent()->IsHTMLElement(nsGkAtoms::table)) { |
| 5394 | return true; |
| 5395 | } |
| 5396 | // If we're on an absolutely-positioned element or a floating |
| 5397 | // element, we're done. |
| 5398 | if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { |
| 5399 | return true; |
| 5400 | } |
| 5401 | // If we're an outer <svg> element, which is classified as an atomic |
| 5402 | // inline-level element, we're done. |
| 5403 | if (aFrame->IsSVGOuterSVGFrame()) { |
| 5404 | return true; |
| 5405 | } |
| 5406 | return false; |
| 5407 | } |
| 5408 | |
| 5409 | void nsTextFrame::GetTextDecorations( |
| 5410 | nsPresContext* aPresContext, |
| 5411 | nsTextFrame::TextDecorationColorResolution aColorResolution, |
| 5412 | nsTextFrame::TextDecorations& aDecorations) { |
| 5413 | const nsCompatibility compatMode = aPresContext->CompatibilityMode(); |
| 5414 | |
| 5415 | bool useOverride = false; |
| 5416 | nscolor overrideColor = NS_RGBA(0, 0, 0, 0)((nscolor)(((0) << 24) | ((0) << 16) | ((0) << 8) | (0))); |
| 5417 | |
| 5418 | bool nearestBlockFound = false; |
| 5419 | // Use writing mode of parent frame for orthogonal text frame to work. |
| 5420 | // See comment in nsTextFrame::DrawTextRunAndDecorations. |
| 5421 | WritingMode wm = GetParent()->GetWritingMode(); |
| 5422 | bool vertical = wm.IsVertical(); |
| 5423 | |
| 5424 | nscoord ascent = GetLogicalBaseline(wm); |
| 5425 | // physicalBlockStartOffset represents the offset from our baseline |
| 5426 | // to f's physical block start, which is top in horizontal writing |
| 5427 | // mode, and left in vertical writing modes, in our coordinate space. |
| 5428 | // This physical block start is logical block start in most cases, |
| 5429 | // but for vertical-rl, it is logical block end, and consequently in |
| 5430 | // that case, it starts from the descent instead of ascent. |
| 5431 | nscoord physicalBlockStartOffset = |
| 5432 | wm.IsVerticalRL() ? GetSize().width - ascent : ascent; |
| 5433 | // baselineOffset represents the offset from our baseline to f's baseline or |
| 5434 | // the nearest block's baseline, in our coordinate space, whichever is closest |
| 5435 | // during the particular iteration |
| 5436 | nscoord baselineOffset = 0; |
| 5437 | |
| 5438 | for (nsIFrame *f = this, *fChild = nullptr; f; |
| 5439 | fChild = f, f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) { |
| 5440 | ComputedStyle* const context = f->Style(); |
| 5441 | if (!context->HasTextDecorationLines()) { |
| 5442 | break; |
| 5443 | } |
| 5444 | |
| 5445 | if (context->GetPseudoType() == PseudoStyleType::Marker && |
| 5446 | (context->StyleList()->mListStylePosition == |
| 5447 | StyleListStylePosition::Outside || |
| 5448 | !context->StyleDisplay()->IsInlineOutsideStyle())) { |
| 5449 | // Outside ::marker pseudos, and inside markers that aren't inlines, don't |
| 5450 | // have text decorations. |
| 5451 | break; |
| 5452 | } |
| 5453 | |
| 5454 | const nsStyleTextReset* const styleTextReset = context->StyleTextReset(); |
| 5455 | StyleTextDecorationLine textDecorations = |
| 5456 | styleTextReset->mTextDecorationLine; |
| 5457 | bool ignoreSubproperties = false; |
| 5458 | |
| 5459 | auto lineStyle = styleTextReset->mTextDecorationStyle; |
| 5460 | if (textDecorations == StyleTextDecorationLine::SPELLING_ERROR || |
| 5461 | textDecorations == StyleTextDecorationLine::GRAMMAR_ERROR) { |
| 5462 | nscolor lineColor; |
| 5463 | float relativeSize; |
| 5464 | useOverride = nsTextPaintStyle::GetSelectionUnderline( |
| 5465 | this, nsTextPaintStyle::SelectionStyleIndex::SpellChecker, &lineColor, |
| 5466 | &relativeSize, &lineStyle); |
| 5467 | if (useOverride) { |
| 5468 | // We don't currently have a SelectionStyleIndex::GrammarChecker; for |
| 5469 | // now just use SpellChecker and change its color to green. |
| 5470 | overrideColor = |
| 5471 | textDecorations == StyleTextDecorationLine::SPELLING_ERROR |
| 5472 | ? lineColor |
| 5473 | : NS_RGBA(0, 128, 0, 255)((nscolor)(((255) << 24) | ((0) << 16) | ((128) << 8) | (0))); |
| 5474 | textDecorations = StyleTextDecorationLine::UNDERLINE; |
| 5475 | ignoreSubproperties = true; |
| 5476 | } |
| 5477 | } |
| 5478 | |
| 5479 | if (!useOverride && |
| 5480 | (StyleTextDecorationLine::COLOR_OVERRIDE & textDecorations)) { |
| 5481 | // This handles the <a href="blah.html"><font color="green">La |
| 5482 | // la la</font></a> case. The link underline should be green. |
| 5483 | useOverride = true; |
| 5484 | overrideColor = nsLayoutUtils::GetTextColor( |
| 5485 | f, &nsStyleTextReset::mTextDecorationColor); |
| 5486 | } |
| 5487 | |
| 5488 | nsBlockFrame* fBlock = do_QueryFrame(f); |
| 5489 | const bool firstBlock = !nearestBlockFound && fBlock; |
| 5490 | |
| 5491 | // Not updating positions once we hit a parent block is equivalent to |
| 5492 | // the CSS 2.1 spec that blocks should propagate decorations down to their |
| 5493 | // children (albeit the style should be preserved) |
| 5494 | // However, if we're vertically aligned within a block, then we need to |
| 5495 | // recover the correct baseline from the line by querying the FrameProperty |
| 5496 | // that should be set (see nsLineLayout::VerticalAlignLine). |
| 5497 | if (firstBlock) { |
| 5498 | // At this point, fChild can't be null since TextFrames can't be blocks |
| 5499 | if (!HasDefaultVerticalAlignment(fChild)) { |
| 5500 | // Since offset is the offset in the child's coordinate space, we have |
| 5501 | // to undo the accumulation to bring the transform out of the block's |
| 5502 | // coordinate space |
| 5503 | const nscoord lineBaselineOffset = |
| 5504 | LazyGetLineBaselineOffset(fChild, fBlock); |
| 5505 | |
| 5506 | baselineOffset = physicalBlockStartOffset - lineBaselineOffset - |
| 5507 | (vertical ? fChild->GetNormalPosition().x |
| 5508 | : fChild->GetNormalPosition().y); |
| 5509 | } |
| 5510 | } else if (!nearestBlockFound) { |
| 5511 | // offset here is the offset from f's baseline to f's top/left |
| 5512 | // boundary. It's descent for vertical-rl, and ascent otherwise. |
| 5513 | nscoord offset = wm.IsVerticalRL() |
| 5514 | ? f->GetSize().width - f->GetLogicalBaseline(wm) |
| 5515 | : f->GetLogicalBaseline(wm); |
| 5516 | baselineOffset = physicalBlockStartOffset - offset; |
| 5517 | } |
| 5518 | |
| 5519 | nearestBlockFound = nearestBlockFound || firstBlock; |
| 5520 | physicalBlockStartOffset += |
| 5521 | vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y; |
| 5522 | |
| 5523 | if (textDecorations) { |
| 5524 | nscolor color; |
| 5525 | if (useOverride) { |
| 5526 | color = overrideColor; |
| 5527 | } else if (IsInSVGTextSubtree()) { |
| 5528 | // XXX We might want to do something with text-decoration-color when |
| 5529 | // painting SVG text, but it's not clear what we should do. We |
| 5530 | // at least need SVG text decorations to paint with 'fill' if |
| 5531 | // text-decoration-color has its initial value currentColor. |
| 5532 | // We could choose to interpret currentColor as "currentFill" |
| 5533 | // for SVG text, and have e.g. text-decoration-color:red to |
| 5534 | // override the fill paint of the decoration. |
| 5535 | color = aColorResolution == eResolvedColors |
| 5536 | ? nsLayoutUtils::GetTextColor(f, &nsStyleSVG::mFill) |
| 5537 | : NS_SAME_AS_FOREGROUND_COLOR; |
| 5538 | } else { |
| 5539 | color = nsLayoutUtils::GetTextColor( |
| 5540 | f, &nsStyleTextReset::mTextDecorationColor); |
| 5541 | } |
| 5542 | |
| 5543 | bool swapUnderlineAndOverline = |
| 5544 | wm.IsCentralBaseline() && IsUnderlineRight(*context); |
| 5545 | const auto kUnderline = swapUnderlineAndOverline |
| 5546 | ? StyleTextDecorationLine::OVERLINE |
| 5547 | : StyleTextDecorationLine::UNDERLINE; |
| 5548 | const auto kOverline = swapUnderlineAndOverline |
| 5549 | ? StyleTextDecorationLine::UNDERLINE |
| 5550 | : StyleTextDecorationLine::OVERLINE; |
| 5551 | |
| 5552 | const nsStyleText* const styleText = context->StyleText(); |
| 5553 | const auto position = ignoreSubproperties |
| 5554 | ? StyleTextUnderlinePosition::AUTO |
| 5555 | : styleText->mTextUnderlinePosition; |
| 5556 | const auto offset = ignoreSubproperties ? LengthPercentageOrAuto::Auto() |
| 5557 | : styleText->mTextUnderlineOffset; |
| 5558 | const auto thickness = ignoreSubproperties |
| 5559 | ? StyleTextDecorationLength::Auto() |
| 5560 | : styleTextReset->mTextDecorationThickness; |
| 5561 | |
| 5562 | if (textDecorations & kUnderline) { |
| 5563 | aDecorations.mUnderlines.AppendElement(nsTextFrame::LineDecoration( |
| 5564 | f, baselineOffset, position, offset, thickness, color, lineStyle, |
| 5565 | !ignoreSubproperties)); |
| 5566 | } |
| 5567 | if (textDecorations & kOverline) { |
| 5568 | aDecorations.mOverlines.AppendElement(nsTextFrame::LineDecoration( |
| 5569 | f, baselineOffset, position, offset, thickness, color, lineStyle, |
| 5570 | !ignoreSubproperties)); |
| 5571 | } |
| 5572 | if (textDecorations & StyleTextDecorationLine::LINE_THROUGH) { |
| 5573 | aDecorations.mStrikes.AppendElement(nsTextFrame::LineDecoration( |
| 5574 | f, baselineOffset, position, offset, thickness, color, lineStyle, |
| 5575 | !ignoreSubproperties)); |
| 5576 | } |
| 5577 | } |
| 5578 | if (FrameStopsLineDecorationPropagation(f, compatMode)) { |
| 5579 | break; |
| 5580 | } |
| 5581 | } |
| 5582 | } |
| 5583 | |
| 5584 | static float GetInflationForTextDecorations(nsIFrame* aFrame, |
| 5585 | nscoord aInflationMinFontSize) { |
| 5586 | if (aFrame->IsInSVGTextSubtree()) { |
| 5587 | auto* container = |
| 5588 | nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText); |
| 5589 | MOZ_ASSERT(container)do { static_assert( mozilla::detail::AssertionConditionType< decltype(container)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(container))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("container", "./../../../layout/generic/nsTextFrame.cpp" , 5589); AnnotateMozCrashReason("MOZ_ASSERT" "(" "container" ")" ); do { MOZ_CrashSequence(__null, 5589); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 5590 | return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor(); |
| 5591 | } |
| 5592 | return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize); |
| 5593 | } |
| 5594 | |
| 5595 | struct EmphasisMarkInfo { |
| 5596 | RefPtr<gfxTextRun> textRun; |
| 5597 | gfxFloat advance; |
| 5598 | gfxFloat baselineOffset; |
| 5599 | }; |
| 5600 | |
| 5601 | NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty, EmphasisMarkInfo)static const mozilla::FramePropertyDescriptor<EmphasisMarkInfo >* EmphasisMarkProperty() { static const auto descriptor = mozilla::FramePropertyDescriptor<EmphasisMarkInfo>::NewWithDestructor <DeleteValue>(); return &descriptor; } |
| 5602 | |
| 5603 | static void ComputeTextEmphasisStyleString(const StyleTextEmphasisStyle& aStyle, |
| 5604 | nsAString& aOut) { |
| 5605 | MOZ_ASSERT(!aStyle.IsNone())do { static_assert( mozilla::detail::AssertionConditionType< decltype(!aStyle.IsNone())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!aStyle.IsNone()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!aStyle.IsNone()" , "./../../../layout/generic/nsTextFrame.cpp", 5605); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "!aStyle.IsNone()" ")"); do { MOZ_CrashSequence (__null, 5605); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 5606 | if (aStyle.IsString()) { |
| 5607 | nsDependentCSubstring string = aStyle.AsString().AsString(); |
| 5608 | AppendUTF8toUTF16(string, aOut); |
| 5609 | return; |
| 5610 | } |
| 5611 | const auto& keyword = aStyle.AsKeyword(); |
| 5612 | const bool fill = keyword.fill == StyleTextEmphasisFillMode::Filled; |
| 5613 | switch (keyword.shape) { |
| 5614 | case StyleTextEmphasisShapeKeyword::Dot: |
| 5615 | return aOut.AppendLiteral(fill ? u"\u2022" : u"\u25e6"); |
| 5616 | case StyleTextEmphasisShapeKeyword::Circle: |
| 5617 | return aOut.AppendLiteral(fill ? u"\u25cf" : u"\u25cb"); |
| 5618 | case StyleTextEmphasisShapeKeyword::DoubleCircle: |
| 5619 | return aOut.AppendLiteral(fill ? u"\u25c9" : u"\u25ce"); |
| 5620 | case StyleTextEmphasisShapeKeyword::Triangle: |
| 5621 | return aOut.AppendLiteral(fill ? u"\u25b2" : u"\u25b3"); |
| 5622 | case StyleTextEmphasisShapeKeyword::Sesame: |
| 5623 | return aOut.AppendLiteral(fill ? u"\ufe45" : u"\ufe46"); |
| 5624 | default: |
| 5625 | MOZ_ASSERT_UNREACHABLE("Unknown emphasis style shape")do { static_assert( mozilla::detail::AssertionConditionType< decltype(false)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: " "Unknown emphasis style shape" ")", "./../../../layout/generic/nsTextFrame.cpp" , 5625); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "Unknown emphasis style shape" ")" ); do { MOZ_CrashSequence(__null, 5625); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 5626 | } |
| 5627 | } |
| 5628 | |
| 5629 | static already_AddRefed<gfxTextRun> GenerateTextRunForEmphasisMarks( |
| 5630 | nsTextFrame* aFrame, gfxFontGroup* aFontGroup, |
| 5631 | ComputedStyle* aComputedStyle, const nsStyleText* aStyleText) { |
| 5632 | nsAutoString string; |
| 5633 | ComputeTextEmphasisStyleString(aStyleText->mTextEmphasisStyle, string); |
| 5634 | |
| 5635 | RefPtr<DrawTarget> dt = CreateReferenceDrawTarget(aFrame); |
| 5636 | auto appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel(); |
| 5637 | gfx::ShapedTextFlags flags = |
| 5638 | nsLayoutUtils::GetTextRunOrientFlagsForStyle(aComputedStyle); |
| 5639 | if (flags == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { |
| 5640 | // The emphasis marks should always be rendered upright per spec. |
| 5641 | flags = gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; |
| 5642 | } |
| 5643 | return aFontGroup->MakeTextRun<char16_t>(string.get(), string.Length(), dt, |
| 5644 | appUnitsPerDevUnit, flags, |
| 5645 | nsTextFrameUtils::Flags(), nullptr); |
| 5646 | } |
| 5647 | |
| 5648 | static nsRubyFrame* FindFurthestInlineRubyAncestor(nsTextFrame* aFrame) { |
| 5649 | nsRubyFrame* rubyFrame = nullptr; |
| 5650 | for (nsIFrame* frame = aFrame->GetParent(); |
| 5651 | frame && frame->IsLineParticipant(); frame = frame->GetParent()) { |
| 5652 | if (frame->IsRubyFrame()) { |
| 5653 | rubyFrame = static_cast<nsRubyFrame*>(frame); |
| 5654 | } |
| 5655 | } |
| 5656 | return rubyFrame; |
| 5657 | } |
| 5658 | |
| 5659 | nsRect nsTextFrame::UpdateTextEmphasis(WritingMode aWM, |
| 5660 | PropertyProvider& aProvider) { |
| 5661 | const nsStyleText* styleText = StyleText(); |
| 5662 | if (!styleText->HasEffectiveTextEmphasis()) { |
| 5663 | RemoveProperty(EmphasisMarkProperty()); |
| 5664 | return nsRect(); |
| 5665 | } |
| 5666 | |
| 5667 | ComputedStyle* computedStyle = Style(); |
| 5668 | bool isTextCombined = computedStyle->IsTextCombined(); |
| 5669 | if (isTextCombined) { |
| 5670 | computedStyle = GetParent()->Style(); |
| 5671 | } |
| 5672 | RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsOfEmphasisMarks( |
| 5673 | computedStyle, PresContext(), GetFontSizeInflation()); |
| 5674 | EmphasisMarkInfo* info = new EmphasisMarkInfo; |
| 5675 | info->textRun = GenerateTextRunForEmphasisMarks( |
| 5676 | this, fm->GetThebesFontGroup(), computedStyle, styleText); |
| 5677 | info->advance = info->textRun->GetAdvanceWidth(); |
| 5678 | |
| 5679 | bool normalizeRubyMetrics = PresContext()->NormalizeRubyMetrics(); |
| 5680 | float rubyMetricsFactor = |
| 5681 | normalizeRubyMetrics ? PresContext()->RubyPositioningFactor() : 0.0f; |
| 5682 | |
| 5683 | // Calculate the baseline offset |
| 5684 | LogicalSide side = |
| 5685 | styleText->TextEmphasisSide(aWM, StyleFont()->GetLangAtom()); |
| 5686 | LogicalSize frameSize = GetLogicalSize(aWM); |
| 5687 | // The overflow rect is inflated in the inline direction by half |
| 5688 | // advance of the emphasis mark on each side, so that even if a mark |
| 5689 | // is drawn for a zero-width character, it won't be clipped. |
| 5690 | LogicalRect overflowRect( |
| 5691 | aWM, -info->advance / 2, /* BStart to be computed below */ 0, |
| 5692 | frameSize.ISize(aWM) + info->advance, |
| 5693 | normalizeRubyMetrics |
| 5694 | ? rubyMetricsFactor * (fm->TrimmedAscent() + fm->TrimmedDescent()) |
| 5695 | : fm->MaxAscent() + fm->MaxDescent()); |
| 5696 | RefPtr<nsFontMetrics> baseFontMetrics = |
| 5697 | isTextCombined |
| 5698 | ? nsLayoutUtils::GetInflatedFontMetricsForFrame(GetParent()) |
| 5699 | : do_AddRef(aProvider.GetFontMetrics()); |
| 5700 | // When the writing mode is vertical-lr the line is inverted, and thus |
| 5701 | // the ascent and descent are swapped. |
| 5702 | bool startSideOrInvertedLine = |
| 5703 | (side == LogicalSide::BStart) != aWM.IsLineInverted(); |
| 5704 | nscoord absOffset; |
| 5705 | if (normalizeRubyMetrics) { |
| 5706 | absOffset = startSideOrInvertedLine |
| 5707 | ? baseFontMetrics->TrimmedAscent() + fm->TrimmedDescent() |
| 5708 | : baseFontMetrics->TrimmedDescent() + fm->TrimmedAscent(); |
| 5709 | absOffset *= rubyMetricsFactor; |
| 5710 | } else { |
| 5711 | absOffset = startSideOrInvertedLine |
| 5712 | ? baseFontMetrics->MaxAscent() + fm->MaxDescent() |
| 5713 | : baseFontMetrics->MaxDescent() + fm->MaxAscent(); |
| 5714 | } |
| 5715 | RubyBlockLeadings leadings; |
| 5716 | if (nsRubyFrame* ruby = FindFurthestInlineRubyAncestor(this)) { |
| 5717 | leadings = ruby->GetBlockLeadings(); |
| 5718 | if (normalizeRubyMetrics) { |
| 5719 | // Adjust absOffset to account for any ruby annotations that effectively |
| 5720 | // added to the trimmed height of the base text. |
| 5721 | auto [ascent, descent] = ruby->RubyMetrics(rubyMetricsFactor); |
| 5722 | absOffset = std::max(absOffset, side == LogicalSide::BStart |
| 5723 | ? ascent + fm->TrimmedDescent() |
| 5724 | : descent + fm->TrimmedAscent()); |
| 5725 | } |
| 5726 | } |
| 5727 | if (side == LogicalSide::BStart) { |
| 5728 | info->baselineOffset = |
| 5729 | normalizeRubyMetrics ? -absOffset : -absOffset - leadings.mStart; |
| 5730 | overflowRect.BStart(aWM) = -overflowRect.BSize(aWM) - leadings.mStart; |
| 5731 | } else { |
| 5732 | MOZ_ASSERT(side == LogicalSide::BEnd)do { static_assert( mozilla::detail::AssertionConditionType< decltype(side == LogicalSide::BEnd)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(side == LogicalSide::BEnd))) , 0))) { do { } while (false); MOZ_ReportAssertionFailure("side == LogicalSide::BEnd" , "./../../../layout/generic/nsTextFrame.cpp", 5732); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "side == LogicalSide::BEnd" ")"); do { MOZ_CrashSequence (__null, 5732); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 5733 | info->baselineOffset = |
| 5734 | normalizeRubyMetrics ? absOffset : absOffset + leadings.mEnd; |
| 5735 | overflowRect.BStart(aWM) = frameSize.BSize(aWM) + leadings.mEnd; |
| 5736 | } |
| 5737 | // If text combined, fix the gap between the text frame and its parent. |
| 5738 | if (isTextCombined) { |
| 5739 | nscoord height = |
| 5740 | normalizeRubyMetrics |
| 5741 | ? rubyMetricsFactor * (baseFontMetrics->TrimmedAscent() + |
| 5742 | baseFontMetrics->TrimmedDescent()) |
| 5743 | : baseFontMetrics->MaxHeight(); |
| 5744 | nscoord gap = (height - frameSize.BSize(aWM)) / 2; |
| 5745 | overflowRect.BStart(aWM) += gap * (side == LogicalSide::BStart ? -1 : 1); |
| 5746 | } |
| 5747 | |
| 5748 | SetProperty(EmphasisMarkProperty(), info); |
| 5749 | return overflowRect.GetPhysicalRect(aWM, frameSize.GetPhysicalSize(aWM)); |
| 5750 | } |
| 5751 | |
| 5752 | // helper function for implementing text-decoration-thickness |
| 5753 | // https://drafts.csswg.org/css-text-decor-4/#text-decoration-width-property |
| 5754 | // Returns the thickness in device pixels. |
| 5755 | static gfxFloat ComputeDecorationLineThickness( |
| 5756 | const StyleTextDecorationLength& aThickness, const gfxFloat aAutoValue, |
| 5757 | const gfxFont::Metrics& aFontMetrics, const gfxFloat aAppUnitsPerDevPixel, |
| 5758 | const nsIFrame* aFrame) { |
| 5759 | if (aThickness.IsAuto()) { |
| 5760 | return widget::ThemeDrawing::SnapBorderWidth(aAutoValue); |
| 5761 | } |
| 5762 | if (aThickness.IsFromFont()) { |
| 5763 | return widget::ThemeDrawing::SnapBorderWidth(aFontMetrics.underlineSize); |
| 5764 | } |
| 5765 | auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); }; |
| 5766 | return widget::ThemeDrawing::SnapBorderWidth( |
| 5767 | aThickness.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel); |
| 5768 | } |
| 5769 | |
| 5770 | // Helper function for implementing text-underline-offset and -position |
| 5771 | // https://drafts.csswg.org/css-text-decor-4/#underline-offset |
| 5772 | // Returns the offset in device pixels. |
| 5773 | static gfxFloat ComputeDecorationLineOffset( |
| 5774 | StyleTextDecorationLine aLineType, |
| 5775 | const StyleTextUnderlinePosition& aPosition, |
| 5776 | const LengthPercentageOrAuto& aOffset, const gfxFont::Metrics& aFontMetrics, |
| 5777 | const gfxFloat aAppUnitsPerDevPixel, const nsIFrame* aFrame, |
| 5778 | bool aIsCentralBaseline, bool aSwappedUnderline) { |
| 5779 | // Em value to use if we need to resolve a percentage length. |
| 5780 | auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); }; |
| 5781 | // If we're in vertical-upright typographic mode, we need to compute the |
| 5782 | // offset of the decoration line from the default central baseline. |
| 5783 | if (aIsCentralBaseline) { |
| 5784 | // Line-through simply goes at the (central) baseline. |
| 5785 | if (aLineType == StyleTextDecorationLine::LINE_THROUGH) { |
| 5786 | return 0; |
| 5787 | } |
| 5788 | |
| 5789 | // Compute "zero position" for the under- or overline. |
| 5790 | gfxFloat zeroPos = 0.5 * aFontMetrics.emHeight; |
| 5791 | |
| 5792 | // aOffset applies to underline only; for overline (or offset:auto) we use |
| 5793 | // a somewhat arbitrary offset of half the font's (horziontal-mode) value |
| 5794 | // for underline-offset, to get a little bit of separation between glyph |
| 5795 | // edges and the line in typical cases. |
| 5796 | // If we have swapped under-/overlines for text-underline-position:right, |
| 5797 | // we need to take account of this to determine which decoration lines are |
| 5798 | // "real" underlines which should respect the text-underline-* values. |
| 5799 | bool isUnderline = |
| 5800 | (aLineType == StyleTextDecorationLine::UNDERLINE) != aSwappedUnderline; |
| 5801 | gfxFloat offset = |
| 5802 | isUnderline && !aOffset.IsAuto() |
| 5803 | ? aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel |
| 5804 | : aFontMetrics.underlineOffset * -0.5; |
| 5805 | |
| 5806 | // Direction of the decoration line's offset from the central baseline. |
| 5807 | gfxFloat dir = aLineType == StyleTextDecorationLine::OVERLINE ? 1.0 : -1.0; |
| 5808 | return dir * (zeroPos + offset); |
| 5809 | } |
| 5810 | |
| 5811 | // Compute line offset for horizontal typographic mode. |
| 5812 | if (aLineType == StyleTextDecorationLine::UNDERLINE) { |
| 5813 | if (aPosition.IsFromFont()) { |
| 5814 | gfxFloat zeroPos = aFontMetrics.underlineOffset; |
| 5815 | gfxFloat offset = |
| 5816 | aOffset.IsAuto() |
| 5817 | ? 0 |
| 5818 | : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel; |
| 5819 | return zeroPos - offset; |
| 5820 | } |
| 5821 | |
| 5822 | if (aPosition.IsUnder()) { |
| 5823 | gfxFloat zeroPos = -aFontMetrics.maxDescent; |
| 5824 | gfxFloat offset = |
| 5825 | aOffset.IsAuto() |
| 5826 | ? -0.5 * aFontMetrics.underlineOffset |
| 5827 | : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel; |
| 5828 | return zeroPos - offset; |
| 5829 | } |
| 5830 | |
| 5831 | // text-underline-position must be 'auto', so zero position is the |
| 5832 | // baseline and 'auto' offset will apply the font's underline-offset. |
| 5833 | // |
| 5834 | // If offset is `auto`, we clamp the offset (in horizontal typographic mode) |
| 5835 | // to a minimum of 1/16 em (equivalent to 1px at font-size 16px) to mitigate |
| 5836 | // skip-ink issues with fonts that leave the underlineOffset field as zero. |
| 5837 | MOZ_ASSERT(aPosition.IsAuto())do { static_assert( mozilla::detail::AssertionConditionType< decltype(aPosition.IsAuto())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aPosition.IsAuto()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aPosition.IsAuto()" , "./../../../layout/generic/nsTextFrame.cpp", 5837); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aPosition.IsAuto()" ")"); do { MOZ_CrashSequence (__null, 5837); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 5838 | return aOffset.IsAuto() ? std::min(aFontMetrics.underlineOffset, |
| 5839 | -aFontMetrics.emHeight / 16.0) |
| 5840 | : -aOffset.AsLengthPercentage().Resolve(em) / |
| 5841 | aAppUnitsPerDevPixel; |
| 5842 | } |
| 5843 | |
| 5844 | if (aLineType == StyleTextDecorationLine::OVERLINE) { |
| 5845 | return aFontMetrics.maxAscent; |
| 5846 | } |
| 5847 | |
| 5848 | if (aLineType == StyleTextDecorationLine::LINE_THROUGH) { |
| 5849 | return aFontMetrics.strikeoutOffset; |
| 5850 | } |
| 5851 | |
| 5852 | MOZ_ASSERT_UNREACHABLE("unknown decoration line type")do { static_assert( mozilla::detail::AssertionConditionType< decltype(false)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: " "unknown decoration line type" ")", "./../../../layout/generic/nsTextFrame.cpp" , 5852); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "unknown decoration line type" ")" ); do { MOZ_CrashSequence(__null, 5852); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 5853 | return 0; |
| 5854 | } |
| 5855 | |
| 5856 | // Per the spec resolution, if it's split (i.e. box-decoration-break:slice) we |
| 5857 | // count the sum width (or height) of the fragments, but if it is with clone, |
| 5858 | // each fragment is considered individually. |
| 5859 | // https://github.com/w3c/csswg-drafts/issues/8403 |
| 5860 | static nscoord TextDecorationInsetPercentageBasis(const nsTextFrame* aFrame, |
| 5861 | const nsIFrame* aDecFrame) { |
| 5862 | // Note: We use the same way to get the writing mode in |
| 5863 | // ComputeDecorationInset(). |
| 5864 | const WritingMode wm = aDecFrame->IsInlineFrame() |
| 5865 | ? aDecFrame->GetWritingMode() |
| 5866 | : FindLineContainer(aFrame)->GetWritingMode(); |
| 5867 | auto getLength = [wm](const nsIFrame* aFrame) { |
| 5868 | return wm.IsVertical() ? aFrame->GetRectRelativeToSelf().height |
| 5869 | : aFrame->GetRectRelativeToSelf().width; |
| 5870 | }; |
| 5871 | if (aDecFrame->StyleBorder()->mBoxDecorationBreak == |
| 5872 | StyleBoxDecorationBreak::Clone) { |
| 5873 | return getLength(aFrame); |
| 5874 | } |
| 5875 | |
| 5876 | // FIXME: This is not great because we may calculate this multiple times if |
| 5877 | // there are a lot of fragments. The better way is to move this calculation |
| 5878 | // out of ComputeDecorationInset(). |
| 5879 | // |
| 5880 | // FIXME: Bug 2050962. We have to add the extra handling if we break the text |
| 5881 | // by `<br>`. It breaks the continuations and it may look like multiple |
| 5882 | // independent text frames, so we cannot use continuation to find all |
| 5883 | // fragments. |
| 5884 | nscoord sum = getLength(aFrame); |
| 5885 | const nsIFrame* prev = aFrame; |
| 5886 | while ((prev = nsLayoutUtils::GetPrevContinuationOrIBSplitSibling(prev))) { |
| 5887 | sum += getLength(prev); |
| 5888 | } |
| 5889 | const nsIFrame* next = aFrame; |
| 5890 | while ((next = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(next))) { |
| 5891 | sum += getLength(next); |
| 5892 | } |
| 5893 | return sum; |
| 5894 | } |
| 5895 | |
| 5896 | // Helper to determine decoration inset. |
| 5897 | // Returns false if the inset would cut off the decoration entirely. |
| 5898 | // If aOnlyExtend is true, this will only consider cases with negative inset |
| 5899 | // (i.e. the line will actually be extended beyond the normal length). |
| 5900 | static bool ComputeDecorationInset( |
| 5901 | nsTextFrame* aFrame, const nsPresContext* aPresCtx, |
| 5902 | const nsIFrame* aDecFrame, const gfxFont::Metrics& aMetrics, |
| 5903 | nsCSSRendering::DecorationRectParams& aParams, bool aOnlyExtend = false) { |
| 5904 | aParams.insetLeft = 0.0; |
| 5905 | aParams.insetRight = 0.0; |
| 5906 | |
| 5907 | // Find the decoration-line inset values for this frame. |
| 5908 | const StyleTextDecorationInset& cssInset = |
| 5909 | aDecFrame->StyleTextReset()->mTextDecorationInset; |
| 5910 | nscoord insetLeft, insetRight; |
| 5911 | if (cssInset.IsAuto()) { |
| 5912 | const float emSize = |
| 5913 | aMetrics.emHeight / aPresCtx->CSSToDevPixelScale().scale; |
| 5914 | const nscoord autoDecorationInset = nsPresContext::CSSPixelsToAppUnits( |
| 5915 | Gecko_CalcAutoDecorationInset(emSize)); |
| 5916 | insetLeft = autoDecorationInset; |
| 5917 | insetRight = autoDecorationInset; |
| 5918 | } else { |
| 5919 | MOZ_ASSERT(cssInset.IsLengthPercentage(),do { static_assert( mozilla::detail::AssertionConditionType< decltype(cssInset.IsLengthPercentage())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(cssInset.IsLengthPercentage( )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("cssInset.IsLengthPercentage()" " (" "Impossible text-decoration-inset" ")", "./../../../layout/generic/nsTextFrame.cpp", 5920); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "cssInset.IsLengthPercentage()" ") (" "Impossible text-decoration-inset" ")"); do { MOZ_CrashSequence(__null, 5920); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
| 5920 | "Impossible text-decoration-inset")do { static_assert( mozilla::detail::AssertionConditionType< decltype(cssInset.IsLengthPercentage())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(cssInset.IsLengthPercentage( )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("cssInset.IsLengthPercentage()" " (" "Impossible text-decoration-inset" ")", "./../../../layout/generic/nsTextFrame.cpp", 5920); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "cssInset.IsLengthPercentage()" ") (" "Impossible text-decoration-inset" ")"); do { MOZ_CrashSequence(__null, 5920); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 5921 | const auto& inset = cssInset.AsLengthPercentage(); |
| 5922 | if (inset.start.IsDefinitelyZero() && inset.end.IsDefinitelyZero()) { |
| 5923 | // We can avoid doing the geometric calculations below, potentially |
| 5924 | // walking up and back down the frame tree, and walking continuations. |
| 5925 | return true; |
| 5926 | } |
| 5927 | |
| 5928 | if (inset.start.IsLength() && inset.end.IsLength()) { |
| 5929 | insetLeft = inset.start.AsLength().ToAppUnits(); |
| 5930 | insetRight = inset.end.AsLength().ToAppUnits(); |
| 5931 | } else { |
| 5932 | const nscoord basis = |
| 5933 | TextDecorationInsetPercentageBasis(aFrame, aDecFrame); |
| 5934 | insetLeft = inset.start.Resolve(basis); |
| 5935 | insetRight = inset.end.Resolve(basis); |
| 5936 | if (!insetLeft && !insetRight) { |
| 5937 | // Same as our previous check. If both are zero, we can bail out. |
| 5938 | return true; |
| 5939 | } |
| 5940 | } |
| 5941 | } |
| 5942 | |
| 5943 | // If we only care about extended lines (for UnionAdditionalOverflow), |
| 5944 | // we can bail out if neither inset value is negative. |
| 5945 | if (aOnlyExtend && insetLeft >= 0 && insetRight >= 0) { |
| 5946 | return true; |
| 5947 | } |
| 5948 | |
| 5949 | // The rect of the decorating box (if an inline) or of the current line (if |
| 5950 | // the decoration is propagated from a block ancestor). We will need to |
| 5951 | // compare this with the rect of the current frame, which may be only a |
| 5952 | // sub-range of the entire decorated range. |
| 5953 | nsRect decRect; |
| 5954 | |
| 5955 | // The container of the decoration, or the fragment of it on this line. |
| 5956 | const nsIFrame* decContainer; |
| 5957 | |
| 5958 | // If the decorating frame is an inline frame, we can use it as the |
| 5959 | // reference frame for measurements. |
| 5960 | // If the decorating frame is not inline, then we will need to consider |
| 5961 | // text indentation and calculate geometry using line boxes. |
| 5962 | WritingMode wm; |
| 5963 | if (aDecFrame->IsInlineFrame()) { |
| 5964 | decRect = aDecFrame->GetContentRectRelativeToSelf(); |
| 5965 | decContainer = aDecFrame; |
| 5966 | wm = aDecFrame->GetWritingMode(); |
| 5967 | } else { |
| 5968 | nsIFrame* const lineContainer = FindLineContainer(aFrame); |
| 5969 | wm = lineContainer->GetWritingMode(); |
| 5970 | if (nsILineIterator* const iter = lineContainer->GetLineIterator()) { |
| 5971 | const int32_t lineNum = GetFrameLineNum(aFrame, iter); |
| 5972 | const nsILineIterator::LineInfo lineInfo = |
| 5973 | iter->GetLine(lineNum).unwrap(); |
| 5974 | decRect = lineInfo.mLineBounds; |
| 5975 | |
| 5976 | // Account for text-indent, which will push text frames into the line box. |
| 5977 | const StyleTextIndent& textIndent = aFrame->StyleText()->mTextIndent; |
| 5978 | if (!textIndent.length.IsDefinitelyZero()) { |
| 5979 | bool isFirstLineOrAfterHardBreak = true; |
| 5980 | if (lineNum > 0 && !textIndent.each_line) { |
| 5981 | isFirstLineOrAfterHardBreak = false; |
| 5982 | } else if (nsBlockFrame* prevBlock = |
| 5983 | do_QueryFrame(lineContainer->GetPrevInFlow())) { |
| 5984 | if (!(textIndent.each_line && |
| 5985 | (prevBlock->Lines().empty() || |
| 5986 | !prevBlock->LinesEnd().prev()->IsLineWrapped()))) { |
| 5987 | isFirstLineOrAfterHardBreak = false; |
| 5988 | } |
| 5989 | } |
| 5990 | if (isFirstLineOrAfterHardBreak != textIndent.hanging) { |
| 5991 | // Determine which side to shrink. |
| 5992 | const Side side = wm.PhysicalSide(LogicalSide::IStart); |
| 5993 | // Calculate the text indent, and shrink the line box by this amount |
| 5994 | // to account for the indent size at the start of the line. |
| 5995 | const nscoord basis = lineContainer->GetLogicalSize(wm).ISize(wm); |
| 5996 | nsMargin indentMargin; |
| 5997 | indentMargin.Side(side) = textIndent.length.Resolve(basis); |
| 5998 | decRect.Deflate(indentMargin); |
| 5999 | } |
| 6000 | } |
| 6001 | |
| 6002 | // We can't allow a block frame to retain a line iterator if we're |
| 6003 | // currently in reflow, as it will become invalid as the line list is |
| 6004 | // reflowed. |
| 6005 | if (lineContainer->HasAnyStateBits(NS_FRAME_IN_REFLOW) && |
| 6006 | lineContainer->IsBlockFrameOrSubclass()) { |
| 6007 | static_cast<nsBlockFrame*>(lineContainer)->ClearLineIterator(); |
| 6008 | } |
| 6009 | } else { |
| 6010 | // Not a block or similar container with multiple lines; just use the |
| 6011 | // content rect directly. |
| 6012 | decRect = lineContainer->GetContentRectRelativeToSelf(); |
| 6013 | } |
| 6014 | |
| 6015 | decContainer = lineContainer; |
| 6016 | } |
| 6017 | |
| 6018 | // The rect of the current frame, mapped to the same coordinate space as |
| 6019 | // decRect so that we can compare their edges. |
| 6020 | const nsRect frameRect = |
| 6021 | aFrame->GetRectRelativeToSelf() + aFrame->GetOffsetTo(decContainer); |
| 6022 | |
| 6023 | // The nominal size of the decoration (prior to insets being applied) is |
| 6024 | // reduced by any margin, border, and padding present on frames intervening |
| 6025 | // between aFrame and decContainer. |
| 6026 | for (const nsIFrame* parent = aFrame->GetParent(); parent != decContainer; |
| 6027 | parent = parent->GetParent()) { |
| 6028 | decRect.Deflate(parent->GetUsedMargin()); |
| 6029 | decRect.Deflate(parent->GetUsedBorderAndPadding()); |
| 6030 | } |
| 6031 | |
| 6032 | // Find the margin of the of this frame inside its container. |
| 6033 | nscoord marginLeft, marginRight, frameSize; |
| 6034 | const nsMargin difference = decRect - frameRect; |
| 6035 | if (wm.IsVertical()) { |
| 6036 | marginLeft = difference.top; |
| 6037 | marginRight = difference.bottom; |
| 6038 | frameSize = frameRect.height; |
| 6039 | } else { |
| 6040 | marginLeft = difference.left; |
| 6041 | marginRight = difference.right; |
| 6042 | frameSize = frameRect.width; |
| 6043 | } |
| 6044 | |
| 6045 | const bool cloneDecBreak = aDecFrame->StyleBorder()->mBoxDecorationBreak == |
| 6046 | StyleBoxDecorationBreak::Clone; |
| 6047 | // TODO alaskanemily: This will not correctly account for the case that the |
| 6048 | // continuations are bidi continuations. |
| 6049 | bool applyLeft = cloneDecBreak || (!aFrame->GetPrevContinuation() && |
| 6050 | !aDecFrame->GetPrevContinuation()); |
| 6051 | bool applyRight = cloneDecBreak || (!aFrame->GetNextContinuation() && |
| 6052 | !aDecFrame->GetNextContinuation()); |
| 6053 | if (wm.IsInlineReversed()) { |
| 6054 | std::swap(insetLeft, insetRight); |
| 6055 | std::swap(applyLeft, applyRight); |
| 6056 | } |
| 6057 | if (applyLeft) { |
| 6058 | insetLeft -= marginLeft; |
| 6059 | } else { |
| 6060 | insetLeft = 0; |
| 6061 | } |
| 6062 | if (applyRight) { |
| 6063 | insetRight -= marginRight; |
| 6064 | } else { |
| 6065 | insetRight = 0; |
| 6066 | } |
| 6067 | |
| 6068 | if (insetLeft + insetRight >= frameSize) { |
| 6069 | // This frame does not contain the decoration at all. |
| 6070 | return false; |
| 6071 | } |
| 6072 | // TODO alaskanemily: We currently determine if we should have a negative |
| 6073 | // inset value by checking if we are at the edge of frame from which the |
| 6074 | // decloration comes from. |
| 6075 | // |
| 6076 | // This is not absolutely correct, there could in theory be a zero-width |
| 6077 | // frame before/after this frame, and we will draw the decoration extension |
| 6078 | // twice (causing a visible inaccuracy for semi-transparent decorations). |
| 6079 | // |
| 6080 | // I am unsure if it's possible that the first/last frame might be inset |
| 6081 | // for some reason, as well, in which case we will not draw the outset |
| 6082 | // decorations. |
| 6083 | if (insetLeft > 0 || marginLeft == 0) { |
| 6084 | aParams.insetLeft = aPresCtx->AppUnitsToFloatDevPixels(insetLeft); |
| 6085 | } |
| 6086 | if (insetRight > 0 || marginRight == 0) { |
| 6087 | aParams.insetRight = aPresCtx->AppUnitsToFloatDevPixels(insetRight); |
| 6088 | } |
| 6089 | return true; |
| 6090 | } |
| 6091 | |
| 6092 | void nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext, |
| 6093 | nsIFrame* aBlock, |
| 6094 | PropertyProvider& aProvider, |
| 6095 | nsRect* aInkOverflowRect, |
| 6096 | bool aIncludeTextDecorations, |
| 6097 | bool aIncludeShadows) { |
| 6098 | const WritingMode wm = GetWritingMode(); |
| 6099 | bool verticalRun = mTextRun->IsVertical(); |
| 6100 | const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(); |
| 6101 | |
| 6102 | if (IsFloatingFirstLetterChild()) { |
| 6103 | bool inverted = wm.IsLineInverted(); |
| 6104 | // The underline/overline drawable area must be contained in the overflow |
| 6105 | // rect when this is in floating first letter frame at *both* modes. |
| 6106 | // In this case, aBlock is the ::first-letter frame. |
| 6107 | auto decorationStyle = |
| 6108 | aBlock->Style()->StyleTextReset()->mTextDecorationStyle; |
| 6109 | // If the style is none, let's include decoration line rect as solid style |
| 6110 | // since changing the style from none to solid/dotted/dashed doesn't cause |
| 6111 | // reflow. |
| 6112 | if (decorationStyle == StyleTextDecorationStyle::None) { |
| 6113 | decorationStyle = StyleTextDecorationStyle::Solid; |
| 6114 | } |
| 6115 | nsCSSRendering::DecorationRectParams params; |
| 6116 | bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline(); |
| 6117 | nsFontMetrics* fontMetrics = aProvider.GetFontMetrics(); |
| 6118 | RefPtr<gfxFont> font = |
| 6119 | fontMetrics->GetThebesFontGroup()->GetFirstValidFont(); |
| 6120 | const gfxFont::Metrics& metrics = |
| 6121 | font->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical |
| 6122 | : nsFontMetrics::eHorizontal); |
| 6123 | |
| 6124 | params.defaultLineThickness = metrics.underlineSize; |
| 6125 | params.lineSize.height = ComputeDecorationLineThickness( |
| 6126 | aBlock->Style()->StyleTextReset()->mTextDecorationThickness, |
| 6127 | params.defaultLineThickness, metrics, appUnitsPerDevUnit, this); |
| 6128 | |
| 6129 | const auto* styleText = aBlock->StyleText(); |
| 6130 | bool swapUnderline = |
| 6131 | wm.IsCentralBaseline() && IsUnderlineRight(*aBlock->Style()); |
| 6132 | params.offset = ComputeDecorationLineOffset( |
| 6133 | StyleTextDecorationLine::UNDERLINE, styleText->mTextUnderlinePosition, |
| 6134 | styleText->mTextUnderlineOffset, metrics, appUnitsPerDevUnit, this, |
| 6135 | wm.IsCentralBaseline(), swapUnderline); |
| 6136 | |
| 6137 | nscoord maxAscent = |
| 6138 | inverted ? fontMetrics->MaxDescent() : fontMetrics->MaxAscent(); |
| 6139 | |
| 6140 | Float gfxWidth = |
| 6141 | (verticalRun ? aInkOverflowRect->height : aInkOverflowRect->width) / |
| 6142 | appUnitsPerDevUnit; |
| 6143 | params.lineSize.width = gfxWidth; |
| 6144 | params.ascent = gfxFloat(mAscent) / appUnitsPerDevUnit; |
| 6145 | params.style = decorationStyle; |
| 6146 | params.vertical = verticalRun; |
| 6147 | params.sidewaysLeft = mTextRun->IsSidewaysLeft(); |
| 6148 | params.decoration = StyleTextDecorationLine::UNDERLINE; |
| 6149 | nsRect underlineRect = |
| 6150 | nsCSSRendering::GetTextDecorationRect(aPresContext, params); |
| 6151 | |
| 6152 | // TODO(jfkthame): |
| 6153 | // Should we actually be calling ComputeDecorationLineOffset again here? |
| 6154 | params.offset = maxAscent / appUnitsPerDevUnit; |
| 6155 | params.decoration = StyleTextDecorationLine::OVERLINE; |
| 6156 | nsRect overlineRect = |
| 6157 | nsCSSRendering::GetTextDecorationRect(aPresContext, params); |
| 6158 | |
| 6159 | aInkOverflowRect->UnionRect(*aInkOverflowRect, underlineRect); |
| 6160 | aInkOverflowRect->UnionRect(*aInkOverflowRect, overlineRect); |
| 6161 | |
| 6162 | // XXX If strikeoutSize is much thicker than the underlineSize, it may |
| 6163 | // cause overflowing from the overflow rect. However, such case |
| 6164 | // isn't realistic, we don't need to compute it now. |
| 6165 | } |
| 6166 | if (aIncludeTextDecorations) { |
| 6167 | // Use writing mode of parent frame for orthogonal text frame to |
| 6168 | // work. See comment in nsTextFrame::DrawTextRunAndDecorations. |
| 6169 | WritingMode parentWM = GetParent()->GetWritingMode(); |
| 6170 | bool verticalDec = parentWM.IsVertical(); |
| 6171 | bool useVerticalMetrics = |
| 6172 | verticalDec != verticalRun |
| 6173 | ? verticalDec |
| 6174 | : verticalRun && mTextRun->UseCenterBaseline(); |
| 6175 | |
| 6176 | // Since CSS 2.1 requires that text-decoration defined on ancestors maintain |
| 6177 | // style and position, they can be drawn at virtually any y-offset, so |
| 6178 | // maxima and minima are required to reliably generate the rectangle for |
| 6179 | // them |
| 6180 | TextDecorations textDecs; |
| 6181 | GetTextDecorations(aPresContext, eResolvedColors, textDecs); |
| 6182 | if (textDecs.HasDecorationLines()) { |
| 6183 | nscoord inflationMinFontSize = |
| 6184 | nsLayoutUtils::InflationMinFontSizeFor(aBlock); |
| 6185 | |
| 6186 | const nscoord measure = verticalDec ? GetSize().height : GetSize().width; |
| 6187 | gfxFloat gfxWidth = measure / appUnitsPerDevUnit; |
| 6188 | gfxFloat ascent = |
| 6189 | gfxFloat(GetLogicalBaseline(parentWM)) / appUnitsPerDevUnit; |
| 6190 | nscoord frameBStart = 0; |
| 6191 | if (parentWM.IsVerticalRL()) { |
| 6192 | frameBStart = GetSize().width; |
| 6193 | ascent = -ascent; |
| 6194 | } |
| 6195 | |
| 6196 | nsCSSRendering::DecorationRectParams params; |
| 6197 | params.lineSize = Size(gfxWidth, 0); |
| 6198 | params.ascent = ascent; |
| 6199 | params.vertical = verticalDec; |
| 6200 | params.sidewaysLeft = mTextRun->IsSidewaysLeft(); |
| 6201 | |
| 6202 | typedef gfxFont::Metrics Metrics; |
| 6203 | auto accumulateDecorationRect = |
| 6204 | [&](const LineDecoration& dec, gfxFloat Metrics::* lineSize, |
| 6205 | mozilla::StyleTextDecorationLine lineType) { |
| 6206 | params.style = dec.mStyle; |
| 6207 | // If the style is solid, let's include decoration line rect of |
| 6208 | // solid style since changing the style from none to |
| 6209 | // solid/dotted/dashed doesn't cause reflow. |
| 6210 | if (params.style == StyleTextDecorationStyle::None) { |
| 6211 | params.style = StyleTextDecorationStyle::Solid; |
| 6212 | } |
| 6213 | |
| 6214 | float inflation = GetInflationForTextDecorations( |
| 6215 | dec.mFrame, inflationMinFontSize); |
| 6216 | const Metrics metrics = |
| 6217 | GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation), |
| 6218 | useVerticalMetrics); |
| 6219 | |
| 6220 | params.defaultLineThickness = metrics.*lineSize; |
| 6221 | params.lineSize.height = ComputeDecorationLineThickness( |
| 6222 | dec.mTextDecorationThickness, params.defaultLineThickness, |
| 6223 | metrics, appUnitsPerDevUnit, this); |
| 6224 | |
| 6225 | bool swapUnderline = |
| 6226 | parentWM.IsCentralBaseline() && IsUnderlineRight(*Style()); |
| 6227 | params.offset = ComputeDecorationLineOffset( |
| 6228 | lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset, |
| 6229 | metrics, appUnitsPerDevUnit, this, parentWM.IsCentralBaseline(), |
| 6230 | swapUnderline); |
| 6231 | |
| 6232 | if (!ComputeDecorationInset(this, aPresContext, dec.mFrame, metrics, |
| 6233 | params, /* aOnlyExtend = */ true)) { |
| 6234 | return; |
| 6235 | } |
| 6236 | |
| 6237 | nsRect decorationRect = |
| 6238 | nsCSSRendering::GetTextDecorationRect(aPresContext, params) + |
| 6239 | (verticalDec ? nsPoint(frameBStart - dec.mBaselineOffset, 0) |
| 6240 | : nsPoint(0, -dec.mBaselineOffset)); |
| 6241 | |
| 6242 | aInkOverflowRect->UnionRect(*aInkOverflowRect, decorationRect); |
| 6243 | }; |
| 6244 | |
| 6245 | // Below we loop through all text decorations and compute the rectangle |
| 6246 | // containing all of them, in this frame's coordinate space |
| 6247 | params.decoration = StyleTextDecorationLine::UNDERLINE; |
| 6248 | for (const LineDecoration& dec : textDecs.mUnderlines) { |
| 6249 | accumulateDecorationRect(dec, &Metrics::underlineSize, |
| 6250 | params.decoration); |
| 6251 | } |
| 6252 | params.decoration = StyleTextDecorationLine::OVERLINE; |
| 6253 | for (const LineDecoration& dec : textDecs.mOverlines) { |
| 6254 | accumulateDecorationRect(dec, &Metrics::underlineSize, |
| 6255 | params.decoration); |
| 6256 | } |
| 6257 | params.decoration = StyleTextDecorationLine::LINE_THROUGH; |
| 6258 | for (const LineDecoration& dec : textDecs.mStrikes) { |
| 6259 | accumulateDecorationRect(dec, &Metrics::strikeoutSize, |
| 6260 | params.decoration); |
| 6261 | } |
| 6262 | } |
| 6263 | |
| 6264 | aInkOverflowRect->UnionRect(*aInkOverflowRect, |
| 6265 | UpdateTextEmphasis(parentWM, aProvider)); |
| 6266 | } |
| 6267 | |
| 6268 | // text-stroke overflows: add half of text-stroke-width on all sides |
| 6269 | nscoord textStrokeWidth = StyleText()->mWebkitTextStrokeWidth; |
| 6270 | if (textStrokeWidth > 0) { |
| 6271 | // Inflate rect by stroke-width/2; we add an extra pixel to allow for |
| 6272 | // antialiasing, rounding errors, etc. |
| 6273 | nsRect strokeRect = *aInkOverflowRect; |
| 6274 | strokeRect.Inflate(textStrokeWidth / 2 + appUnitsPerDevUnit); |
| 6275 | aInkOverflowRect->UnionRect(*aInkOverflowRect, strokeRect); |
| 6276 | } |
| 6277 | |
| 6278 | // Text-shadow overflows |
| 6279 | if (aIncludeShadows) { |
| 6280 | *aInkOverflowRect = |
| 6281 | nsLayoutUtils::GetTextShadowRectsUnion(*aInkOverflowRect, this); |
| 6282 | } |
| 6283 | |
| 6284 | // When this frame is not selected, the text-decoration area must be in |
| 6285 | // frame bounds. |
| 6286 | if (!IsSelected() || |
| 6287 | !CombineSelectionUnderlineRect(aPresContext, *aInkOverflowRect)) { |
| 6288 | return; |
| 6289 | } |
| 6290 | AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED); |
| 6291 | } |
| 6292 | |
| 6293 | nscoord nsTextFrame::ComputeLineHeight() const { |
| 6294 | return ReflowInput::CalcLineHeight(*Style(), PresContext(), GetContent(), |
| 6295 | GetFontSizeInflation()); |
| 6296 | } |
| 6297 | |
| 6298 | gfxFloat nsTextFrame::ComputeDescentLimitForSelectionUnderline( |
| 6299 | nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics) { |
| 6300 | const gfxFloat lineHeight = |
| 6301 | gfxFloat(ComputeLineHeight()) / aPresContext->AppUnitsPerDevPixel(); |
| 6302 | if (lineHeight <= aFontMetrics.maxHeight) { |
| 6303 | return aFontMetrics.maxDescent; |
| 6304 | } |
| 6305 | return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2; |
| 6306 | } |
| 6307 | |
| 6308 | // Make sure this stays in sync with DrawSelectionDecorations below |
| 6309 | static constexpr SelectionTypeMask kSelectionTypesWithDecorations = |
| 6310 | ToSelectionTypeMask(SelectionType::eNormal) | |
| 6311 | ToSelectionTypeMask(SelectionType::eTargetText) | |
| 6312 | ToSelectionTypeMask(SelectionType::eHighlight) | |
| 6313 | ToSelectionTypeMask(SelectionType::eSpellCheck) | |
| 6314 | ToSelectionTypeMask(SelectionType::eURLStrikeout) | |
| 6315 | ToSelectionTypeMask(SelectionType::eIMERawClause) | |
| 6316 | ToSelectionTypeMask(SelectionType::eIMESelectedRawClause) | |
| 6317 | ToSelectionTypeMask(SelectionType::eIMEConvertedClause) | |
| 6318 | ToSelectionTypeMask(SelectionType::eIMESelectedClause); |
| 6319 | |
| 6320 | /* static */ |
| 6321 | gfxFloat nsTextFrame::ComputeSelectionUnderlineHeight( |
| 6322 | nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics, |
| 6323 | SelectionType aSelectionType) { |
| 6324 | switch (aSelectionType) { |
| 6325 | case SelectionType::eNormal: |
| 6326 | case SelectionType::eTargetText: |
| 6327 | case SelectionType::eHighlight: |
| 6328 | case SelectionType::eIMERawClause: |
| 6329 | case SelectionType::eIMESelectedRawClause: |
| 6330 | case SelectionType::eIMEConvertedClause: |
| 6331 | case SelectionType::eIMESelectedClause: |
| 6332 | return aFontMetrics.underlineSize; |
| 6333 | case SelectionType::eSpellCheck: { |
| 6334 | // The thickness of the spellchecker underline shouldn't honor the font |
| 6335 | // metrics. It should be constant pixels value which is decided from the |
| 6336 | // default font size. Note that if the actual font size is smaller than |
| 6337 | // the default font size, we should use the actual font size because the |
| 6338 | // computed value from the default font size can be too thick for the |
| 6339 | // current font size. |
| 6340 | Length defaultFontSize = |
| 6341 | aPresContext->Document() |
| 6342 | ->GetFontPrefsForLang(nullptr) |
| 6343 | ->GetDefaultFont(StyleGenericFontFamily::None) |
| 6344 | ->size; |
| 6345 | int32_t zoomedFontSize = aPresContext->CSSPixelsToDevPixels( |
| 6346 | nsStyleFont::ZoomText(*aPresContext->Document(), defaultFontSize) |
| 6347 | .ToCSSPixels()); |
| 6348 | gfxFloat fontSize = |
| 6349 | std::min(gfxFloat(zoomedFontSize), aFontMetrics.emHeight); |
| 6350 | fontSize = std::max(fontSize, 1.0); |
| 6351 | return ceil(fontSize / 20); |
| 6352 | } |
| 6353 | default: |
| 6354 | NS_WARNING("Requested underline style is not valid")NS_DebugBreak(NS_DEBUG_WARNING, "Requested underline style is not valid" , nullptr, "./../../../layout/generic/nsTextFrame.cpp", 6354); |
| 6355 | return aFontMetrics.underlineSize; |
| 6356 | } |
| 6357 | } |
| 6358 | |
| 6359 | enum class DecorationType { Normal, Selection }; |
| 6360 | struct nsTextFrame::PaintDecorationLineParams |
| 6361 | : nsCSSRendering::DecorationRectParams { |
| 6362 | gfxContext* context = nullptr; |
| 6363 | LayoutDeviceRect dirtyRect; |
| 6364 | Point pt; |
| 6365 | const nscolor* overrideColor = nullptr; |
| 6366 | nscolor color = NS_RGBA(0, 0, 0, 0)((nscolor)(((0) << 24) | ((0) << 16) | ((0) << 8) | (0))); |
| 6367 | gfxFloat icoordInFrame = 0.0f; |
| 6368 | gfxFloat baselineOffset = 0.0f; |
| 6369 | DecorationType decorationType = DecorationType::Normal; |
| 6370 | DrawPathCallbacks* callbacks = nullptr; |
| 6371 | bool paintingShadows = false; |
| 6372 | bool allowInkSkipping = true; |
| 6373 | StyleTextDecorationSkipInk skipInk = StyleTextDecorationSkipInk::None; |
| 6374 | }; |
| 6375 | |
| 6376 | void nsTextFrame::PaintDecorationLine( |
| 6377 | const PaintDecorationLineParams& aParams) { |
| 6378 | nsCSSRendering::PaintDecorationLineParams params; |
| 6379 | static_cast<nsCSSRendering::DecorationRectParams&>(params) = aParams; |
| 6380 | params.dirtyRect = aParams.dirtyRect.ToUnknownRect(); |
| 6381 | params.pt = aParams.pt; |
| 6382 | params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color; |
| 6383 | params.icoordInFrame = Float(aParams.icoordInFrame); |
| 6384 | params.baselineOffset = Float(aParams.baselineOffset); |
| 6385 | // Disable ink-skipping for frames with text-combine-upright. |
| 6386 | params.allowInkSkipping = |
| 6387 | aParams.allowInkSkipping && !Style()->IsTextCombined(); |
| 6388 | params.skipInk = aParams.skipInk; |
| 6389 | if (aParams.callbacks) { |
| 6390 | Rect path = nsCSSRendering::DecorationLineToPath(params); |
| 6391 | if (aParams.decorationType == DecorationType::Normal) { |
| 6392 | aParams.callbacks->PaintDecorationLine(path, aParams.paintingShadows, |
| 6393 | params.color); |
| 6394 | } else { |
| 6395 | aParams.callbacks->PaintSelectionDecorationLine( |
| 6396 | path, aParams.paintingShadows, params.color); |
| 6397 | } |
| 6398 | } else { |
| 6399 | nsCSSRendering::PaintDecorationLine(this, *aParams.context->GetDrawTarget(), |
| 6400 | params); |
| 6401 | } |
| 6402 | } |
| 6403 | |
| 6404 | static StyleTextDecorationStyle ToStyleLineStyle(const TextRangeStyle& aStyle) { |
| 6405 | switch (aStyle.mLineStyle) { |
| 6406 | case TextRangeStyle::LineStyle::None: |
| 6407 | return StyleTextDecorationStyle::None; |
| 6408 | case TextRangeStyle::LineStyle::Solid: |
| 6409 | return StyleTextDecorationStyle::Solid; |
| 6410 | case TextRangeStyle::LineStyle::Dotted: |
| 6411 | return StyleTextDecorationStyle::Dotted; |
| 6412 | case TextRangeStyle::LineStyle::Dashed: |
| 6413 | return StyleTextDecorationStyle::Dashed; |
| 6414 | case TextRangeStyle::LineStyle::Double: |
| 6415 | return StyleTextDecorationStyle::Double; |
| 6416 | case TextRangeStyle::LineStyle::Wavy: |
| 6417 | return StyleTextDecorationStyle::Wavy; |
| 6418 | } |
| 6419 | MOZ_ASSERT_UNREACHABLE("Invalid line style")do { static_assert( mozilla::detail::AssertionConditionType< decltype(false)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: " "Invalid line style" ")", "./../../../layout/generic/nsTextFrame.cpp" , 6419); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "Invalid line style" ")"); do { MOZ_CrashSequence (__null, 6419); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 6420 | return StyleTextDecorationStyle::None; |
| 6421 | } |
| 6422 | |
| 6423 | /** |
| 6424 | * This, plus kSelectionTypesWithDecorations, encapsulates all knowledge |
| 6425 | * about drawing text decoration for selections. |
| 6426 | */ |
| 6427 | void nsTextFrame::DrawSelectionDecorations( |
| 6428 | gfxContext* aContext, const LayoutDeviceRect& aDirtyRect, |
| 6429 | SelectionType aSelectionType, nsAtom* aHighlightName, |
| 6430 | nsTextPaintStyle& aTextPaintStyle, const TextRangeStyle& aRangeStyle, |
| 6431 | const Point& aPt, gfxFloat aICoordInFrame, gfxFloat aWidth, |
| 6432 | gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics, |
| 6433 | DrawPathCallbacks* aCallbacks, bool aVertical, |
| 6434 | StyleTextDecorationLine aDecoration, const Range& aGlyphRange, |
| 6435 | PropertyProvider* aProvider) { |
| 6436 | PaintDecorationLineParams params; |
| 6437 | params.context = aContext; |
| 6438 | params.dirtyRect = aDirtyRect; |
| 6439 | params.pt = aPt; |
| 6440 | params.lineSize.width = aWidth; |
| 6441 | params.ascent = aAscent; |
| 6442 | params.decoration = aDecoration; |
| 6443 | params.decorationType = DecorationType::Selection; |
| 6444 | params.callbacks = aCallbacks; |
| 6445 | params.vertical = aVertical; |
| 6446 | params.sidewaysLeft = mTextRun->IsSidewaysLeft(); |
| 6447 | params.descentLimit = ComputeDescentLimitForSelectionUnderline( |
| 6448 | aTextPaintStyle.PresContext(), aFontMetrics); |
| 6449 | params.glyphRange = aGlyphRange; |
| 6450 | params.provider = aProvider; |
| 6451 | |
| 6452 | float relativeSize = 1.f; |
| 6453 | const auto& decThickness = StyleTextReset()->mTextDecorationThickness; |
| 6454 | const gfxFloat appUnitsPerDevPixel = |
| 6455 | aTextPaintStyle.PresContext()->AppUnitsPerDevPixel(); |
| 6456 | |
| 6457 | const WritingMode wm = GetWritingMode(); |
| 6458 | switch (aSelectionType) { |
| 6459 | case SelectionType::eNormal: |
| 6460 | case SelectionType::eHighlight: |
| 6461 | case SelectionType::eTargetText: { |
| 6462 | RefPtr computedStyleFromPseudo = |
| 6463 | aTextPaintStyle.GetComputedStyleForSelectionPseudo(aSelectionType, |
| 6464 | aHighlightName); |
| 6465 | const bool hasTextDecorations = |
| 6466 | computedStyleFromPseudo |
| 6467 | ? computedStyleFromPseudo->HasTextDecorationLines() |
| 6468 | : false; |
| 6469 | if (!hasTextDecorations) { |
| 6470 | return; |
| 6471 | } |
| 6472 | params.style = |
| 6473 | computedStyleFromPseudo->StyleTextReset()->mTextDecorationStyle; |
| 6474 | params.color = |
| 6475 | computedStyleFromPseudo->StyleTextReset() |
| 6476 | ->mTextDecorationColor.CalcColor(*computedStyleFromPseudo); |
| 6477 | params.decoration = |
| 6478 | computedStyleFromPseudo->StyleTextReset()->mTextDecorationLine; |
| 6479 | params.descentLimit = -1.f; |
| 6480 | |
| 6481 | const bool swapUnderline = |
| 6482 | wm.IsCentralBaseline() && IsUnderlineRight(*Style()); |
| 6483 | params.icoordInFrame = aICoordInFrame; |
| 6484 | auto paintForLine = [&](StyleTextDecorationLine decoration) { |
| 6485 | if (!(computedStyleFromPseudo->StyleTextReset()->mTextDecorationLine & |
| 6486 | decoration)) { |
| 6487 | return; |
| 6488 | } |
| 6489 | |
| 6490 | params.allowInkSkipping = true; |
| 6491 | params.skipInk = |
| 6492 | computedStyleFromPseudo->StyleText()->mTextDecorationSkipInk; |
| 6493 | params.decoration = decoration; |
| 6494 | params.offset = ComputeDecorationLineOffset( |
| 6495 | params.decoration, |
| 6496 | computedStyleFromPseudo->StyleText()->mTextUnderlinePosition, |
| 6497 | computedStyleFromPseudo->StyleText()->mTextUnderlineOffset, |
| 6498 | aFontMetrics, appUnitsPerDevPixel, this, wm.IsCentralBaseline(), |
| 6499 | swapUnderline); |
| 6500 | |
| 6501 | if (decoration == StyleTextDecorationLine::LINE_THROUGH) { |
| 6502 | params.defaultLineThickness = aFontMetrics.strikeoutSize; |
| 6503 | } else { |
| 6504 | params.defaultLineThickness = ComputeSelectionUnderlineHeight( |
| 6505 | aTextPaintStyle.PresContext(), aFontMetrics, aSelectionType); |
| 6506 | } |
| 6507 | params.lineSize.height = ComputeDecorationLineThickness( |
| 6508 | computedStyleFromPseudo->StyleTextReset()->mTextDecorationThickness, |
| 6509 | params.defaultLineThickness, aFontMetrics, appUnitsPerDevPixel, |
| 6510 | this); |
| 6511 | |
| 6512 | PaintDecorationLine(params); |
| 6513 | }; |
| 6514 | paintForLine(StyleTextDecorationLine::UNDERLINE); |
| 6515 | paintForLine(StyleTextDecorationLine::OVERLINE); |
| 6516 | paintForLine(StyleTextDecorationLine::LINE_THROUGH); |
| 6517 | return; |
| 6518 | } |
| 6519 | case SelectionType::eIMERawClause: |
| 6520 | case SelectionType::eIMESelectedRawClause: |
| 6521 | case SelectionType::eIMEConvertedClause: |
| 6522 | case SelectionType::eIMESelectedClause: |
| 6523 | case SelectionType::eSpellCheck: { |
| 6524 | auto index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType( |
| 6525 | aSelectionType); |
| 6526 | bool weDefineSelectionUnderline = |
| 6527 | aTextPaintStyle.GetSelectionUnderlineForPaint( |
| 6528 | index, ¶ms.color, &relativeSize, ¶ms.style); |
| 6529 | params.defaultLineThickness = ComputeSelectionUnderlineHeight( |
| 6530 | aTextPaintStyle.PresContext(), aFontMetrics, aSelectionType); |
| 6531 | params.lineSize.height = ComputeDecorationLineThickness( |
| 6532 | decThickness, params.defaultLineThickness, aFontMetrics, |
| 6533 | appUnitsPerDevPixel, this); |
| 6534 | |
| 6535 | bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style()); |
| 6536 | const auto* styleText = StyleText(); |
| 6537 | params.offset = ComputeDecorationLineOffset( |
| 6538 | aDecoration, styleText->mTextUnderlinePosition, |
| 6539 | styleText->mTextUnderlineOffset, aFontMetrics, appUnitsPerDevPixel, |
| 6540 | this, wm.IsCentralBaseline(), swapUnderline); |
| 6541 | |
| 6542 | bool isIMEType = aSelectionType != SelectionType::eSpellCheck; |
| 6543 | |
| 6544 | if (isIMEType) { |
| 6545 | // IME decoration lines should not be drawn on the both ends, i.e., we |
| 6546 | // need to cut both edges of the decoration lines. Because same style |
| 6547 | // IME selections can adjoin, but the users need to be able to know |
| 6548 | // where are the boundaries of the selections. |
| 6549 | // |
| 6550 | // X: underline |
| 6551 | // |
| 6552 | // IME selection #1 IME selection #2 IME selection #3 |
| 6553 | // | | | |
| 6554 | // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX |
| 6555 | // +---------------------+----------------------+-------------------- |
| 6556 | // ^ ^ ^ ^ ^ |
| 6557 | // gap gap gap |
| 6558 | params.pt.x += 1.0; |
| 6559 | params.lineSize.width -= 2.0; |
| 6560 | } |
| 6561 | if (isIMEType && aRangeStyle.IsDefined()) { |
| 6562 | // If IME defines the style, that should override our definition. |
| 6563 | if (aRangeStyle.IsLineStyleDefined()) { |
| 6564 | if (aRangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) { |
| 6565 | return; |
| 6566 | } |
| 6567 | params.style = ToStyleLineStyle(aRangeStyle); |
| 6568 | relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f; |
| 6569 | } else if (!weDefineSelectionUnderline) { |
| 6570 | // There is no underline style definition. |
| 6571 | return; |
| 6572 | } |
| 6573 | // If underline color is defined and that doesn't depend on the |
| 6574 | // foreground color, we should use the color directly. |
| 6575 | if (aRangeStyle.IsUnderlineColorDefined() && |
| 6576 | (!aRangeStyle.IsForegroundColorDefined() || |
| 6577 | aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor)) { |
| 6578 | params.color = aRangeStyle.mUnderlineColor; |
| 6579 | } |
| 6580 | // If foreground color or background color is defined, the both colors |
| 6581 | // are computed by GetSelectionTextColors(). Then, we should use its |
| 6582 | // foreground color always. The color should have sufficient contrast |
| 6583 | // with the background color. |
| 6584 | else if (aRangeStyle.IsForegroundColorDefined() || |
| 6585 | aRangeStyle.IsBackgroundColorDefined()) { |
| 6586 | params.color = GetSelectionTextColors(aSelectionType, nullptr, |
| 6587 | aTextPaintStyle, aRangeStyle) |
| 6588 | .mForeground; |
| 6589 | } |
| 6590 | // Otherwise, use the foreground color of the frame. |
| 6591 | else { |
| 6592 | params.color = aTextPaintStyle.GetTextColor(); |
| 6593 | } |
| 6594 | } else if (!weDefineSelectionUnderline) { |
| 6595 | // IME doesn't specify the selection style and we don't define selection |
| 6596 | // underline. |
| 6597 | return; |
| 6598 | } |
| 6599 | break; |
| 6600 | } |
| 6601 | case SelectionType::eURLStrikeout: { |
| 6602 | nscoord inflationMinFontSize = |
| 6603 | nsLayoutUtils::InflationMinFontSizeFor(this); |
| 6604 | float inflation = |
| 6605 | GetInflationForTextDecorations(this, inflationMinFontSize); |
| 6606 | const gfxFont::Metrics metrics = |
| 6607 | GetFirstFontMetrics(GetFontGroupForFrame(this, inflation), aVertical); |
| 6608 | |
| 6609 | relativeSize = 2.0f; |
| 6610 | aTextPaintStyle.GetURLSecondaryColor(¶ms.color); |
| 6611 | params.style = StyleTextDecorationStyle::Solid; |
| 6612 | params.defaultLineThickness = metrics.strikeoutSize; |
| 6613 | params.lineSize.height = ComputeDecorationLineThickness( |
| 6614 | decThickness, params.defaultLineThickness, metrics, |
| 6615 | appUnitsPerDevPixel, this); |
| 6616 | // TODO(jfkthame): ComputeDecorationLineOffset? check vertical mode! |
| 6617 | params.offset = metrics.strikeoutOffset + 0.5; |
| 6618 | params.decoration = StyleTextDecorationLine::LINE_THROUGH; |
| 6619 | break; |
| 6620 | } |
| 6621 | default: |
| 6622 | NS_WARNING("Requested selection decorations when there aren't any")NS_DebugBreak(NS_DEBUG_WARNING, "Requested selection decorations when there aren't any" , nullptr, "./../../../layout/generic/nsTextFrame.cpp", 6622); |
| 6623 | return; |
| 6624 | } |
| 6625 | params.lineSize.height *= relativeSize; |
| 6626 | params.defaultLineThickness *= relativeSize; |
| 6627 | params.icoordInFrame = |
| 6628 | (aVertical ? params.pt.y - aPt.y : params.pt.x - aPt.x) + aICoordInFrame; |
| 6629 | PaintDecorationLine(params); |
| 6630 | } |
| 6631 | |
| 6632 | /* static */ |
| 6633 | nsTextFrame::SelectionColors nsTextFrame::GetSelectionTextColors( |
| 6634 | SelectionType aSelectionType, nsAtom* aHighlightName, |
| 6635 | nsTextPaintStyle& aTextPaintStyle, const TextRangeStyle& aRangeStyle) { |
| 6636 | SelectionColors selectionColors; |
| 6637 | switch (aSelectionType) { |
| 6638 | case SelectionType::eNormal: { |
| 6639 | const bool displayed = aTextPaintStyle.GetSelectionColors( |
| 6640 | &selectionColors.mForeground, &selectionColors.mBackground); |
| 6641 | selectionColors.mHasBackground = |
| 6642 | displayed && NS_GET_A(selectionColors.mBackground)((uint8_t)(((selectionColors.mBackground) >> 24) & 0xff )) > 0; |
| 6643 | selectionColors.mOverridesForeground = displayed; |
| 6644 | break; |
| 6645 | } |
| 6646 | case SelectionType::eFind: |
| 6647 | aTextPaintStyle.GetHighlightColors(&selectionColors.mForeground, |
| 6648 | &selectionColors.mBackground); |
| 6649 | selectionColors.mHasBackground = |
| 6650 | NS_GET_A(selectionColors.mBackground)((uint8_t)(((selectionColors.mBackground) >> 24) & 0xff )) > 0; |
| 6651 | selectionColors.mOverridesForeground = true; |
| 6652 | break; |
| 6653 | case SelectionType::eHighlight: |
| 6654 | // Intentionally not short-cutting here: both methods must always run. |
| 6655 | selectionColors.mOverridesForeground = |
| 6656 | aTextPaintStyle.GetCustomHighlightTextColor( |
| 6657 | aHighlightName, &selectionColors.mForeground); |
| 6658 | selectionColors.mHasBackground = |
| 6659 | aTextPaintStyle.GetCustomHighlightBackgroundColor( |
| 6660 | aHighlightName, &selectionColors.mBackground); |
| 6661 | selectionColors.mHasPaintImpact = |
| 6662 | !!aTextPaintStyle.GetComputedStyleForSelectionPseudo(aSelectionType, |
| 6663 | aHighlightName); |
| 6664 | break; |
| 6665 | case SelectionType::eTargetText: |
| 6666 | // Intentionally not short-cutting here: both methods must always run. |
| 6667 | selectionColors.mOverridesForeground = |
| 6668 | aTextPaintStyle.GetTargetTextColor(&selectionColors.mForeground); |
| 6669 | selectionColors.mHasBackground = |
| 6670 | aTextPaintStyle.GetTargetTextBackgroundColor( |
| 6671 | &selectionColors.mBackground); |
| 6672 | selectionColors.mHasPaintImpact = |
| 6673 | !!aTextPaintStyle.GetComputedStyleForSelectionPseudo(aSelectionType, |
| 6674 | aHighlightName); |
| 6675 | break; |
| 6676 | case SelectionType::eURLSecondary: |
| 6677 | aTextPaintStyle.GetURLSecondaryColor(&selectionColors.mForeground); |
| 6678 | selectionColors.mOverridesForeground = true; |
| 6679 | break; |
| 6680 | case SelectionType::eIMERawClause: |
| 6681 | case SelectionType::eIMESelectedRawClause: |
| 6682 | case SelectionType::eIMEConvertedClause: |
| 6683 | case SelectionType::eIMESelectedClause: |
| 6684 | if (aRangeStyle.IsDefined()) { |
| 6685 | if (!aRangeStyle.IsForegroundColorDefined() && |
| 6686 | !aRangeStyle.IsBackgroundColorDefined()) { |
| 6687 | selectionColors.mForeground = aTextPaintStyle.GetTextColor(); |
| 6688 | break; |
| 6689 | } |
| 6690 | if (aRangeStyle.IsForegroundColorDefined()) { |
| 6691 | selectionColors.mForeground = aRangeStyle.mForegroundColor; |
| 6692 | if (aRangeStyle.IsBackgroundColorDefined()) { |
| 6693 | selectionColors.mBackground = aRangeStyle.mBackgroundColor; |
| 6694 | } else { |
| 6695 | // If foreground color is defined but background color isn't |
| 6696 | // defined, we can guess that IME must expect that the background |
| 6697 | // color is system's default field background color. |
| 6698 | selectionColors.mBackground = |
| 6699 | aTextPaintStyle.GetSystemFieldBackgroundColor(); |
| 6700 | } |
| 6701 | } else { // aRangeStyle.IsBackgroundColorDefined() is true |
| 6702 | selectionColors.mBackground = aRangeStyle.mBackgroundColor; |
| 6703 | // If background color is defined but foreground color isn't defined, |
| 6704 | // we can assume that IME must expect that the foreground color is |
| 6705 | // same as system's field text color. |
| 6706 | selectionColors.mForeground = |
| 6707 | aTextPaintStyle.GetSystemFieldForegroundColor(); |
| 6708 | } |
| 6709 | selectionColors.mHasBackground = |
| 6710 | NS_GET_A(selectionColors.mBackground)((uint8_t)(((selectionColors.mBackground) >> 24) & 0xff )) > 0; |
| 6711 | selectionColors.mOverridesForeground = true; |
| 6712 | break; |
| 6713 | } |
| 6714 | aTextPaintStyle.GetIMESelectionColors( |
| 6715 | nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType( |
| 6716 | aSelectionType), |
| 6717 | &selectionColors.mForeground, &selectionColors.mBackground); |
| 6718 | selectionColors.mHasBackground = |
| 6719 | NS_GET_A(selectionColors.mBackground)((uint8_t)(((selectionColors.mBackground) >> 24) & 0xff )) > 0; |
| 6720 | selectionColors.mOverridesForeground = true; |
| 6721 | break; |
| 6722 | default: |
| 6723 | selectionColors.mForeground = aTextPaintStyle.GetTextColor(); |
| 6724 | break; |
| 6725 | } |
| 6726 | selectionColors.mHasPaintImpact |= selectionColors.HasAnyColorImpact(); |
| 6727 | return selectionColors; |
| 6728 | } |
| 6729 | |
| 6730 | /** |
| 6731 | * This sets *aShadows to the appropriate shadows, if any, for the given |
| 6732 | * type of selection. |
| 6733 | * If text-shadow was not specified, *aShadows is left untouched. |
| 6734 | */ |
| 6735 | mozilla::Span<const StyleSimpleShadow> nsTextFrame::GetSelectionTextShadow( |
| 6736 | SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle, |
| 6737 | nsAtom* aHighlightName) { |
| 6738 | if (aSelectionType == SelectionType::eNormal) { |
| 6739 | return aTextPaintStyle.GetSelectionShadow(); |
| 6740 | } |
| 6741 | if (aSelectionType == SelectionType::eTargetText) { |
| 6742 | return aTextPaintStyle.GetTargetTextShadow(); |
| 6743 | } |
| 6744 | if (aSelectionType == SelectionType::eHighlight && aHighlightName) { |
| 6745 | return aTextPaintStyle.GetCustomHighlightTextShadow(aHighlightName); |
| 6746 | } |
| 6747 | return {}; |
| 6748 | } |
| 6749 | |
| 6750 | /** |
| 6751 | * This class lets us iterate over chunks of text recorded in an array of |
| 6752 | * resolved selection ranges, observing cluster boundaries, in content order, |
| 6753 | * maintaining the current x-offset as we go, and telling whether the text |
| 6754 | * chunk has a hyphen after it or not. |
| 6755 | * In addition to returning the selected chunks, the iterator is responsible |
| 6756 | * to interpolate unselected chunks in any gaps between them. |
| 6757 | * The caller is responsible for actually computing the advance width of each |
| 6758 | * chunk. |
| 6759 | */ |
| 6760 | class MOZ_STACK_CLASS SelectionRangeIterator { |
| 6761 | using PropertyProvider = nsTextFrame::PropertyProvider; |
| 6762 | using CombinedSelectionRange = nsTextFrame::PriorityOrderedSelectionsForRange; |
| 6763 | |
| 6764 | public: |
| 6765 | // aSelectionRanges and aRange are according to the original string. |
| 6766 | SelectionRangeIterator( |
| 6767 | const nsTArray<CombinedSelectionRange>& aSelectionRanges, |
| 6768 | gfxTextRun::Range aRange, PropertyProvider& aProvider, |
| 6769 | gfxTextRun* aTextRun, gfxFloat aXOffset); |
| 6770 | |
| 6771 | bool GetNextSegment(gfxFloat* aXOffset, gfxTextRun::Range* aRange, |
| 6772 | gfxFloat* aHyphenWidth, |
| 6773 | nsTArray<SelectionType>& aSelectionType, |
| 6774 | nsTArray<RefPtr<nsAtom>>& aHighlightName, |
| 6775 | nsTArray<TextRangeStyle>& aStyle); |
| 6776 | |
| 6777 | void UpdateWithAdvance(gfxFloat aAdvance) { |
| 6778 | mXOffset += aAdvance * mTextRun->GetDirection(); |
| 6779 | } |
| 6780 | |
| 6781 | private: |
| 6782 | const nsTArray<CombinedSelectionRange>& mSelectionRanges; |
| 6783 | PropertyProvider& mProvider; |
| 6784 | gfxTextRun* mTextRun; |
| 6785 | gfxSkipCharsIterator mIterator; |
| 6786 | gfxTextRun::Range mOriginalRange; |
| 6787 | gfxFloat mXOffset; |
| 6788 | uint32_t mIndex; |
| 6789 | }; |
| 6790 | |
| 6791 | SelectionRangeIterator::SelectionRangeIterator( |
| 6792 | const nsTArray<nsTextFrame::PriorityOrderedSelectionsForRange>& |
| 6793 | aSelectionRanges, |
| 6794 | gfxTextRun::Range aRange, PropertyProvider& aProvider, gfxTextRun* aTextRun, |
| 6795 | gfxFloat aXOffset) |
| 6796 | : mSelectionRanges(aSelectionRanges), |
| 6797 | mProvider(aProvider), |
| 6798 | mTextRun(aTextRun), |
| 6799 | mIterator(aProvider.GetStart()), |
| 6800 | mOriginalRange(aRange), |
| 6801 | mXOffset(aXOffset), |
| 6802 | mIndex(0) { |
| 6803 | mIterator.SetOriginalOffset(int32_t(aRange.start)); |
| 6804 | } |
| 6805 | |
| 6806 | bool SelectionRangeIterator::GetNextSegment( |
| 6807 | gfxFloat* aXOffset, gfxTextRun::Range* aRange, gfxFloat* aHyphenWidth, |
| 6808 | nsTArray<SelectionType>& aSelectionType, |
| 6809 | nsTArray<RefPtr<nsAtom>>& aHighlightName, |
| 6810 | nsTArray<TextRangeStyle>& aStyle) { |
| 6811 | if (mIterator.GetOriginalOffset() >= int32_t(mOriginalRange.end)) { |
| 6812 | return false; |
| 6813 | } |
| 6814 | |
| 6815 | uint32_t runOffset = mIterator.GetSkippedOffset(); |
| 6816 | uint32_t segmentEnd = mOriginalRange.end; |
| 6817 | |
| 6818 | aSelectionType.Clear(); |
| 6819 | aHighlightName.Clear(); |
| 6820 | aStyle.Clear(); |
| 6821 | |
| 6822 | if (mIndex == mSelectionRanges.Length() || |
| 6823 | mIterator.GetOriginalOffset() < |
| 6824 | int32_t(mSelectionRanges[mIndex].mRange.start)) { |
| 6825 | // There's an unselected segment before the next range (or at the end). |
| 6826 | aSelectionType.AppendElement(SelectionType::eNone); |
| 6827 | aHighlightName.AppendElement(); |
| 6828 | aStyle.AppendElement(TextRangeStyle()); |
| 6829 | if (mIndex < mSelectionRanges.Length()) { |
| 6830 | segmentEnd = mSelectionRanges[mIndex].mRange.start; |
| 6831 | } |
| 6832 | } else { |
| 6833 | // Get the selection details for the next segment, and increment index. |
| 6834 | for (const SelectionDetails* sdptr : |
| 6835 | mSelectionRanges[mIndex].mSelectionRanges) { |
| 6836 | aSelectionType.AppendElement(sdptr->mSelectionType); |
| 6837 | aHighlightName.AppendElement(sdptr->mHighlightData.mHighlightName); |
| 6838 | aStyle.AppendElement(sdptr->mTextRangeStyle); |
| 6839 | } |
| 6840 | segmentEnd = mSelectionRanges[mIndex].mRange.end; |
| 6841 | ++mIndex; |
| 6842 | } |
| 6843 | |
| 6844 | // Advance iterator to the end of the segment. |
| 6845 | mIterator.SetOriginalOffset(int32_t(segmentEnd)); |
| 6846 | |
| 6847 | // Further advance if necessary to a cluster boundary. |
| 6848 | while (mIterator.GetOriginalOffset() < int32_t(mOriginalRange.end) && |
| 6849 | !mIterator.IsOriginalCharSkipped() && |
| 6850 | !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) { |
| 6851 | mIterator.AdvanceOriginal(1); |
| 6852 | } |
| 6853 | |
| 6854 | aRange->start = runOffset; |
| 6855 | aRange->end = mIterator.GetSkippedOffset(); |
| 6856 | *aXOffset = mXOffset; |
| 6857 | *aHyphenWidth = 0; |
| 6858 | if (mIterator.GetOriginalOffset() == int32_t(mOriginalRange.end) && |
| 6859 | mProvider.GetFrame()->HasAnyStateBits(TEXT_HYPHEN_BREAK)) { |
| 6860 | *aHyphenWidth = mProvider.GetHyphenWidth(); |
| 6861 | } |
| 6862 | |
| 6863 | return true; |
| 6864 | } |
| 6865 | |
| 6866 | static void AddHyphenToMetrics(nsTextFrame* aTextFrame, bool aIsRightToLeft, |
| 6867 | gfxTextRun::Metrics* aMetrics, |
| 6868 | gfxFont::BoundingBoxType aBoundingBoxType, |
| 6869 | DrawTarget* aDrawTarget) { |
| 6870 | // Fix up metrics to include hyphen |
| 6871 | RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(aTextFrame, aDrawTarget); |
| 6872 | if (!hyphenTextRun) { |
| 6873 | return; |
| 6874 | } |
| 6875 | |
| 6876 | gfxTextRun::Metrics hyphenMetrics = |
| 6877 | hyphenTextRun->MeasureText(aBoundingBoxType, aDrawTarget); |
| 6878 | if (aTextFrame->GetWritingMode().IsLineInverted()) { |
| 6879 | hyphenMetrics.mBoundingBox.y = -hyphenMetrics.mBoundingBox.YMost(); |
| 6880 | } |
| 6881 | aMetrics->CombineWith(hyphenMetrics, aIsRightToLeft); |
| 6882 | } |
| 6883 | |
| 6884 | void nsTextFrame::PaintOneShadow(const PaintShadowParams& aParams, |
| 6885 | const StyleSimpleShadow& aShadowDetails, |
| 6886 | gfxRect& aBoundingBox, uint32_t aBlurFlags, |
| 6887 | imgDrawingParams& aImgParams) { |
| 6888 | AUTO_PROFILER_LABEL("nsTextFrame::PaintOneShadow", GRAPHICS)mozilla::AutoProfilerLabel raiiObject6888( "nsTextFrame::PaintOneShadow" , nullptr, JS::ProfilingCategoryPair::GRAPHICS); |
| 6889 | |
| 6890 | nsPoint shadowOffset(aShadowDetails.horizontal.ToAppUnits(), |
| 6891 | aShadowDetails.vertical.ToAppUnits()); |
| 6892 | nscoord blurRadius = std::max(aShadowDetails.blur.ToAppUnits(), 0); |
| 6893 | |
| 6894 | nscolor shadowColor = aShadowDetails.color.CalcColor(aParams.foregroundColor); |
| 6895 | |
| 6896 | if (auto* textDrawer = aParams.context->GetTextDrawer()) { |
| 6897 | wr::Shadow wrShadow; |
| 6898 | |
| 6899 | wrShadow.offset = {PresContext()->AppUnitsToFloatDevPixels(shadowOffset.x), |
| 6900 | PresContext()->AppUnitsToFloatDevPixels(shadowOffset.y)}; |
| 6901 | |
| 6902 | wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(blurRadius); |
| 6903 | wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor)); |
| 6904 | |
| 6905 | bool inflateMOZ_Z_inflate = true; |
| 6906 | textDrawer->AppendShadow(wrShadow, inflateMOZ_Z_inflate); |
| 6907 | return; |
| 6908 | } |
| 6909 | |
| 6910 | // This rect is the box which is equivalent to where the shadow will be |
| 6911 | // painted. The origin of aBoundingBox is the text baseline left, so we must |
| 6912 | // translate it by that much in order to make the origin the top-left corner |
| 6913 | // of the text bounding box. Note that aLeftSideOffset is line-left, so |
| 6914 | // actually means top offset in vertical writing modes. |
| 6915 | gfxRect shadowGfxRect; |
| 6916 | WritingMode wm = GetWritingMode(); |
| 6917 | if (wm.IsVertical()) { |
| 6918 | shadowGfxRect = aBoundingBox; |
| 6919 | if (wm.IsVerticalRL()) { |
| 6920 | // for vertical-RL, reverse direction of x-coords of bounding box |
| 6921 | shadowGfxRect.x = -shadowGfxRect.XMost(); |
| 6922 | } |
| 6923 | shadowGfxRect += gfxPoint(aParams.textBaselinePt.x, |
| 6924 | aParams.framePt.y + aParams.leftSideOffset); |
| 6925 | } else { |
| 6926 | shadowGfxRect = |
| 6927 | aBoundingBox + gfxPoint(aParams.framePt.x + aParams.leftSideOffset, |
| 6928 | aParams.textBaselinePt.y); |
| 6929 | } |
| 6930 | Point shadowGfxOffset(shadowOffset.x, shadowOffset.y); |
| 6931 | shadowGfxRect += gfxPoint(shadowGfxOffset.x, shadowOffset.y); |
| 6932 | |
| 6933 | nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()), |
| 6934 | NSToCoordRound(shadowGfxRect.Y()), |
| 6935 | NSToCoordRound(shadowGfxRect.Width()), |
| 6936 | NSToCoordRound(shadowGfxRect.Height())); |
| 6937 | |
| 6938 | nsContextBoxBlur contextBoxBlur; |
| 6939 | const auto A2D = PresContext()->AppUnitsPerDevPixel(); |
| 6940 | gfxContext* shadowContext = |
| 6941 | contextBoxBlur.Init(shadowRect, 0, blurRadius, A2D, aParams.context, |
| 6942 | LayoutDevicePixel::ToAppUnits(aParams.dirtyRect, A2D), |
| 6943 | nullptr, aBlurFlags); |
| 6944 | if (!shadowContext) { |
| 6945 | return; |
| 6946 | } |
| 6947 | |
| 6948 | aParams.context->Save(); |
| 6949 | aParams.context->SetColor(sRGBColor::FromABGR(shadowColor)); |
| 6950 | |
| 6951 | // Draw the text onto our alpha-only surface to capture the alpha values. |
| 6952 | // Remember that the box blur context has a device offset on it, so we don't |
| 6953 | // need to translate any coordinates to fit on the surface. |
| 6954 | gfxFloat advanceWidth; |
| 6955 | nsTextPaintStyle textPaintStyle(this); |
| 6956 | DrawTextParams params(shadowContext, PresContext()->FontPaletteCache()); |
| 6957 | params.paintingShadows = true; |
| 6958 | params.advanceWidth = &advanceWidth; |
| 6959 | params.dirtyRect = aParams.dirtyRect; |
| 6960 | params.framePt = aParams.framePt + shadowGfxOffset; |
| 6961 | params.provider = aParams.provider; |
| 6962 | params.textStyle = &textPaintStyle; |
| 6963 | params.textColor = |
| 6964 | aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0)((nscolor)((255 << 24) | ((0) << 16) | ((0) << 8) | (0))); |
| 6965 | params.callbacks = aParams.callbacks; |
| 6966 | params.clipEdges = aParams.clipEdges; |
| 6967 | params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK); |
| 6968 | // Multi-color shadow is not allowed, so we use the same color as the text |
| 6969 | // color. |
| 6970 | params.decorationOverrideColor = ¶ms.textColor; |
| 6971 | params.fontPalette = StyleFont()->GetFontPaletteAtom(); |
| 6972 | |
| 6973 | DrawText(aParams.range, aParams.textBaselinePt + shadowGfxOffset, params, |
| 6974 | aImgParams); |
| 6975 | |
| 6976 | contextBoxBlur.DoPaint(); |
| 6977 | aParams.context->Restore(); |
| 6978 | } |
| 6979 | |
| 6980 | /* static */ |
| 6981 | SelectionTypeMask nsTextFrame::CreateSelectionRangeList( |
| 6982 | const SelectionDetails& aDetails, SelectionType aSelectionType, |
| 6983 | const PaintTextSelectionParams& aParams, |
| 6984 | nsTArray<SelectionRange>& aSelectionRanges, bool* aAnyBackgrounds) { |
| 6985 | SelectionTypeMask allTypes = 0; |
| 6986 | bool anyBackgrounds = false; |
| 6987 | |
| 6988 | uint32_t priorityOfInsertionOrder = 0; |
| 6989 | for (const SelectionDetails* sd = &aDetails; sd; sd = sd->mNext.get()) { |
| 6990 | MOZ_ASSERT(sd->mStart >= 0 && sd->mEnd >= 0)do { static_assert( mozilla::detail::AssertionConditionType< decltype(sd->mStart >= 0 && sd->mEnd >= 0 )>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(sd->mStart >= 0 && sd->mEnd >= 0 ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "sd->mStart >= 0 && sd->mEnd >= 0", "./../../../layout/generic/nsTextFrame.cpp" , 6990); AnnotateMozCrashReason("MOZ_ASSERT" "(" "sd->mStart >= 0 && sd->mEnd >= 0" ")"); do { MOZ_CrashSequence(__null, 6990); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); // XXX make unsigned? |
| 6991 | uint32_t start = std::max(aParams.contentRange.start, uint32_t(sd->mStart)); |
| 6992 | uint32_t end = std::min(aParams.contentRange.end, uint32_t(sd->mEnd)); |
| 6993 | if (start < end) { |
| 6994 | // The PaintTextWithSelectionColors caller passes SelectionType::eNone, |
| 6995 | // so we collect all selections that affect text/background paint, and |
| 6996 | // prioritize them according to selection type (lower types take |
| 6997 | // precedence). |
| 6998 | if (aSelectionType == SelectionType::eNone) { |
| 6999 | allTypes |= ToSelectionTypeMask(sd->mSelectionType); |
| 7000 | // Ignore selections that don't affect text/background paint. |
| 7001 | const auto colors = GetSelectionTextColors( |
| 7002 | sd->mSelectionType, sd->mHighlightData.mHighlightName, |
| 7003 | *aParams.textPaintStyle, sd->mTextRangeStyle); |
| 7004 | if (colors.HasAnyPaintImpact()) { |
| 7005 | if (colors.mHasBackground) { |
| 7006 | anyBackgrounds = true; |
| 7007 | } |
| 7008 | aSelectionRanges.AppendElement( |
| 7009 | SelectionRange{sd, {start, end}, priorityOfInsertionOrder++}); |
| 7010 | } |
| 7011 | } else if (sd->mSelectionType == aSelectionType) { |
| 7012 | // The PaintSelectionTextDecorations caller passes a specific type, |
| 7013 | // so we include only ranges of that type, and keep them in order |
| 7014 | // so that later ones take precedence over earlier. |
| 7015 | aSelectionRanges.AppendElement( |
| 7016 | SelectionRange{sd, {start, end}, priorityOfInsertionOrder++}); |
| 7017 | } |
| 7018 | } |
| 7019 | } |
| 7020 | if (aAnyBackgrounds) { |
| 7021 | *aAnyBackgrounds = anyBackgrounds; |
| 7022 | } |
| 7023 | return allTypes; |
| 7024 | } |
| 7025 | |
| 7026 | /* static */ |
| 7027 | void nsTextFrame::CombineSelectionRanges( |
| 7028 | const nsTArray<SelectionRange>& aSelectionRanges, |
| 7029 | nsTArray<PriorityOrderedSelectionsForRange>& aCombinedSelectionRanges) { |
| 7030 | struct SelectionRangeEndCmp { |
| 7031 | bool Equals(const SelectionRange* a, const SelectionRange* b) const { |
| 7032 | return a->mRange.end == b->mRange.end; |
| 7033 | } |
| 7034 | bool LessThan(const SelectionRange* a, const SelectionRange* b) const { |
| 7035 | return a->mRange.end < b->mRange.end; |
| 7036 | } |
| 7037 | }; |
| 7038 | |
| 7039 | struct SelectionRangePriorityCmp { |
| 7040 | bool Equals(const SelectionRange* a, const SelectionRange* b) const { |
| 7041 | const SelectionDetails* aDetails = a->mDetails; |
| 7042 | const SelectionDetails* bDetails = b->mDetails; |
| 7043 | if (aDetails->mSelectionType != bDetails->mSelectionType) { |
| 7044 | return false; |
| 7045 | } |
| 7046 | if (aDetails->mSelectionType != SelectionType::eHighlight) { |
| 7047 | return a->mPriority == b->mPriority; |
| 7048 | } |
| 7049 | if (aDetails->mHighlightData.mHighlight->Priority() != |
| 7050 | bDetails->mHighlightData.mHighlight->Priority()) { |
| 7051 | return false; |
| 7052 | } |
| 7053 | return a->mPriority == b->mPriority; |
| 7054 | } |
| 7055 | |
| 7056 | bool LessThan(const SelectionRange* a, const SelectionRange* b) const { |
| 7057 | if (a->mDetails->mSelectionType != b->mDetails->mSelectionType) { |
| 7058 | // Even though this looks counter-intuitive, |
| 7059 | // this is intended, as values in `SelectionType` are inverted: |
| 7060 | // a lower value indicates a higher priority. |
| 7061 | return a->mDetails->mSelectionType > b->mDetails->mSelectionType; |
| 7062 | } |
| 7063 | |
| 7064 | if (a->mDetails->mSelectionType != SelectionType::eHighlight) { |
| 7065 | // for non-highlights, the selection which was added later |
| 7066 | // has a higher priority. |
| 7067 | return a->mPriority < b->mPriority; |
| 7068 | } |
| 7069 | |
| 7070 | if (a->mDetails->mHighlightData.mHighlight->Priority() != |
| 7071 | b->mDetails->mHighlightData.mHighlight->Priority()) { |
| 7072 | // For highlights, first compare the priorities set by the user. |
| 7073 | return a->mDetails->mHighlightData.mHighlight->Priority() < |
| 7074 | b->mDetails->mHighlightData.mHighlight->Priority(); |
| 7075 | } |
| 7076 | // only if the user priorities are equal, let the highlight that was added |
| 7077 | // later take precedence. |
| 7078 | return a->mPriority < b->mPriority; |
| 7079 | } |
| 7080 | }; |
| 7081 | |
| 7082 | uint32_t currentOffset = 0; |
| 7083 | AutoTArray<const SelectionRange*, 1> activeSelectionsForCurrentSegment; |
| 7084 | size_t rangeIndex = 0; |
| 7085 | |
| 7086 | // Divide the given selection ranges into segments which share the same |
| 7087 | // set of selections. |
| 7088 | // The following algorithm iterates `aSelectionRanges`, assuming |
| 7089 | // that its elements are sorted by their start offset. |
| 7090 | // Each time a new selection starts, it is pushed into an array of |
| 7091 | // "currently present" selections, sorted by their *end* offset. |
| 7092 | // For each iteration the next segment end offset is determined, |
| 7093 | // which is either the start offset of the next selection or |
| 7094 | // the next end offset of all "currently present" selections |
| 7095 | // (which is always the first element of the array because of its order). |
| 7096 | // Then, a `CombinedSelectionRange` can be constructed, which describes |
| 7097 | // the text segment until its end offset (as determined above), and contains |
| 7098 | // all elements of the "currently present" selection list, now sorted |
| 7099 | // by their priority. |
| 7100 | // If a range ends at the given offset, it is removed from the array. |
| 7101 | while (rangeIndex < aSelectionRanges.Length() || |
| 7102 | !activeSelectionsForCurrentSegment.IsEmpty()) { |
| 7103 | uint32_t currentSegmentEndOffset = |
| 7104 | activeSelectionsForCurrentSegment.IsEmpty() |
| 7105 | ? -1 |
| 7106 | : activeSelectionsForCurrentSegment[0]->mRange.end; |
| 7107 | uint32_t nextRangeStartOffset = |
| 7108 | rangeIndex < aSelectionRanges.Length() |
| 7109 | ? aSelectionRanges[rangeIndex].mRange.start |
| 7110 | : -1; |
| 7111 | uint32_t nextOffset = |
| 7112 | std::min(currentSegmentEndOffset, nextRangeStartOffset); |
| 7113 | if (!activeSelectionsForCurrentSegment.IsEmpty() && |
| 7114 | currentOffset != nextOffset) { |
| 7115 | auto activeSelectionRangesSortedByPriority = |
| 7116 | activeSelectionsForCurrentSegment.Clone(); |
| 7117 | activeSelectionRangesSortedByPriority.Sort(SelectionRangePriorityCmp()); |
| 7118 | |
| 7119 | AutoTArray<const SelectionDetails*, 1> selectionDetails; |
| 7120 | selectionDetails.SetCapacity( |
| 7121 | activeSelectionRangesSortedByPriority.Length()); |
| 7122 | // ensure that overlapping highlights which have the same name |
| 7123 | // are only added once. If added each time, they would be painted |
| 7124 | // several times (see wpt |
| 7125 | // /css/css-highlight-api/painting/custom-highlight-painting-003.html) |
| 7126 | // Comparing the highlight name with the previous one is |
| 7127 | // sufficient here because selections are already sorted |
| 7128 | // in a way that ensures that highlights of the same name are |
| 7129 | // grouped together. |
| 7130 | nsAtom* currentHighlightName = nullptr; |
| 7131 | for (const auto* selectionRange : activeSelectionRangesSortedByPriority) { |
| 7132 | if (selectionRange->mDetails->mSelectionType == |
| 7133 | SelectionType::eHighlight) { |
| 7134 | if (selectionRange->mDetails->mHighlightData.mHighlightName == |
| 7135 | currentHighlightName) { |
| 7136 | continue; |
| 7137 | } |
| 7138 | currentHighlightName = |
| 7139 | selectionRange->mDetails->mHighlightData.mHighlightName; |
| 7140 | } |
| 7141 | selectionDetails.AppendElement(selectionRange->mDetails); |
| 7142 | } |
| 7143 | aCombinedSelectionRanges.AppendElement(PriorityOrderedSelectionsForRange{ |
| 7144 | std::move(selectionDetails), {currentOffset, nextOffset}}); |
| 7145 | } |
| 7146 | currentOffset = nextOffset; |
| 7147 | |
| 7148 | if (nextRangeStartOffset < currentSegmentEndOffset) { |
| 7149 | activeSelectionsForCurrentSegment.InsertElementSorted( |
| 7150 | &aSelectionRanges[rangeIndex++], SelectionRangeEndCmp()); |
| 7151 | } else { |
| 7152 | activeSelectionsForCurrentSegment.RemoveElementAt(0); |
| 7153 | } |
| 7154 | } |
| 7155 | } |
| 7156 | |
| 7157 | SelectionTypeMask nsTextFrame::ResolveSelections( |
| 7158 | const PaintTextSelectionParams& aParams, const SelectionDetails& aDetails, |
| 7159 | nsTArray<PriorityOrderedSelectionsForRange>& aResult, |
| 7160 | SelectionType aSelectionType, bool* aAnyBackgrounds) const { |
| 7161 | AutoTArray<SelectionRange, 4> selectionRanges; |
| 7162 | |
| 7163 | SelectionTypeMask allTypes = CreateSelectionRangeList( |
| 7164 | aDetails, aSelectionType, aParams, selectionRanges, aAnyBackgrounds); |
| 7165 | |
| 7166 | if (selectionRanges.IsEmpty()) { |
| 7167 | return allTypes; |
| 7168 | } |
| 7169 | |
| 7170 | struct SelectionRangeStartCmp { |
| 7171 | bool Equals(const SelectionRange& a, const SelectionRange& b) const { |
| 7172 | return a.mRange.start == b.mRange.start; |
| 7173 | } |
| 7174 | bool LessThan(const SelectionRange& a, const SelectionRange& b) const { |
| 7175 | return a.mRange.start < b.mRange.start; |
| 7176 | } |
| 7177 | }; |
| 7178 | selectionRanges.Sort(SelectionRangeStartCmp()); |
| 7179 | |
| 7180 | CombineSelectionRanges(selectionRanges, aResult); |
| 7181 | |
| 7182 | return allTypes; |
| 7183 | } |
| 7184 | |
| 7185 | // Paints selection backgrounds and text in the correct colors. Also computes |
| 7186 | // aAllSelectionTypeMask, the union of all selection types that are applying to |
| 7187 | // this text. |
| 7188 | bool nsTextFrame::PaintTextWithSelectionColors( |
| 7189 | const PaintTextSelectionParams& aParams, const SelectionDetails& aDetails, |
| 7190 | SelectionTypeMask* aAllSelectionTypeMask, const ClipEdges& aClipEdges, |
| 7191 | imgDrawingParams& aImgParams) { |
| 7192 | bool anyBackgrounds = false; |
| 7193 | AutoTArray<PriorityOrderedSelectionsForRange, 8> selectionRanges; |
| 7194 | |
| 7195 | *aAllSelectionTypeMask = |
| 7196 | ResolveSelections(aParams, aDetails, selectionRanges, |
| 7197 | SelectionType::eNone, &anyBackgrounds); |
| 7198 | bool vertical = mTextRun->IsVertical(); |
| 7199 | const gfxFloat startIOffset = |
| 7200 | vertical ? aParams.textBaselinePt.y - aParams.framePt.y |
| 7201 | : aParams.textBaselinePt.x - aParams.framePt.x; |
| 7202 | gfxFloat iOffset, hyphenWidth; |
| 7203 | Range range; // in transformed string |
| 7204 | |
| 7205 | const gfxTextRun::Range& contentRange = aParams.contentRange; |
| 7206 | auto* textDrawer = aParams.context->GetTextDrawer(); |
| 7207 | |
| 7208 | if (anyBackgrounds && !aParams.IsGenerateTextMask()) { |
| 7209 | int32_t appUnitsPerDevPixel = |
| 7210 | aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel(); |
| 7211 | SelectionRangeIterator iterator(selectionRanges, contentRange, |
| 7212 | *aParams.provider, mTextRun, startIOffset); |
| 7213 | AutoTArray<SelectionType, 1> selectionTypes; |
| 7214 | AutoTArray<RefPtr<nsAtom>, 1> highlightNames; |
| 7215 | AutoTArray<TextRangeStyle, 1> rangeStyles; |
| 7216 | while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth, |
| 7217 | selectionTypes, highlightNames, |
| 7218 | rangeStyles)) { |
| 7219 | gfxFloat advance = |
| 7220 | hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider); |
| 7221 | nsRect bgRect; |
| 7222 | gfxFloat offs = iOffset - (mTextRun->IsInlineReversed() ? advance : 0); |
| 7223 | if (vertical) { |
| 7224 | bgRect = nsRect(nscoord(aParams.framePt.x), |
| 7225 | nscoord(aParams.framePt.y + offs), GetSize().width, |
| 7226 | nscoord(advance)); |
| 7227 | } else { |
| 7228 | // For text-combine-upright, we may have collapsed the frame height, |
| 7229 | // so we instead use 1em to ensure the selection is visible. |
| 7230 | nscoord height = Style()->IsTextCombined() |
| 7231 | ? aParams.provider->GetFontMetrics()->EmHeight() |
| 7232 | : GetSize().height; |
| 7233 | bgRect = nsRect(nscoord(aParams.framePt.x + offs), |
| 7234 | nscoord(aParams.framePt.y), nscoord(advance), height); |
| 7235 | } |
| 7236 | |
| 7237 | LayoutDeviceRect selectionRect = |
| 7238 | LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel); |
| 7239 | // The elements in `selectionTypes` are ordered ascending by their |
| 7240 | // priority. To account for non-opaque overlapping selections, all |
| 7241 | // selection backgrounds are painted. |
| 7242 | for (size_t index = 0; index < selectionTypes.Length(); ++index) { |
| 7243 | const auto colors = |
| 7244 | GetSelectionTextColors(selectionTypes[index], highlightNames[index], |
| 7245 | *aParams.textPaintStyle, rangeStyles[index]); |
| 7246 | if (colors.mHasBackground) { |
| 7247 | if (textDrawer) { |
| 7248 | textDrawer->AppendSelectionRect(selectionRect, |
| 7249 | ToDeviceColor(colors.mBackground)); |
| 7250 | } else { |
| 7251 | PaintSelectionBackground(*aParams.context->GetDrawTarget(), |
| 7252 | colors.mBackground, aParams.dirtyRect, |
| 7253 | selectionRect, aParams.callbacks); |
| 7254 | } |
| 7255 | } |
| 7256 | } |
| 7257 | iterator.UpdateWithAdvance(advance); |
| 7258 | } |
| 7259 | } |
| 7260 | |
| 7261 | gfxFloat advance; |
| 7262 | DrawTextParams params(aParams.context, PresContext()->FontPaletteCache()); |
| 7263 | params.dirtyRect = aParams.dirtyRect; |
| 7264 | params.framePt = aParams.framePt; |
| 7265 | params.provider = aParams.provider; |
| 7266 | params.textStyle = aParams.textPaintStyle; |
| 7267 | params.clipEdges = &aClipEdges; |
| 7268 | params.advanceWidth = &advance; |
| 7269 | params.callbacks = aParams.callbacks; |
| 7270 | params.glyphRange = aParams.glyphRange; |
| 7271 | params.fontPalette = StyleFont()->GetFontPaletteAtom(); |
| 7272 | params.hasTextShadow = !StyleText()->mTextShadow.IsEmpty(); |
| 7273 | |
| 7274 | PaintShadowParams shadowParams(aParams); |
| 7275 | shadowParams.provider = aParams.provider; |
| 7276 | shadowParams.callbacks = aParams.callbacks; |
| 7277 | shadowParams.clipEdges = &aClipEdges; |
| 7278 | |
| 7279 | // Draw text |
| 7280 | const nsStyleText* textStyle = StyleText(); |
| 7281 | SelectionRangeIterator iterator(selectionRanges, contentRange, |
| 7282 | *aParams.provider, mTextRun, startIOffset); |
| 7283 | AutoTArray<SelectionType, 1> selectionTypes; |
| 7284 | AutoTArray<RefPtr<nsAtom>, 1> highlightNames; |
| 7285 | AutoTArray<TextRangeStyle, 1> rangeStyles; |
| 7286 | while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth, selectionTypes, |
| 7287 | highlightNames, rangeStyles)) { |
| 7288 | nscolor foreground(0); |
| 7289 | if (aParams.IsGenerateTextMask()) { |
| 7290 | foreground = NS_RGBA(0, 0, 0, 255)((nscolor)(((255) << 24) | ((0) << 16) | ((0) << 8) | (0))); |
| 7291 | } else { |
| 7292 | nscolor fallbackForeground(0); |
| 7293 | bool colorHasBeenSet = false; |
| 7294 | for (size_t index = 0; index < selectionTypes.Length(); ++index) { |
| 7295 | const auto colors = |
| 7296 | GetSelectionTextColors(selectionTypes[index], highlightNames[index], |
| 7297 | *aParams.textPaintStyle, rangeStyles[index]); |
| 7298 | fallbackForeground = colors.mForeground; |
| 7299 | if (colors.mOverridesForeground) { |
| 7300 | foreground = colors.mForeground; |
| 7301 | colorHasBeenSet = true; |
| 7302 | } |
| 7303 | } |
| 7304 | if (!colorHasBeenSet) { |
| 7305 | foreground = fallbackForeground; |
| 7306 | } |
| 7307 | } |
| 7308 | |
| 7309 | gfx::Point textBaselinePt = |
| 7310 | vertical |
| 7311 | ? gfx::Point(aParams.textBaselinePt.x, aParams.framePt.y + iOffset) |
| 7312 | : gfx::Point(aParams.framePt.x + iOffset, aParams.textBaselinePt.y); |
| 7313 | |
| 7314 | // Determine what shadow, if any, to draw - either from textStyle |
| 7315 | // or from the ::-moz-selection pseudo-class if specified there |
| 7316 | AutoTArray<Span<const StyleSimpleShadow>, 1> shadows; |
| 7317 | bool hasSelectionShadow = false; |
| 7318 | |
| 7319 | // Collect all selection/highlight shadows first |
| 7320 | for (size_t index = 0; index < selectionTypes.Length(); ++index) { |
| 7321 | nsAtom* highlightName = index < highlightNames.Length() |
| 7322 | ? highlightNames[index].get() |
| 7323 | : nullptr; |
| 7324 | RefPtr<ComputedStyle> selectionStyle = |
| 7325 | aParams.textPaintStyle->GetComputedStyleForSelectionPseudo( |
| 7326 | selectionTypes[index], highlightName); |
| 7327 | if (selectionStyle && |
| 7328 | selectionStyle->HasAuthorOrUserSpecifiedTextShadow()) { |
| 7329 | // text-shadow was explicitly specified (including "none") |
| 7330 | hasSelectionShadow = true; |
| 7331 | Span<const StyleSimpleShadow> shadowSpan = |
| 7332 | selectionStyle->StyleText()->mTextShadow.AsSpan(); |
| 7333 | if (!shadowSpan.IsEmpty()) { |
| 7334 | shadows.AppendElement(shadowSpan); |
| 7335 | } |
| 7336 | } |
| 7337 | } |
| 7338 | |
| 7339 | // Only use element shadow if no selection shadow was explicitly specified |
| 7340 | // See https://github.com/w3c/csswg-drafts/issues/13376 for context. |
| 7341 | if (!hasSelectionShadow) { |
| 7342 | if (auto sh = textStyle->mTextShadow.AsSpan(); !sh.IsEmpty()) { |
| 7343 | shadows.AppendElement(sh); |
| 7344 | } |
| 7345 | } |
| 7346 | if (!shadows.IsEmpty()) { |
| 7347 | nscoord startEdge = iOffset; |
| 7348 | if (mTextRun->IsInlineReversed()) { |
| 7349 | startEdge -= |
| 7350 | hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider); |
| 7351 | } |
| 7352 | shadowParams.foregroundColor = foreground; |
| 7353 | shadowParams.textBaselinePt = textBaselinePt; |
| 7354 | shadowParams.framePt = aParams.framePt; |
| 7355 | shadowParams.leftSideOffset = startEdge; |
| 7356 | shadowParams.range = range; |
| 7357 | for (const Span<const StyleSimpleShadow>& shadowSpan : shadows) { |
| 7358 | PaintShadows(shadowSpan, shadowParams, aImgParams); |
| 7359 | } |
| 7360 | } |
| 7361 | |
| 7362 | // Draw text segment. -webkit-text-stroke-* are not applicable to highlight |
| 7363 | // pseudo-elements per |
| 7364 | // https://drafts.csswg.org/css-pseudo-4/#highlight-styling. Suppress stroke |
| 7365 | // for highlighted segments; non-highlighted segments (eNone) still use the |
| 7366 | // originating element's stroke. |
| 7367 | const bool isUnselected = selectionTypes.Length() == 1 && |
| 7368 | selectionTypes[0] == SelectionType::eNone; |
| 7369 | params.textColor = foreground; |
| 7370 | params.textStrokeColor = |
| 7371 | isUnselected ? aParams.textPaintStyle->GetWebkitTextStrokeColor() : 0; |
| 7372 | params.textStrokeWidth = |
| 7373 | isUnselected ? aParams.textPaintStyle->GetWebkitTextStrokeWidth() |
| 7374 | : 0.0f; |
| 7375 | params.drawSoftHyphen = hyphenWidth > 0; |
| 7376 | DrawText(range, textBaselinePt, params, aImgParams); |
| 7377 | advance += hyphenWidth; |
| 7378 | iterator.UpdateWithAdvance(advance); |
| 7379 | } |
| 7380 | return true; |
| 7381 | } |
| 7382 | |
| 7383 | void nsTextFrame::PaintTextSelectionDecorations( |
| 7384 | const PaintTextSelectionParams& aParams, const SelectionDetails& aDetails, |
| 7385 | SelectionType aSelectionType) { |
| 7386 | // Hide text decorations if we're currently hiding @font-face fallback text |
| 7387 | if (aParams.provider->GetFontGroup()->ShouldSkipDrawing()) { |
| 7388 | return; |
| 7389 | } |
| 7390 | |
| 7391 | AutoTArray<PriorityOrderedSelectionsForRange, 8> selectionRanges; |
| 7392 | ResolveSelections(aParams, aDetails, selectionRanges, aSelectionType); |
| 7393 | |
| 7394 | RefPtr<gfxFont> firstFont = |
| 7395 | aParams.provider->GetFontGroup()->GetFirstValidFont(); |
| 7396 | bool verticalRun = mTextRun->IsVertical(); |
| 7397 | bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline(); |
| 7398 | bool rightUnderline = useVerticalMetrics && IsUnderlineRight(*Style()); |
| 7399 | const auto kDecoration = rightUnderline ? StyleTextDecorationLine::OVERLINE |
| 7400 | : StyleTextDecorationLine::UNDERLINE; |
| 7401 | gfxFont::Metrics decorationMetrics( |
| 7402 | firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical |
| 7403 | : nsFontMetrics::eHorizontal)); |
| 7404 | decorationMetrics.underlineOffset = |
| 7405 | aParams.provider->GetFontGroup()->GetUnderlineOffset(); |
| 7406 | |
| 7407 | const gfxTextRun::Range& contentRange = aParams.contentRange; |
| 7408 | gfxFloat startIOffset = verticalRun |
| 7409 | ? aParams.textBaselinePt.y - aParams.framePt.y |
| 7410 | : aParams.textBaselinePt.x - aParams.framePt.x; |
| 7411 | SelectionRangeIterator iterator(selectionRanges, contentRange, |
| 7412 | *aParams.provider, mTextRun, startIOffset); |
| 7413 | gfxFloat iOffset, hyphenWidth; |
| 7414 | Range range; |
| 7415 | gfxFloat app = aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel(); |
| 7416 | // Use the parent frame's writing mode for decoration positioning, matching |
| 7417 | // DrawTextRunAndDecorations. This matters when IsCentralBaseline() is true |
| 7418 | // (e.g. vertical-lr), where GetLogicalBaseline(parentWM) differs from |
| 7419 | // mAscent. |
| 7420 | const WritingMode parentWM = GetParent()->GetWritingMode(); |
| 7421 | const bool verticalDec = parentWM.IsVertical(); |
| 7422 | gfxFloat decorationAscent = gfxFloat(GetLogicalBaseline(parentWM)) / app; |
| 7423 | gfxFloat frameBStart = verticalDec ? aParams.framePt.x : aParams.framePt.y; |
| 7424 | if (parentWM.IsVerticalRL()) { |
| 7425 | frameBStart += GetSize().width; |
| 7426 | decorationAscent = -decorationAscent; |
| 7427 | } |
| 7428 | Point pt; |
| 7429 | if (verticalRun) { |
| 7430 | pt.x = frameBStart / app; |
| 7431 | } else { |
| 7432 | pt.y = frameBStart / app; |
| 7433 | } |
| 7434 | AutoTArray<SelectionType, 1> nextSelectionTypes; |
| 7435 | AutoTArray<RefPtr<nsAtom>, 1> highlightNames; |
| 7436 | AutoTArray<TextRangeStyle, 1> selectedStyles; |
| 7437 | |
| 7438 | while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth, |
| 7439 | nextSelectionTypes, highlightNames, |
| 7440 | selectedStyles)) { |
| 7441 | gfxFloat advance = |
| 7442 | hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider); |
| 7443 | for (size_t index = 0; index < nextSelectionTypes.Length(); ++index) { |
| 7444 | if (nextSelectionTypes[index] == aSelectionType) { |
| 7445 | if (verticalRun) { |
| 7446 | pt.y = (aParams.framePt.y + iOffset - |
| 7447 | (mTextRun->IsInlineReversed() ? advance : 0)) / |
| 7448 | app; |
| 7449 | } else { |
| 7450 | pt.x = (aParams.framePt.x + iOffset - |
| 7451 | (mTextRun->IsInlineReversed() ? advance : 0)) / |
| 7452 | app; |
| 7453 | } |
| 7454 | gfxFloat width = Abs(advance) / app; |
| 7455 | gfxFloat xInFrame = pt.x - (aParams.framePt.x / app); |
| 7456 | DrawSelectionDecorations( |
| 7457 | aParams.context, aParams.dirtyRect, aSelectionType, |
| 7458 | highlightNames[index], *aParams.textPaintStyle, |
| 7459 | selectedStyles[index], pt, xInFrame, width, decorationAscent, |
| 7460 | decorationMetrics, aParams.callbacks, verticalRun, kDecoration, |
| 7461 | aParams.glyphRange, aParams.provider); |
| 7462 | } |
| 7463 | } |
| 7464 | iterator.UpdateWithAdvance(advance); |
| 7465 | } |
| 7466 | } |
| 7467 | |
| 7468 | bool nsTextFrame::PaintTextWithSelection( |
| 7469 | const PaintTextSelectionParams& aParams, const ClipEdges& aClipEdges, |
| 7470 | const SelectionDetails& aDetails, imgDrawingParams& aImgParams) { |
| 7471 | NS_ASSERTION(GetContent()->IsMaybeSelected(), "wrong paint path")do { if (!(GetContent()->IsMaybeSelected())) { NS_DebugBreak (NS_DEBUG_ASSERTION, "wrong paint path", "GetContent()->IsMaybeSelected()" , "./../../../layout/generic/nsTextFrame.cpp", 7471); MOZ_PretendNoReturn (); } } while (0); |
| 7472 | |
| 7473 | SelectionTypeMask allSelectionTypeMask; |
| 7474 | if (!PaintTextWithSelectionColors(aParams, aDetails, &allSelectionTypeMask, |
| 7475 | aClipEdges, aImgParams)) { |
| 7476 | return false; |
| 7477 | } |
| 7478 | // Iterate through just the selection rawSelectionTypes that paint decorations |
| 7479 | // and paint decorations for any that actually occur in this frame. Paint |
| 7480 | // higher-numbered selection rawSelectionTypes below lower-numered ones on the |
| 7481 | // general principal that lower-numbered selections are higher priority. |
| 7482 | allSelectionTypeMask &= kSelectionTypesWithDecorations; |
| 7483 | MOZ_ASSERT(kPresentSelectionTypes[0] == SelectionType::eNormal,do { static_assert( mozilla::detail::AssertionConditionType< decltype(kPresentSelectionTypes[0] == SelectionType::eNormal) >::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(kPresentSelectionTypes[0] == SelectionType::eNormal) )), 0))) { do { } while (false); MOZ_ReportAssertionFailure("kPresentSelectionTypes[0] == SelectionType::eNormal" " (" "The following for loop assumes that the first item of " "kPresentSelectionTypes is SelectionType::eNormal" ")", "./../../../layout/generic/nsTextFrame.cpp" , 7485); AnnotateMozCrashReason("MOZ_ASSERT" "(" "kPresentSelectionTypes[0] == SelectionType::eNormal" ") (" "The following for loop assumes that the first item of " "kPresentSelectionTypes is SelectionType::eNormal" ")"); do { MOZ_CrashSequence(__null, 7485); __attribute__((nomerge)) :: abort(); } while (false); } } while (false) |
| 7484 | "The following for loop assumes that the first item of "do { static_assert( mozilla::detail::AssertionConditionType< decltype(kPresentSelectionTypes[0] == SelectionType::eNormal) >::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(kPresentSelectionTypes[0] == SelectionType::eNormal) )), 0))) { do { } while (false); MOZ_ReportAssertionFailure("kPresentSelectionTypes[0] == SelectionType::eNormal" " (" "The following for loop assumes that the first item of " "kPresentSelectionTypes is SelectionType::eNormal" ")", "./../../../layout/generic/nsTextFrame.cpp" , 7485); AnnotateMozCrashReason("MOZ_ASSERT" "(" "kPresentSelectionTypes[0] == SelectionType::eNormal" ") (" "The following for loop assumes that the first item of " "kPresentSelectionTypes is SelectionType::eNormal" ")"); do { MOZ_CrashSequence(__null, 7485); __attribute__((nomerge)) :: abort(); } while (false); } } while (false) |
| 7485 | "kPresentSelectionTypes is SelectionType::eNormal")do { static_assert( mozilla::detail::AssertionConditionType< decltype(kPresentSelectionTypes[0] == SelectionType::eNormal) >::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(kPresentSelectionTypes[0] == SelectionType::eNormal) )), 0))) { do { } while (false); MOZ_ReportAssertionFailure("kPresentSelectionTypes[0] == SelectionType::eNormal" " (" "The following for loop assumes that the first item of " "kPresentSelectionTypes is SelectionType::eNormal" ")", "./../../../layout/generic/nsTextFrame.cpp" , 7485); AnnotateMozCrashReason("MOZ_ASSERT" "(" "kPresentSelectionTypes[0] == SelectionType::eNormal" ") (" "The following for loop assumes that the first item of " "kPresentSelectionTypes is SelectionType::eNormal" ")"); do { MOZ_CrashSequence(__null, 7485); __attribute__((nomerge)) :: abort(); } while (false); } } while (false); |
| 7486 | |
| 7487 | Span presentTypes(kPresentSelectionTypes); |
| 7488 | for (SelectionType selectionType : Reversed(presentTypes)) { |
| 7489 | if (ToSelectionTypeMask(selectionType) & allSelectionTypeMask) { |
| 7490 | // There is some selection of this selectionType. Try to paint its |
| 7491 | // decorations (there might not be any for this type but that's OK, |
| 7492 | // PaintTextSelectionDecorations will exit early). |
| 7493 | PaintTextSelectionDecorations(aParams, aDetails, selectionType); |
| 7494 | } |
| 7495 | } |
| 7496 | |
| 7497 | return true; |
| 7498 | } |
| 7499 | |
| 7500 | void nsTextFrame::DrawEmphasisMarks(gfxContext* aContext, WritingMode aWM, |
| 7501 | const gfx::Point& aTextBaselinePt, |
| 7502 | const gfx::Point& aFramePt, Range aRange, |
| 7503 | const nscolor* aDecorationOverrideColor, |
| 7504 | PropertyProvider* aProvider, |
| 7505 | image::imgDrawingParams& aImgParams) { |
| 7506 | const EmphasisMarkInfo* info = GetProperty(EmphasisMarkProperty()); |
| 7507 | if (!info) { |
| 7508 | return; |
| 7509 | } |
| 7510 | |
| 7511 | bool isTextCombined = Style()->IsTextCombined(); |
| 7512 | if (isTextCombined && !aWM.IsVertical()) { |
| 7513 | // XXX This only happens when the parent is display:contents with an |
| 7514 | // orthogonal writing mode. This should be rare, and don't have use |
| 7515 | // cases, so we don't care. It is non-trivial to implement a sane |
| 7516 | // behavior for that case: if you treat the text as not combined, |
| 7517 | // the marks would spread wider than the text (which is rendered as |
| 7518 | // combined); if you try to draw a single mark, selecting part of |
| 7519 | // the text could dynamically create multiple new marks. |
| 7520 | NS_WARNING("Give up on combined text with horizontal wm")NS_DebugBreak(NS_DEBUG_WARNING, "Give up on combined text with horizontal wm" , nullptr, "./../../../layout/generic/nsTextFrame.cpp", 7520); |
| 7521 | return; |
| 7522 | } |
| 7523 | nscolor color = |
| 7524 | aDecorationOverrideColor |
| 7525 | ? *aDecorationOverrideColor |
| 7526 | : nsLayoutUtils::GetTextColor(this, &nsStyleText::mTextEmphasisColor); |
| 7527 | aContext->SetColor(sRGBColor::FromABGR(color)); |
| 7528 | gfx::Point pt; |
| 7529 | if (!isTextCombined) { |
| 7530 | pt = aTextBaselinePt; |
| 7531 | } else { |
| 7532 | MOZ_ASSERT(aWM.IsVertical())do { static_assert( mozilla::detail::AssertionConditionType< decltype(aWM.IsVertical())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aWM.IsVertical()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aWM.IsVertical()" , "./../../../layout/generic/nsTextFrame.cpp", 7532); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aWM.IsVertical()" ")"); do { MOZ_CrashSequence (__null, 7532); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 7533 | pt = aFramePt; |
| 7534 | if (aWM.IsVerticalRL()) { |
| 7535 | pt.x += GetSize().width - GetLogicalBaseline(aWM); |
| 7536 | } else { |
| 7537 | pt.x += GetLogicalBaseline(aWM); |
| 7538 | } |
| 7539 | } |
| 7540 | if (!aWM.IsVertical()) { |
| 7541 | pt.y += info->baselineOffset; |
| 7542 | } else { |
| 7543 | if (aWM.IsVerticalRL()) { |
| 7544 | pt.x -= info->baselineOffset; |
| 7545 | } else { |
| 7546 | pt.x += info->baselineOffset; |
| 7547 | } |
| 7548 | } |
| 7549 | if (!isTextCombined) { |
| 7550 | mTextRun->DrawEmphasisMarks(aContext, info->textRun.get(), info->advance, |
| 7551 | pt, aRange, aProvider, |
| 7552 | PresContext()->FontPaletteCache(), aImgParams); |
| 7553 | } else { |
| 7554 | pt.y += (GetSize().height - info->advance) / 2; |
| 7555 | gfxTextRun::DrawParams params(aContext, PresContext()->FontPaletteCache()); |
| 7556 | info->textRun->Draw(Range(info->textRun.get()), pt, params, aImgParams); |
| 7557 | } |
| 7558 | } |
| 7559 | |
| 7560 | nscolor nsTextFrame::GetCaretColorAt(int32_t aOffset) { |
| 7561 | MOZ_ASSERT(aOffset >= 0, "aOffset must be positive")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aOffset >= 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aOffset >= 0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aOffset >= 0" " (" "aOffset must be positive" ")", "./../../../layout/generic/nsTextFrame.cpp" , 7561); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aOffset >= 0" ") (" "aOffset must be positive" ")"); do { MOZ_CrashSequence (__null, 7561); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 7562 | |
| 7563 | nscolor result = nsIFrame::GetCaretColorAt(aOffset); |
| 7564 | gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); |
| 7565 | PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics); |
| 7566 | int32_t contentOffset = provider.GetStart().GetOriginalOffset(); |
| 7567 | int32_t contentLength = provider.GetOriginalLength(); |
| 7568 | MOZ_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType< decltype(aOffset >= contentOffset && aOffset <= contentOffset + contentLength)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aOffset >= contentOffset && aOffset <= contentOffset + contentLength))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aOffset >= contentOffset && aOffset <= contentOffset + contentLength" " (" "aOffset must be in the frame's range" ")", "./../../../layout/generic/nsTextFrame.cpp" , 7570); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aOffset >= contentOffset && aOffset <= contentOffset + contentLength" ") (" "aOffset must be in the frame's range" ")"); do { MOZ_CrashSequence (__null, 7570); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 7569 | aOffset >= contentOffset && aOffset <= contentOffset + contentLength,do { static_assert( mozilla::detail::AssertionConditionType< decltype(aOffset >= contentOffset && aOffset <= contentOffset + contentLength)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aOffset >= contentOffset && aOffset <= contentOffset + contentLength))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aOffset >= contentOffset && aOffset <= contentOffset + contentLength" " (" "aOffset must be in the frame's range" ")", "./../../../layout/generic/nsTextFrame.cpp" , 7570); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aOffset >= contentOffset && aOffset <= contentOffset + contentLength" ") (" "aOffset must be in the frame's range" ")"); do { MOZ_CrashSequence (__null, 7570); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 7570 | "aOffset must be in the frame's range")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aOffset >= contentOffset && aOffset <= contentOffset + contentLength)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aOffset >= contentOffset && aOffset <= contentOffset + contentLength))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aOffset >= contentOffset && aOffset <= contentOffset + contentLength" " (" "aOffset must be in the frame's range" ")", "./../../../layout/generic/nsTextFrame.cpp" , 7570); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aOffset >= contentOffset && aOffset <= contentOffset + contentLength" ") (" "aOffset must be in the frame's range" ")"); do { MOZ_CrashSequence (__null, 7570); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 7571 | |
| 7572 | int32_t offsetInFrame = aOffset - contentOffset; |
| 7573 | if (offsetInFrame < 0 || offsetInFrame >= contentLength) { |
| 7574 | return result; |
| 7575 | } |
| 7576 | |
| 7577 | bool isSolidTextColor = true; |
| 7578 | if (IsInSVGTextSubtree()) { |
| 7579 | const nsStyleSVG* style = StyleSVG(); |
| 7580 | if (!style->mFill.kind.IsNone() && !style->mFill.kind.IsColor()) { |
| 7581 | isSolidTextColor = false; |
| 7582 | } |
| 7583 | } |
| 7584 | |
| 7585 | nsTextPaintStyle textPaintStyle(this); |
| 7586 | textPaintStyle.SetResolveColors(isSolidTextColor); |
| 7587 | UniquePtr<SelectionDetails> details = GetSelectionDetails(); |
| 7588 | SelectionType selectionType = SelectionType::eNone; |
| 7589 | for (SelectionDetails* sdptr = details.get(); sdptr; |
| 7590 | sdptr = sdptr->mNext.get()) { |
| 7591 | int32_t start = std::max(0, sdptr->mStart - contentOffset); |
| 7592 | int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset); |
| 7593 | if (start <= offsetInFrame && offsetInFrame < end && |
| 7594 | (selectionType == SelectionType::eNone || |
| 7595 | sdptr->mSelectionType < selectionType)) { |
| 7596 | const auto colors = GetSelectionTextColors( |
| 7597 | sdptr->mSelectionType, sdptr->mHighlightData.mHighlightName, |
| 7598 | textPaintStyle, sdptr->mTextRangeStyle); |
| 7599 | if (colors.HasAnyColorImpact()) { |
| 7600 | if (!isSolidTextColor && |
| 7601 | NS_IS_SELECTION_SPECIAL_COLOR(colors.mForeground)((colors.mForeground) == NS_TRANSPARENT || (colors.mForeground ) == NS_SAME_AS_FOREGROUND_COLOR || (colors.mForeground) == NS_40PERCENT_FOREGROUND_COLOR )) { |
| 7602 | result = NS_RGBA(0, 0, 0, 255)((nscolor)(((255) << 24) | ((0) << 16) | ((0) << 8) | (0))); |
| 7603 | } else { |
| 7604 | result = colors.mForeground; |
| 7605 | } |
| 7606 | selectionType = sdptr->mSelectionType; |
| 7607 | } |
| 7608 | } |
| 7609 | } |
| 7610 | |
| 7611 | return result; |
| 7612 | } |
| 7613 | |
| 7614 | static gfxTextRun::Range ComputeTransformedRange( |
| 7615 | nsTextFrame::PropertyProvider& aProvider) { |
| 7616 | gfxSkipCharsIterator iter(aProvider.GetStart()); |
| 7617 | uint32_t start = iter.GetSkippedOffset(); |
| 7618 | iter.AdvanceOriginal(aProvider.GetOriginalLength()); |
| 7619 | return gfxTextRun::Range(start, iter.GetSkippedOffset()); |
| 7620 | } |
| 7621 | |
| 7622 | bool nsTextFrame::MeasureCharClippedText(nscoord aVisIStartEdge, |
| 7623 | nscoord aVisIEndEdge, |
| 7624 | nscoord* aSnappedStartEdge, |
| 7625 | nscoord* aSnappedEndEdge) { |
| 7626 | // We need a *reference* rendering context (not one that might have a |
| 7627 | // transform), so we don't have a rendering context argument. |
| 7628 | // XXX get the block and line passed to us somehow! This is slow! |
| 7629 | gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); |
| 7630 | if (!mTextRun) { |
| 7631 | return false; |
| 7632 | } |
| 7633 | |
| 7634 | PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics); |
| 7635 | // Trim trailing whitespace |
| 7636 | provider.InitializeForDisplay(true); |
| 7637 | |
| 7638 | Range range = ComputeTransformedRange(provider); |
| 7639 | uint32_t startOffset = range.start; |
| 7640 | uint32_t maxLength = range.Length(); |
| 7641 | return MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge, |
| 7642 | &startOffset, &maxLength, aSnappedStartEdge, |
| 7643 | aSnappedEndEdge); |
| 7644 | } |
| 7645 | |
| 7646 | static uint32_t GetClusterLength(const gfxTextRun* aTextRun, |
| 7647 | uint32_t aStartOffset, uint32_t aMaxLength) { |
| 7648 | uint32_t clusterLength = 0; |
| 7649 | while (++clusterLength < aMaxLength) { |
| 7650 | if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) { |
| 7651 | return clusterLength; |
| 7652 | } |
| 7653 | } |
| 7654 | return aMaxLength; |
| 7655 | } |
| 7656 | |
| 7657 | bool nsTextFrame::MeasureCharClippedText( |
| 7658 | PropertyProvider& aProvider, nscoord aVisIStartEdge, nscoord aVisIEndEdge, |
| 7659 | uint32_t* aStartOffset, uint32_t* aMaxLength, nscoord* aSnappedStartEdge, |
| 7660 | nscoord* aSnappedEndEdge) { |
| 7661 | *aSnappedStartEdge = 0; |
| 7662 | *aSnappedEndEdge = 0; |
| 7663 | if (aVisIStartEdge <= 0 && aVisIEndEdge <= 0) { |
| 7664 | return true; |
| 7665 | } |
| 7666 | |
| 7667 | uint32_t offset = *aStartOffset; |
| 7668 | uint32_t maxLength = *aMaxLength; |
| 7669 | const nscoord frameISize = ISize(); |
| 7670 | const bool rtl = mTextRun->IsRightToLeft(); |
| 7671 | gfxFloat advanceWidth = 0; |
| 7672 | const nscoord startEdge = rtl ? aVisIEndEdge : aVisIStartEdge; |
| 7673 | if (startEdge > 0) { |
| 7674 | const gfxFloat maxAdvance = gfxFloat(startEdge); |
| 7675 | while (maxLength > 0) { |
| 7676 | uint32_t clusterLength = GetClusterLength(mTextRun, offset, maxLength); |
| 7677 | advanceWidth += mTextRun->GetAdvanceWidth( |
| 7678 | Range(offset, offset + clusterLength), &aProvider); |
| 7679 | maxLength -= clusterLength; |
| 7680 | offset += clusterLength; |
| 7681 | if (advanceWidth >= maxAdvance) { |
| 7682 | break; |
| 7683 | } |
| 7684 | } |
| 7685 | nscoord* snappedStartEdge = rtl ? aSnappedEndEdge : aSnappedStartEdge; |
| 7686 | *snappedStartEdge = NSToCoordFloor(advanceWidth); |
| 7687 | *aStartOffset = offset; |
| 7688 | } |
| 7689 | |
| 7690 | const nscoord endEdge = rtl ? aVisIStartEdge : aVisIEndEdge; |
| 7691 | if (endEdge > 0) { |
| 7692 | const gfxFloat maxAdvance = gfxFloat(frameISize - endEdge); |
| 7693 | while (maxLength > 0) { |
| 7694 | uint32_t clusterLength = GetClusterLength(mTextRun, offset, maxLength); |
| 7695 | gfxFloat nextAdvance = |
| 7696 | advanceWidth + mTextRun->GetAdvanceWidth( |
| 7697 | Range(offset, offset + clusterLength), &aProvider); |
| 7698 | if (nextAdvance > maxAdvance) { |
| 7699 | break; |
| 7700 | } |
| 7701 | // This cluster fits, include it. |
| 7702 | advanceWidth = nextAdvance; |
| 7703 | maxLength -= clusterLength; |
| 7704 | offset += clusterLength; |
| 7705 | } |
| 7706 | maxLength = offset - *aStartOffset; |
| 7707 | nscoord* snappedEndEdge = rtl ? aSnappedStartEdge : aSnappedEndEdge; |
| 7708 | *snappedEndEdge = NSToCoordFloor(gfxFloat(frameISize) - advanceWidth); |
| 7709 | } |
| 7710 | *aMaxLength = maxLength; |
| 7711 | return maxLength != 0; |
| 7712 | } |
| 7713 | |
| 7714 | void nsTextFrame::PaintShadows(Span<const StyleSimpleShadow> aShadows, |
| 7715 | const PaintShadowParams& aParams, |
| 7716 | imgDrawingParams& aImgParams) { |
| 7717 | if (aShadows.IsEmpty()) { |
| 7718 | return; |
| 7719 | } |
| 7720 | |
| 7721 | gfxTextRun::Metrics shadowMetrics = mTextRun->MeasureText( |
| 7722 | aParams.range, gfxFont::LOOSE_INK_EXTENTS, nullptr, aParams.provider); |
| 7723 | if (GetWritingMode().IsLineInverted()) { |
| 7724 | std::swap(shadowMetrics.mAscent, shadowMetrics.mDescent); |
| 7725 | shadowMetrics.mBoundingBox.y = -shadowMetrics.mBoundingBox.YMost(); |
| 7726 | } |
| 7727 | if (HasAnyStateBits(TEXT_HYPHEN_BREAK)) { |
| 7728 | AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &shadowMetrics, |
| 7729 | gfxFont::LOOSE_INK_EXTENTS, |
| 7730 | aParams.context->GetDrawTarget()); |
| 7731 | } |
| 7732 | // Add bounds of text decorations |
| 7733 | gfxRect decorationRect(0, -shadowMetrics.mAscent, shadowMetrics.mAdvanceWidth, |
| 7734 | shadowMetrics.mAscent + shadowMetrics.mDescent); |
| 7735 | shadowMetrics.mBoundingBox.UnionRect(shadowMetrics.mBoundingBox, |
| 7736 | decorationRect); |
| 7737 | |
| 7738 | // If the textrun uses any color or SVG fonts, we need to force use of a mask |
| 7739 | // for shadow rendering even if blur radius is zero. |
| 7740 | uint32_t blurFlags = 0; |
| 7741 | uint32_t numGlyphRuns; |
| 7742 | const gfxTextRun::GlyphRun* run = mTextRun->GetGlyphRuns(&numGlyphRuns); |
| 7743 | while (numGlyphRuns-- > 0) { |
| 7744 | if (run->mFont->AlwaysNeedsMaskForShadow()) { |
| 7745 | blurFlags |= nsContextBoxBlur::FORCE_MASK; |
| 7746 | break; |
| 7747 | } |
| 7748 | run++; |
| 7749 | } |
| 7750 | |
| 7751 | if (mTextRun->IsVertical()) { |
| 7752 | std::swap(shadowMetrics.mBoundingBox.x, shadowMetrics.mBoundingBox.y); |
| 7753 | std::swap(shadowMetrics.mBoundingBox.width, |
| 7754 | shadowMetrics.mBoundingBox.height); |
| 7755 | } |
| 7756 | |
| 7757 | for (const auto& shadow : Reversed(aShadows)) { |
| 7758 | PaintOneShadow(aParams, shadow, shadowMetrics.mBoundingBox, blurFlags, |
| 7759 | aImgParams); |
| 7760 | } |
| 7761 | } |
| 7762 | |
| 7763 | void nsTextFrame::PaintText(const PaintTextParams& aParams, |
| 7764 | const nscoord aVisIStartEdge, |
| 7765 | const nscoord aVisIEndEdge, |
| 7766 | const nsPoint& aToReferenceFrame, |
| 7767 | const bool aIsSelected, |
| 7768 | imgDrawingParams& aImgParams, |
| 7769 | float aOpacity /* = 1.0f */) { |
| 7770 | #ifdef DEBUG1 |
| 7771 | if (IsInSVGTextSubtree()) { |
| 7772 | auto* container = |
| 7773 | nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::SVGText); |
| 7774 | MOZ_ASSERT(container)do { static_assert( mozilla::detail::AssertionConditionType< decltype(container)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(container))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("container", "./../../../layout/generic/nsTextFrame.cpp" , 7774); AnnotateMozCrashReason("MOZ_ASSERT" "(" "container" ")" ); do { MOZ_CrashSequence(__null, 7774); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 7775 | MOZ_ASSERT(!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ||do { static_assert( mozilla::detail::AssertionConditionType< decltype(!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD ) || !aParams.IsPaintText())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!container->HasAnyStateBits (NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()" " (" "Expecting IsPaintText to be false for a clipPath" ")", "./../../../layout/generic/nsTextFrame.cpp", 7777); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()" ") (" "Expecting IsPaintText to be false for a clipPath" ")" ); do { MOZ_CrashSequence(__null, 7777); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
| 7776 | !aParams.IsPaintText(),do { static_assert( mozilla::detail::AssertionConditionType< decltype(!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD ) || !aParams.IsPaintText())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!container->HasAnyStateBits (NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()" " (" "Expecting IsPaintText to be false for a clipPath" ")", "./../../../layout/generic/nsTextFrame.cpp", 7777); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()" ") (" "Expecting IsPaintText to be false for a clipPath" ")" ); do { MOZ_CrashSequence(__null, 7777); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
| 7777 | "Expecting IsPaintText to be false for a clipPath")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD ) || !aParams.IsPaintText())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!container->HasAnyStateBits (NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()" " (" "Expecting IsPaintText to be false for a clipPath" ")", "./../../../layout/generic/nsTextFrame.cpp", 7777); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) || !aParams.IsPaintText()" ") (" "Expecting IsPaintText to be false for a clipPath" ")" ); do { MOZ_CrashSequence(__null, 7777); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 7778 | } |
| 7779 | #endif |
| 7780 | |
| 7781 | // Don't pass in the rendering context here, because we need a |
| 7782 | // *reference* context and rendering context might have some transform |
| 7783 | // in it |
| 7784 | // XXX get the block and line passed to us somehow! This is slow! |
| 7785 | gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); |
| 7786 | if (!mTextRun) { |
| 7787 | return; |
| 7788 | } |
| 7789 | |
| 7790 | PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics); |
| 7791 | |
| 7792 | // Trim trailing whitespace, unless we have a normal selection (bug 1146754) |
| 7793 | // This matches behavior in other browsers. |
| 7794 | UniquePtr<SelectionDetails> selectionDetails; |
| 7795 | bool hasNormalSelection = false; |
| 7796 | if (aIsSelected) { |
| 7797 | selectionDetails = GetSelectionDetails(); |
| 7798 | for (const SelectionDetails* sd = selectionDetails.get(); sd; |
| 7799 | sd = sd->mNext.get()) { |
| 7800 | if (sd->mSelectionType == SelectionType::eNormal) { |
| 7801 | hasNormalSelection = true; |
| 7802 | break; |
| 7803 | } |
| 7804 | } |
| 7805 | } |
| 7806 | provider.InitializeForDisplay(!hasNormalSelection); |
| 7807 | |
| 7808 | const bool reversed = mTextRun->IsInlineReversed(); |
| 7809 | const bool verticalRun = mTextRun->IsVertical(); |
| 7810 | WritingMode wm = GetWritingMode(); |
| 7811 | const float frameWidth = GetSize().width; |
| 7812 | const float frameHeight = GetSize().height; |
| 7813 | gfx::Point textBaselinePt; |
| 7814 | if (verticalRun) { |
| 7815 | if (wm.IsVerticalLR()) { |
| 7816 | textBaselinePt.x = nsLayoutUtils::GetMaybeSnappedBaselineX( |
| 7817 | this, aParams.context, nscoord(aParams.framePt.x), mAscent); |
| 7818 | } else { |
| 7819 | textBaselinePt.x = nsLayoutUtils::GetMaybeSnappedBaselineX( |
| 7820 | this, aParams.context, nscoord(aParams.framePt.x) + frameWidth, |
| 7821 | -mAscent); |
| 7822 | } |
| 7823 | textBaselinePt.y = reversed ? aParams.framePt.y.value + frameHeight |
| 7824 | : aParams.framePt.y.value; |
| 7825 | } else { |
| 7826 | textBaselinePt = |
| 7827 | gfx::Point(reversed ? aParams.framePt.x.value + frameWidth |
| 7828 | : aParams.framePt.x.value, |
| 7829 | nsLayoutUtils::GetMaybeSnappedBaselineY( |
| 7830 | this, aParams.context, aParams.framePt.y, mAscent)); |
| 7831 | } |
| 7832 | Range range = ComputeTransformedRange(provider); |
| 7833 | uint32_t startOffset = range.start; |
| 7834 | uint32_t maxLength = range.Length(); |
| 7835 | nscoord snappedStartEdge, snappedEndEdge; |
| 7836 | if (!MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge, |
| 7837 | &startOffset, &maxLength, &snappedStartEdge, |
| 7838 | &snappedEndEdge)) { |
| 7839 | return; |
| 7840 | } |
| 7841 | if (verticalRun) { |
| 7842 | textBaselinePt.y += reversed ? -snappedEndEdge : snappedStartEdge; |
| 7843 | } else { |
| 7844 | textBaselinePt.x += reversed ? -snappedEndEdge : snappedStartEdge; |
| 7845 | } |
| 7846 | const ClipEdges clipEdges(this, aToReferenceFrame, snappedStartEdge, |
| 7847 | snappedEndEdge); |
| 7848 | nsTextPaintStyle textPaintStyle(this); |
| 7849 | textPaintStyle.SetResolveColors(!aParams.callbacks); |
| 7850 | |
| 7851 | // Fork off to the (slower) paint-with-selection path if necessary. |
| 7852 | if (aIsSelected && selectionDetails) { |
| 7853 | MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aOpacity == 1.0f)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aOpacity == 1.0f))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aOpacity == 1.0f" " (" "We don't support opacity with selections!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 7853); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aOpacity == 1.0f" ") (" "We don't support opacity with selections!" ")"); do { MOZ_CrashSequence(__null, 7853); __attribute__((nomerge)) :: abort(); } while (false); } } while (false); |
| 7854 | gfxSkipCharsIterator tmp(provider.GetStart()); |
| 7855 | Range contentRange( |
| 7856 | uint32_t(tmp.ConvertSkippedToOriginal(startOffset)), |
| 7857 | uint32_t(tmp.ConvertSkippedToOriginal(startOffset + maxLength))); |
| 7858 | PaintTextSelectionParams params(aParams); |
| 7859 | params.textBaselinePt = textBaselinePt; |
| 7860 | params.provider = &provider; |
| 7861 | params.contentRange = contentRange; |
| 7862 | params.textPaintStyle = &textPaintStyle; |
| 7863 | params.glyphRange = range; |
| 7864 | if (PaintTextWithSelection(params, clipEdges, *selectionDetails, |
| 7865 | aImgParams)) { |
| 7866 | return; |
| 7867 | } |
| 7868 | } |
| 7869 | |
| 7870 | nscolor foregroundColor = aParams.IsGenerateTextMask() |
| 7871 | ? NS_RGBA(0, 0, 0, 255)((nscolor)(((255) << 24) | ((0) << 16) | ((0) << 8) | (0))) |
| 7872 | : textPaintStyle.GetTextColor(); |
| 7873 | if (aOpacity != 1.0f) { |
| 7874 | gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(foregroundColor); |
| 7875 | gfxColor.a *= aOpacity; |
| 7876 | foregroundColor = gfxColor.ToABGR(); |
| 7877 | } |
| 7878 | |
| 7879 | nscolor textStrokeColor = aParams.IsGenerateTextMask() |
| 7880 | ? NS_RGBA(0, 0, 0, 255)((nscolor)(((255) << 24) | ((0) << 16) | ((0) << 8) | (0))) |
| 7881 | : textPaintStyle.GetWebkitTextStrokeColor(); |
| 7882 | if (aOpacity != 1.0f) { |
| 7883 | gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(textStrokeColor); |
| 7884 | gfxColor.a *= aOpacity; |
| 7885 | textStrokeColor = gfxColor.ToABGR(); |
| 7886 | } |
| 7887 | |
| 7888 | range = Range(startOffset, startOffset + maxLength); |
| 7889 | if (aParams.IsPaintText()) { |
| 7890 | const nsStyleText* textStyle = StyleText(); |
| 7891 | PaintShadowParams shadowParams(aParams); |
| 7892 | shadowParams.range = range; |
| 7893 | shadowParams.textBaselinePt = textBaselinePt; |
| 7894 | shadowParams.leftSideOffset = snappedStartEdge; |
| 7895 | shadowParams.provider = &provider; |
| 7896 | shadowParams.callbacks = aParams.callbacks; |
| 7897 | shadowParams.foregroundColor = foregroundColor; |
| 7898 | shadowParams.clipEdges = &clipEdges; |
| 7899 | PaintShadows(textStyle->mTextShadow.AsSpan(), shadowParams, aImgParams); |
| 7900 | } |
| 7901 | |
| 7902 | gfxFloat advanceWidth; |
| 7903 | DrawTextParams params(aParams.context, PresContext()->FontPaletteCache()); |
| 7904 | params.dirtyRect = aParams.dirtyRect; |
| 7905 | params.framePt = aParams.framePt; |
| 7906 | params.provider = &provider; |
| 7907 | params.advanceWidth = &advanceWidth; |
| 7908 | params.textStyle = &textPaintStyle; |
| 7909 | params.textColor = foregroundColor; |
| 7910 | params.textStrokeColor = textStrokeColor; |
| 7911 | params.textStrokeWidth = textPaintStyle.GetWebkitTextStrokeWidth(); |
| 7912 | params.clipEdges = &clipEdges; |
| 7913 | params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK); |
| 7914 | params.contextPaint = aParams.contextPaint; |
| 7915 | params.callbacks = aParams.callbacks; |
| 7916 | params.glyphRange = range; |
| 7917 | params.fontPalette = StyleFont()->GetFontPaletteAtom(); |
| 7918 | params.hasTextShadow = !StyleText()->mTextShadow.IsEmpty(); |
| 7919 | |
| 7920 | DrawText(range, textBaselinePt, params, aImgParams); |
| 7921 | } |
| 7922 | |
| 7923 | static void DrawTextRun(const gfxTextRun* aTextRun, |
| 7924 | const gfx::Point& aTextBaselinePt, |
| 7925 | gfxTextRun::Range aRange, |
| 7926 | const nsTextFrame::DrawTextRunParams& aParams, |
| 7927 | mozilla::image::imgDrawingParams& aImgParams, |
| 7928 | nsTextFrame* aFrame) { |
| 7929 | gfxTextRun::DrawParams params(aParams.context, aParams.paletteCache); |
| 7930 | params.provider = aParams.provider; |
| 7931 | params.advanceWidth = aParams.advanceWidth; |
| 7932 | params.contextPaint = aParams.contextPaint; |
| 7933 | params.fontPalette = aParams.fontPalette; |
| 7934 | params.callbacks = aParams.callbacks; |
| 7935 | params.hasTextShadow = aParams.hasTextShadow; |
| 7936 | if (aParams.callbacks) { |
| 7937 | aParams.callbacks->NotifyBeforeText(aParams.paintingShadows, |
| 7938 | aParams.textColor); |
| 7939 | params.drawMode = DrawMode::GLYPH_PATH; |
| 7940 | aTextRun->Draw(aRange, aTextBaselinePt, params, aImgParams); |
| 7941 | aParams.callbacks->NotifyAfterText(); |
| 7942 | } else { |
| 7943 | auto* textDrawer = aParams.context->GetTextDrawer(); |
| 7944 | if (NS_GET_A(aParams.textColor)((uint8_t)(((aParams.textColor) >> 24) & 0xff)) != 0 || textDrawer || |
| 7945 | aParams.textStrokeWidth == 0.0f) { |
| 7946 | aParams.context->SetColor(sRGBColor::FromABGR(aParams.textColor)); |
| 7947 | } else { |
| 7948 | params.drawMode = DrawMode::GLYPH_STROKE; |
| 7949 | } |
| 7950 | |
| 7951 | if ((NS_GET_A(aParams.textStrokeColor)((uint8_t)(((aParams.textStrokeColor) >> 24) & 0xff )) != 0 || textDrawer) && |
| 7952 | aParams.textStrokeWidth != 0.0f) { |
| 7953 | if (textDrawer) { |
| 7954 | textDrawer->FoundUnsupportedFeature(); |
| 7955 | return; |
| 7956 | } |
| 7957 | params.drawMode |= DrawMode::GLYPH_STROKE; |
| 7958 | |
| 7959 | // Check the paint-order property; if we find stroke before fill, |
| 7960 | // then change mode to GLYPH_STROKE_UNDERNEATH. |
| 7961 | uint32_t paintOrder = aFrame->StyleSVG()->mPaintOrder; |
| 7962 | while (paintOrder) { |
| 7963 | auto component = StylePaintOrder(paintOrder & kPaintOrderMask); |
| 7964 | switch (component) { |
| 7965 | case StylePaintOrder::Fill: |
| 7966 | // Just break the loop, no need to check further |
| 7967 | paintOrder = 0; |
| 7968 | break; |
| 7969 | case StylePaintOrder::Stroke: |
| 7970 | params.drawMode |= DrawMode::GLYPH_STROKE_UNDERNEATH; |
| 7971 | paintOrder = 0; |
| 7972 | break; |
| 7973 | default: |
| 7974 | MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?")do { do { } while (false); MOZ_ReportCrash("" "MOZ_FALLTHROUGH_ASSERT: " "Unknown paint-order variant, how?", "./../../../layout/generic/nsTextFrame.cpp" , 7974); AnnotateMozCrashReason("MOZ_CRASH(" "MOZ_FALLTHROUGH_ASSERT: " "Unknown paint-order variant, how?" ")"); do { MOZ_CrashSequence (__null, 7974); __attribute__((nomerge)) ::abort(); } while ( false); } while (false); |
| 7975 | case StylePaintOrder::Markers: |
| 7976 | case StylePaintOrder::Normal: |
| 7977 | break; |
| 7978 | } |
| 7979 | paintOrder >>= kPaintOrderShift; |
| 7980 | } |
| 7981 | |
| 7982 | // Use ROUND joins as they are less likely to produce ugly artifacts |
| 7983 | // when stroking glyphs with sharp angles (see bug 1546985). |
| 7984 | StrokeOptions strokeOpts(aParams.textStrokeWidth, JoinStyle::ROUND); |
| 7985 | params.textStrokeColor = aParams.textStrokeColor; |
| 7986 | params.strokeOpts = &strokeOpts; |
| 7987 | aTextRun->Draw(aRange, aTextBaselinePt, params, aImgParams); |
| 7988 | } else { |
| 7989 | aTextRun->Draw(aRange, aTextBaselinePt, params, aImgParams); |
| 7990 | } |
| 7991 | } |
| 7992 | } |
| 7993 | |
| 7994 | void nsTextFrame::DrawTextRun(Range aRange, const gfx::Point& aTextBaselinePt, |
| 7995 | const DrawTextRunParams& aParams, |
| 7996 | imgDrawingParams& aImgParams) { |
| 7997 | MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aParams.advanceWidth)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aParams.advanceWidth))), 0)) ) { do { } while (false); MOZ_ReportAssertionFailure("aParams.advanceWidth" " (" "Must provide advanceWidth" ")", "./../../../layout/generic/nsTextFrame.cpp" , 7997); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aParams.advanceWidth" ") (" "Must provide advanceWidth" ")"); do { MOZ_CrashSequence (__null, 7997); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 7998 | |
| 7999 | ::DrawTextRun(mTextRun, aTextBaselinePt, aRange, aParams, aImgParams, this); |
| 8000 | |
| 8001 | if (aParams.drawSoftHyphen) { |
| 8002 | // Don't use ctx as the context, because we need a reference context here, |
| 8003 | // ctx may be transformed. |
| 8004 | DrawTextRunParams params = aParams; |
| 8005 | params.provider = nullptr; |
| 8006 | params.advanceWidth = nullptr; |
| 8007 | RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(this, nullptr); |
| 8008 | if (hyphenTextRun) { |
| 8009 | gfx::Point p(aTextBaselinePt); |
| 8010 | bool vertical = GetWritingMode().IsVertical(); |
| 8011 | // For right-to-left text runs, the soft-hyphen is positioned at the left |
| 8012 | // of the text. |
| 8013 | float shift = mTextRun->GetDirection() * (*aParams.advanceWidth); |
| 8014 | if (vertical) { |
| 8015 | p.y += shift; |
| 8016 | } else { |
| 8017 | p.x += shift; |
| 8018 | } |
| 8019 | ::DrawTextRun(hyphenTextRun.get(), p, Range(hyphenTextRun.get()), params, |
| 8020 | aImgParams, this); |
| 8021 | } |
| 8022 | } |
| 8023 | } |
| 8024 | |
| 8025 | void nsTextFrame::DrawTextRunAndDecorations(Range aRange, |
| 8026 | const gfx::Point& aTextBaselinePt, |
| 8027 | const DrawTextParams& aParams, |
| 8028 | const TextDecorations& aDecorations, |
| 8029 | imgDrawingParams& aImgParams) { |
| 8030 | const gfxFloat app = aParams.textStyle->PresContext()->AppUnitsPerDevPixel(); |
| 8031 | // Writing mode of parent frame is used because the text frame may |
| 8032 | // be orthogonal to its parent when text-combine-upright is used or |
| 8033 | // its parent has "display: contents", and in those cases, we want |
| 8034 | // to draw the decoration lines according to parents' direction |
| 8035 | // rather than ours. |
| 8036 | const WritingMode wm = GetParent()->GetWritingMode(); |
| 8037 | bool verticalDec = wm.IsVertical(); |
| 8038 | bool verticalRun = mTextRun->IsVertical(); |
| 8039 | // If the text run and the decoration is orthogonal, we choose the |
| 8040 | // metrics for decoration so that decoration line won't be broken. |
| 8041 | bool useVerticalMetrics = verticalDec != verticalRun |
| 8042 | ? verticalDec |
| 8043 | : verticalRun && mTextRun->UseCenterBaseline(); |
| 8044 | |
| 8045 | // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint? |
| 8046 | nscoord x = NSToCoordRound(aParams.framePt.x); |
| 8047 | nscoord y = NSToCoordRound(aParams.framePt.y); |
| 8048 | |
| 8049 | // 'measure' here is textrun-relative, so for a horizontal run it's the |
| 8050 | // width, while for a vertical run it's the height of the decoration |
| 8051 | const nsSize frameSize = GetSize(); |
| 8052 | nscoord measure = verticalDec ? frameSize.height : frameSize.width; |
| 8053 | |
| 8054 | if (verticalDec) { |
| 8055 | aParams.clipEdges->Intersect(&y, &measure); |
| 8056 | } else { |
| 8057 | aParams.clipEdges->Intersect(&x, &measure); |
| 8058 | } |
| 8059 | |
| 8060 | // decSize is a textrun-relative size, so its 'width' field is actually |
| 8061 | // the run-relative measure, and 'height' will be the line thickness |
| 8062 | gfxFloat ascent = gfxFloat(GetLogicalBaseline(wm)) / app; |
| 8063 | // The starting edge of the frame in block direction |
| 8064 | gfxFloat frameBStart = verticalDec ? aParams.framePt.x : aParams.framePt.y; |
| 8065 | |
| 8066 | // In vertical-rl mode, block coordinates are measured from the |
| 8067 | // right, so we need to adjust here. |
| 8068 | if (wm.IsVerticalRL()) { |
| 8069 | frameBStart += frameSize.width; |
| 8070 | ascent = -ascent; |
| 8071 | } |
| 8072 | |
| 8073 | nscoord inflationMinFontSize = nsLayoutUtils::InflationMinFontSizeFor(this); |
| 8074 | |
| 8075 | PaintDecorationLineParams params; |
| 8076 | params.context = aParams.context; |
| 8077 | params.dirtyRect = aParams.dirtyRect; |
| 8078 | params.overrideColor = aParams.decorationOverrideColor; |
| 8079 | params.callbacks = aParams.callbacks; |
| 8080 | params.glyphRange = aParams.glyphRange; |
| 8081 | params.provider = aParams.provider; |
| 8082 | params.paintingShadows = aParams.paintingShadows; |
| 8083 | // pt is the physical point where the decoration is to be drawn, |
| 8084 | // relative to the frame; one of its coordinates will be updated below. |
| 8085 | params.pt = Point(x / app, y / app); |
| 8086 | Float& bCoord = verticalDec ? params.pt.x.value : params.pt.y.value; |
| 8087 | params.lineSize = Size(measure / app, 0); |
| 8088 | params.ascent = ascent; |
| 8089 | params.vertical = verticalDec; |
| 8090 | params.sidewaysLeft = mTextRun->IsSidewaysLeft(); |
| 8091 | |
| 8092 | // The matrix of the context may have been altered for text-combine- |
| 8093 | // upright. However, we want to draw decoration lines unscaled, thus |
| 8094 | // we need to revert the scaling here. |
| 8095 | gfxContextMatrixAutoSaveRestore scaledRestorer; |
| 8096 | if (Style()->IsTextCombined()) { |
| 8097 | float scaleFactor = GetTextCombineScale(); |
| 8098 | if (scaleFactor != 1.0f) { |
| 8099 | scaledRestorer.SetContext(aParams.context); |
| 8100 | gfxMatrix unscaled = aParams.context->CurrentMatrixDouble(); |
| 8101 | gfxPoint pt(x / app, y / app); |
| 8102 | if (GetTextRun(nsTextFrame::eInflated)->IsRightToLeft()) { |
| 8103 | pt.x += gfxFloat(frameSize.width) / app; |
| 8104 | } |
| 8105 | unscaled.PreTranslate(pt) |
| 8106 | .PreScale(1.0f / scaleFactor, 1.0f) |
| 8107 | .PreTranslate(-pt); |
| 8108 | aParams.context->SetMatrixDouble(unscaled); |
| 8109 | } |
| 8110 | } |
| 8111 | |
| 8112 | // We create a clip region in order to draw the decoration lines only in the |
| 8113 | // range of the text. Restricting the draw area prevents the decoration lines |
| 8114 | // to be drawn multiple times when a part of the text is selected. |
| 8115 | Maybe<gfxRect> clipRect; |
| 8116 | |
| 8117 | // We skip clipping for the following cases: |
| 8118 | // - drawing the whole text |
| 8119 | // - having different orientation of the text and the writing-mode, such as |
| 8120 | // "text-combine-upright" (Bug 1408825) |
| 8121 | if (aRange.Length() != mTextRun->GetLength() && verticalDec == verticalRun) { |
| 8122 | // Get the inline-size according to the specified range. |
| 8123 | gfxFloat clipLength = mTextRun->GetAdvanceWidth(aRange, aParams.provider); |
| 8124 | nsRect visualRect = InkOverflowRect(); |
| 8125 | |
| 8126 | const bool isInlineReversed = mTextRun->IsInlineReversed(); |
| 8127 | gfxFloat x, y, w, h; |
| 8128 | if (verticalDec) { |
| 8129 | x = aParams.framePt.x + visualRect.x; |
| 8130 | y = isInlineReversed ? aTextBaselinePt.y.value - clipLength |
| 8131 | : aTextBaselinePt.y.value; |
| 8132 | w = visualRect.width; |
| 8133 | h = clipLength; |
| 8134 | } else { |
| 8135 | x = isInlineReversed ? aTextBaselinePt.x.value - clipLength |
| 8136 | : aTextBaselinePt.x.value; |
| 8137 | y = aParams.framePt.y + visualRect.y; |
| 8138 | w = clipLength; |
| 8139 | h = visualRect.height; |
| 8140 | } |
| 8141 | clipRect.emplace(x, y, w, h); |
| 8142 | clipRect->Scale(1 / app); |
| 8143 | // Round the inline axis to nearest: that's the axis the clip exists for, |
| 8144 | // and it keeps the clips of adjacent ranges tiling exactly, so a decoration |
| 8145 | // is neither dropped nor drawn twice where two ranges meet. The block axis |
| 8146 | // only bounds the rect - it spans the ink overflow rect, which contains the |
| 8147 | // decoration - so round it outward instead. An edge rounded inward there |
| 8148 | // can land on the device pixel row the line was snapped to, and then the |
| 8149 | // line is culled entirely rather than trimmed (bug 2059455). |
| 8150 | gfxRect inlineRounded = *clipRect; |
| 8151 | inlineRounded.Round(); |
| 8152 | clipRect->RoundOut(); |
| 8153 | if (verticalDec) { |
| 8154 | clipRect->y = inlineRounded.y; |
| 8155 | clipRect->height = inlineRounded.height; |
| 8156 | } else { |
| 8157 | clipRect->x = inlineRounded.x; |
| 8158 | clipRect->width = inlineRounded.width; |
| 8159 | } |
| 8160 | } |
| 8161 | |
| 8162 | typedef gfxFont::Metrics Metrics; |
| 8163 | auto paintDecorationLine = [&](const LineDecoration& dec, |
| 8164 | gfxFloat Metrics::* lineSize, |
| 8165 | StyleTextDecorationLine lineType) { |
| 8166 | if (dec.mStyle == StyleTextDecorationStyle::None) { |
| 8167 | return; |
| 8168 | } |
| 8169 | |
| 8170 | float inflation = |
| 8171 | GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); |
| 8172 | const Metrics metrics = GetFirstFontMetrics( |
| 8173 | GetFontGroupForFrame(dec.mFrame, inflation), useVerticalMetrics); |
| 8174 | if (!ComputeDecorationInset(this, aParams.textStyle->PresContext(), |
| 8175 | dec.mFrame, metrics, params)) { |
| 8176 | return; |
| 8177 | } |
| 8178 | bCoord = (frameBStart - dec.mBaselineOffset) / app; |
| 8179 | |
| 8180 | params.color = dec.mColor; |
| 8181 | params.baselineOffset = dec.mBaselineOffset / app; |
| 8182 | params.defaultLineThickness = metrics.*lineSize; |
| 8183 | params.lineSize.height = ComputeDecorationLineThickness( |
| 8184 | dec.mTextDecorationThickness, params.defaultLineThickness, metrics, app, |
| 8185 | dec.mFrame); |
| 8186 | |
| 8187 | bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style()); |
| 8188 | params.offset = ComputeDecorationLineOffset( |
| 8189 | lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset, metrics, |
| 8190 | app, dec.mFrame, wm.IsCentralBaseline(), swapUnderline); |
| 8191 | |
| 8192 | params.style = dec.mStyle; |
| 8193 | params.allowInkSkipping = dec.mAllowInkSkipping; |
| 8194 | params.skipInk = StyleText()->mTextDecorationSkipInk; |
| 8195 | gfxClipAutoSaveRestore clipRestore(params.context); |
| 8196 | // If we have a negative inset value, then the decoration will extend |
| 8197 | // outside the edges of the text. |
| 8198 | // TODO alaskanemily: Ideally we would adjust the clipping rect, but as |
| 8199 | // an initial pass we just disable clipping in this case. |
| 8200 | if (clipRect && !params.HasNegativeInset()) { |
| 8201 | clipRestore.Clip(*clipRect); |
| 8202 | } |
| 8203 | PaintDecorationLine(params); |
| 8204 | }; |
| 8205 | |
| 8206 | // Underlines |
| 8207 | params.decoration = StyleTextDecorationLine::UNDERLINE; |
| 8208 | for (const LineDecoration& dec : Reversed(aDecorations.mUnderlines)) { |
| 8209 | paintDecorationLine(dec, &Metrics::underlineSize, params.decoration); |
| 8210 | } |
| 8211 | |
| 8212 | // Overlines |
| 8213 | params.decoration = StyleTextDecorationLine::OVERLINE; |
| 8214 | for (const LineDecoration& dec : Reversed(aDecorations.mOverlines)) { |
| 8215 | paintDecorationLine(dec, &Metrics::underlineSize, params.decoration); |
| 8216 | } |
| 8217 | |
| 8218 | // Some glyphs and emphasis marks may extend outside the region, so we do |
| 8219 | // not set the clip region here to the clip rect. For an example, italic |
| 8220 | // glyphs. |
| 8221 | |
| 8222 | { |
| 8223 | gfxContextMatrixAutoSaveRestore unscaledRestorer; |
| 8224 | if (scaledRestorer.HasMatrix()) { |
| 8225 | unscaledRestorer.SetContext(aParams.context); |
| 8226 | aParams.context->SetMatrix(scaledRestorer.Matrix()); |
| 8227 | } |
| 8228 | |
| 8229 | // CSS 2.1 mandates that text be painted after over/underlines, |
| 8230 | // and *then* line-throughs |
| 8231 | DrawTextRun(aRange, aTextBaselinePt, aParams, aImgParams); |
| 8232 | } |
| 8233 | |
| 8234 | // Emphasis marks |
| 8235 | DrawEmphasisMarks(aParams.context, wm, aTextBaselinePt, aParams.framePt, |
| 8236 | aRange, aParams.decorationOverrideColor, aParams.provider, |
| 8237 | aImgParams); |
| 8238 | |
| 8239 | // Line-throughs |
| 8240 | params.decoration = StyleTextDecorationLine::LINE_THROUGH; |
| 8241 | for (const LineDecoration& dec : Reversed(aDecorations.mStrikes)) { |
| 8242 | paintDecorationLine(dec, &Metrics::strikeoutSize, params.decoration); |
| 8243 | } |
| 8244 | } |
| 8245 | |
| 8246 | void nsTextFrame::DrawText(Range aRange, const gfx::Point& aTextBaselinePt, |
| 8247 | const DrawTextParams& aParams, |
| 8248 | imgDrawingParams& aImgParams) { |
| 8249 | TextDecorations decorations; |
| 8250 | GetTextDecorations(aParams.textStyle->PresContext(), |
| 8251 | aParams.callbacks ? eUnresolvedColors : eResolvedColors, |
| 8252 | decorations); |
| 8253 | |
| 8254 | // Hide text decorations if we're currently hiding @font-face fallback text |
| 8255 | const bool drawDecorations = |
| 8256 | !aParams.provider->GetFontGroup()->ShouldSkipDrawing() && |
| 8257 | (decorations.HasDecorationLines() || |
| 8258 | StyleText()->HasEffectiveTextEmphasis()); |
| 8259 | if (drawDecorations) { |
| 8260 | DrawTextRunAndDecorations(aRange, aTextBaselinePt, aParams, decorations, |
| 8261 | aImgParams); |
| 8262 | } else { |
| 8263 | DrawTextRun(aRange, aTextBaselinePt, aParams, aImgParams); |
| 8264 | } |
| 8265 | |
| 8266 | if (auto* textDrawer = aParams.context->GetTextDrawer()) { |
| 8267 | textDrawer->TerminateShadows(); |
| 8268 | } |
| 8269 | } |
| 8270 | |
| 8271 | NS_DECLARE_FRAME_PROPERTY_DELETABLE(WebRenderTextBounds, nsRect)static const mozilla::FramePropertyDescriptor<nsRect>* WebRenderTextBounds () { static const auto descriptor = mozilla::FramePropertyDescriptor <nsRect>::NewWithDestructor<DeleteValue>(); return &descriptor; } |
| 8272 | |
| 8273 | nsRect nsTextFrame::WebRenderBounds() { |
| 8274 | // WR text bounds is just our ink overflow rect but without shadows. So if we |
| 8275 | // have no shadows, just use the layout bounds. |
| 8276 | if (!StyleText()->HasTextShadow()) { |
| 8277 | return InkOverflowRect(); |
| 8278 | } |
| 8279 | nsRect* cachedBounds = GetProperty(WebRenderTextBounds()); |
| 8280 | if (!cachedBounds) { |
| 8281 | OverflowAreas overflowAreas; |
| 8282 | ComputeCustomOverflowInternal(overflowAreas, false); |
| 8283 | cachedBounds = new nsRect(overflowAreas.InkOverflow()); |
| 8284 | SetProperty(WebRenderTextBounds(), cachedBounds); |
| 8285 | } |
| 8286 | return *cachedBounds; |
| 8287 | } |
| 8288 | |
| 8289 | int16_t nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags) { |
| 8290 | nsISelectionController* const selCon = GetSelectionController(); |
| 8291 | if (MOZ_UNLIKELY(!selCon)(__builtin_expect(!!(!selCon), 0))) { |
| 8292 | return nsISelectionController::SELECTION_OFF; |
| 8293 | } |
| 8294 | |
| 8295 | selCon->GetSelectionFlags(aSelectionFlags); |
| 8296 | |
| 8297 | int16_t selectionValue = nsISelectionController::SELECTION_OFF; |
| 8298 | selCon->GetDisplaySelection(&selectionValue); |
| 8299 | return selectionValue; |
| 8300 | } |
| 8301 | |
| 8302 | bool nsTextFrame::IsEntirelyWhitespace() const { |
| 8303 | const auto& characterDataBuffer = mContent->AsText()->DataBuffer(); |
| 8304 | for (uint32_t index = 0; index < characterDataBuffer.GetLength(); ++index) { |
| 8305 | const char16_t ch = characterDataBuffer.CharAt(index); |
| 8306 | if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == 0xa0) { |
| 8307 | continue; |
| 8308 | } |
| 8309 | return false; |
| 8310 | } |
| 8311 | return true; |
| 8312 | } |
| 8313 | |
| 8314 | /** |
| 8315 | * Compute the longest prefix of text whose width is <= aWidth. Return |
| 8316 | * the length of the prefix. Also returns the width of the prefix in aFitWidth. |
| 8317 | */ |
| 8318 | static uint32_t CountCharsFit(const gfxTextRun* aTextRun, |
| 8319 | gfxTextRun::Range aRange, gfxFloat aWidth, |
| 8320 | nsTextFrame::PropertyProvider* aProvider, |
| 8321 | gfxFloat* aFitWidth) { |
| 8322 | uint32_t last = 0; |
| 8323 | gfxFloat width = 0; |
| 8324 | for (uint32_t i = 1; i <= aRange.Length(); ++i) { |
| 8325 | if (i == aRange.Length() || aTextRun->IsClusterStart(aRange.start + i)) { |
| 8326 | gfxTextRun::Range range(aRange.start + last, aRange.start + i); |
| 8327 | gfxFloat nextWidth = width + aTextRun->GetAdvanceWidth(range, aProvider); |
| 8328 | if (nextWidth > aWidth) { |
| 8329 | break; |
| 8330 | } |
| 8331 | last = i; |
| 8332 | width = nextWidth; |
| 8333 | } |
| 8334 | } |
| 8335 | *aFitWidth = width; |
| 8336 | return last; |
| 8337 | } |
| 8338 | |
| 8339 | nsIFrame::ContentOffsets nsTextFrame::CalcContentOffsetsFromFramePoint( |
| 8340 | const nsPoint& aPoint) { |
| 8341 | return GetCharacterOffsetAtFramePointInternal(aPoint, true); |
| 8342 | } |
| 8343 | |
| 8344 | nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePoint( |
| 8345 | const nsPoint& aPoint) { |
| 8346 | return GetCharacterOffsetAtFramePointInternal(aPoint, false); |
| 8347 | } |
| 8348 | |
| 8349 | nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePointInternal( |
| 8350 | const nsPoint& aPoint, bool aForInsertionPoint) { |
| 8351 | ContentOffsets offsets; |
| 8352 | |
| 8353 | gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); |
| 8354 | if (!mTextRun) { |
| 8355 | return offsets; |
| 8356 | } |
| 8357 | |
| 8358 | PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics); |
| 8359 | // Trim leading but not trailing whitespace if possible |
| 8360 | provider.InitializeForDisplay(false); |
| 8361 | gfxFloat width = |
| 8362 | mTextRun->IsVertical() |
| 8363 | ? (mTextRun->IsInlineReversed() ? mRect.height - aPoint.y : aPoint.y) |
| 8364 | : (mTextRun->IsInlineReversed() ? mRect.width - aPoint.x : aPoint.x); |
| 8365 | if (Style()->IsTextCombined()) { |
| 8366 | width /= GetTextCombineScale(); |
| 8367 | } |
| 8368 | gfxFloat fitWidth; |
| 8369 | Range skippedRange = ComputeTransformedRange(provider); |
| 8370 | |
| 8371 | uint32_t charsFit = |
| 8372 | CountCharsFit(mTextRun, skippedRange, width, &provider, &fitWidth); |
| 8373 | |
| 8374 | int32_t selectedOffset; |
| 8375 | if (charsFit < skippedRange.Length()) { |
| 8376 | // charsFit characters fitted, but no more could fit. See if we're |
| 8377 | // more than halfway through the cluster.. If we are, choose the next |
| 8378 | // cluster. |
| 8379 | gfxSkipCharsIterator extraCluster(provider.GetStart()); |
| 8380 | extraCluster.AdvanceSkipped(charsFit); |
| 8381 | |
| 8382 | bool allowSplitLigature = true; // Allow selection of partial ligature... |
| 8383 | |
| 8384 | // ...but don't let selection/insertion-point split two Regional Indicator |
| 8385 | // chars that are ligated in the textrun to form a single flag symbol. |
| 8386 | uint32_t offs = extraCluster.GetOriginalOffset(); |
| 8387 | const auto& characterDataBuffer = CharacterDataBuffer(); |
| 8388 | if (characterDataBuffer.IsHighSurrogateFollowedByLowSurrogateAt(offs) && |
| 8389 | gfxFontUtils::IsRegionalIndicator( |
| 8390 | characterDataBuffer.ScalarValueAt(offs))) { |
| 8391 | allowSplitLigature = false; |
| 8392 | if (extraCluster.GetSkippedOffset() >= skippedRange.start + 2 && |
| 8393 | !mTextRun->IsLigatureGroupStart(extraCluster.GetSkippedOffset())) { |
| 8394 | // CountCharsFit() left us in the middle of the flag; back up over the |
| 8395 | // first character of the ligature, and adjust fitWidth accordingly. |
| 8396 | extraCluster.AdvanceSkipped(-2); // it's a surrogate pair: 2 code units |
| 8397 | fitWidth -= mTextRun->GetAdvanceWidth( |
| 8398 | Range(extraCluster.GetSkippedOffset(), |
| 8399 | extraCluster.GetSkippedOffset() + 2), |
| 8400 | &provider); |
| 8401 | } |
| 8402 | } |
| 8403 | |
| 8404 | gfxSkipCharsIterator extraClusterLastChar(extraCluster); |
| 8405 | FindClusterEnd( |
| 8406 | mTextRun, |
| 8407 | provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(), |
| 8408 | &extraClusterLastChar, allowSplitLigature); |
| 8409 | PropertyProvider::Spacing spacing; |
| 8410 | Range extraClusterRange(extraCluster.GetSkippedOffset(), |
| 8411 | extraClusterLastChar.GetSkippedOffset() + 1); |
| 8412 | gfxFloat charWidth = |
| 8413 | mTextRun->GetAdvanceWidth(extraClusterRange, &provider, &spacing); |
| 8414 | charWidth -= spacing.mBefore + spacing.mAfter; |
| 8415 | selectedOffset = !aForInsertionPoint || |
| 8416 | width <= fitWidth + spacing.mBefore + charWidth / 2 |
| 8417 | ? extraCluster.GetOriginalOffset() |
| 8418 | : extraClusterLastChar.GetOriginalOffset() + 1; |
| 8419 | } else { |
| 8420 | // All characters fitted, we're at (or beyond) the end of the text. |
| 8421 | // XXX This could be some pathological situation where negative spacing |
| 8422 | // caused characters to move backwards. We can't really handle that |
| 8423 | // in the current frame system because frames can't have negative |
| 8424 | // intrinsic widths. |
| 8425 | selectedOffset = |
| 8426 | provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(); |
| 8427 | // If we're at the end of a preformatted line which has a terminating |
| 8428 | // linefeed, we want to reduce the offset by one to make sure that the |
| 8429 | // selection is placed before the linefeed character. |
| 8430 | if (HasSignificantTerminalNewline()) { |
| 8431 | --selectedOffset; |
| 8432 | } |
| 8433 | } |
| 8434 | |
| 8435 | offsets.content = GetContent(); |
| 8436 | offsets.offset = offsets.secondaryOffset = selectedOffset; |
| 8437 | offsets.associate = mContentOffset == offsets.offset |
| 8438 | ? CaretAssociationHint::After |
| 8439 | : CaretAssociationHint::Before; |
| 8440 | return offsets; |
| 8441 | } |
| 8442 | |
| 8443 | bool nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext, |
| 8444 | nsRect& aRect) { |
| 8445 | if (aRect.IsEmpty()) { |
| 8446 | return false; |
| 8447 | } |
| 8448 | |
| 8449 | nsRect givenRect = aRect; |
| 8450 | |
| 8451 | gfxFontGroup* fontGroup = GetInflatedFontGroupForFrame(this); |
| 8452 | RefPtr<gfxFont> firstFont = fontGroup->GetFirstValidFont(); |
| 8453 | WritingMode wm = GetWritingMode(); |
| 8454 | bool verticalRun = wm.IsVertical(); |
| 8455 | bool useVerticalMetrics = verticalRun && !wm.IsSideways(); |
| 8456 | const gfxFont::Metrics& metrics = |
| 8457 | firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical |
| 8458 | : nsFontMetrics::eHorizontal); |
| 8459 | |
| 8460 | nsCSSRendering::DecorationRectParams params; |
| 8461 | params.ascent = aPresContext->AppUnitsToGfxUnits(mAscent); |
| 8462 | |
| 8463 | params.offset = fontGroup->GetUnderlineOffset(); |
| 8464 | |
| 8465 | TextDecorations textDecs; |
| 8466 | GetTextDecorations(aPresContext, eResolvedColors, textDecs); |
| 8467 | |
| 8468 | params.descentLimit = |
| 8469 | ComputeDescentLimitForSelectionUnderline(aPresContext, metrics); |
| 8470 | params.vertical = verticalRun; |
| 8471 | |
| 8472 | if (verticalRun) { |
| 8473 | EnsureTextRun(nsTextFrame::eInflated); |
| 8474 | params.sidewaysLeft = mTextRun ? mTextRun->IsSidewaysLeft() : false; |
| 8475 | } else { |
| 8476 | params.sidewaysLeft = false; |
| 8477 | } |
| 8478 | |
| 8479 | UniquePtr<SelectionDetails> details = GetSelectionDetails(); |
| 8480 | for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) { |
| 8481 | if (sd->mStart == sd->mEnd || |
| 8482 | sd->mSelectionType == SelectionType::eInvalid || |
| 8483 | !(ToSelectionTypeMask(sd->mSelectionType) & |
| 8484 | kSelectionTypesWithDecorations) || |
| 8485 | // URL strikeout does not use underline. |
| 8486 | sd->mSelectionType == SelectionType::eURLStrikeout) { |
| 8487 | continue; |
| 8488 | } |
| 8489 | float relativeSize = 1.f; |
| 8490 | RefPtr<ComputedStyle> style = Style(); |
| 8491 | |
| 8492 | if (sd->mSelectionType == SelectionType::eNormal || |
| 8493 | sd->mSelectionType == SelectionType::eTargetText || |
| 8494 | sd->mSelectionType == SelectionType::eHighlight) { |
| 8495 | style = [&]() { |
| 8496 | if (sd->mSelectionType == SelectionType::eHighlight) { |
| 8497 | return ComputeHighlightSelectionStyle( |
| 8498 | sd->mHighlightData.mHighlightName); |
| 8499 | } |
| 8500 | if (sd->mSelectionType == SelectionType::eTargetText) { |
| 8501 | return ComputeTargetTextStyle(); |
| 8502 | } |
| 8503 | int16_t unusedFlags = 0; |
| 8504 | const int16_t selectionStatus = GetSelectionStatus(&unusedFlags); |
| 8505 | return ComputeSelectionStyle(selectionStatus); |
| 8506 | }(); |
| 8507 | if (!style || !style->HasTextDecorationLines()) { |
| 8508 | continue; |
| 8509 | } |
| 8510 | // For pseudo-based selection styles (::selection, ::highlight, |
| 8511 | // ::target-text), compute a decoration rect for each line type declared |
| 8512 | // by the pseudo's style, mirroring the logic in DrawSelectionDecorations. |
| 8513 | // This must use the pseudo's own text-underline-position and |
| 8514 | // text-underline-offset (not the originating element's), and pass the |
| 8515 | // correct decoration line type so that ComputeDecorationLineOffset |
| 8516 | // applies the offset to underlines rather than treating them as |
| 8517 | // overlines. |
| 8518 | // descentLimit must be negative to disable lifting, matching |
| 8519 | // DrawSelectionDecorations which also disables it for pseudo-based |
| 8520 | // selection styles (the offset is explicitly specified by the author). |
| 8521 | const auto* styleTextReset = style->StyleTextReset(); |
| 8522 | const auto& decThickness = styleTextReset->mTextDecorationThickness; |
| 8523 | params.lineSize.width = aPresContext->AppUnitsToGfxUnits(aRect.width); |
| 8524 | params.style = styleTextReset->mTextDecorationStyle; |
| 8525 | params.descentLimit = -1.f; |
| 8526 | const bool swapUnderline = |
| 8527 | wm.IsCentralBaseline() && IsUnderlineRight(*style); |
| 8528 | const auto* styleText = style->StyleText(); |
| 8529 | auto accumForLine = [&](StyleTextDecorationLine decoration) { |
| 8530 | if (!(styleTextReset->mTextDecorationLine & decoration)) { |
| 8531 | return; |
| 8532 | } |
| 8533 | params.decoration = decoration; |
| 8534 | params.offset = ComputeDecorationLineOffset( |
| 8535 | decoration, styleText->mTextUnderlinePosition, |
| 8536 | styleText->mTextUnderlineOffset, metrics, |
| 8537 | aPresContext->AppUnitsPerDevPixel(), this, wm.IsCentralBaseline(), |
| 8538 | swapUnderline); |
| 8539 | |
| 8540 | if (decoration == StyleTextDecorationLine::LINE_THROUGH) { |
| 8541 | params.defaultLineThickness = metrics.strikeoutSize; |
| 8542 | } else { |
| 8543 | params.defaultLineThickness = ComputeSelectionUnderlineHeight( |
| 8544 | aPresContext, metrics, sd->mSelectionType); |
| 8545 | } |
| 8546 | params.lineSize.height = ComputeDecorationLineThickness( |
| 8547 | decThickness, params.defaultLineThickness, metrics, |
| 8548 | aPresContext->AppUnitsPerDevPixel(), this); |
| 8549 | |
| 8550 | nsRect decorationArea = |
| 8551 | nsCSSRendering::GetTextDecorationRect(aPresContext, params); |
| 8552 | aRect.UnionRect(aRect, decorationArea); |
| 8553 | }; |
| 8554 | accumForLine(StyleTextDecorationLine::UNDERLINE); |
| 8555 | accumForLine(StyleTextDecorationLine::OVERLINE); |
| 8556 | accumForLine(StyleTextDecorationLine::LINE_THROUGH); |
| 8557 | } else { |
| 8558 | auto index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType( |
| 8559 | sd->mSelectionType); |
| 8560 | if (sd->mSelectionType == SelectionType::eSpellCheck) { |
| 8561 | if (!nsTextPaintStyle::GetSelectionUnderline( |
| 8562 | this, index, nullptr, &relativeSize, ¶ms.style)) { |
| 8563 | continue; |
| 8564 | } |
| 8565 | } else { |
| 8566 | // IME selections |
| 8567 | TextRangeStyle& rangeStyle = sd->mTextRangeStyle; |
| 8568 | if (rangeStyle.IsDefined()) { |
| 8569 | if (!rangeStyle.IsLineStyleDefined() || |
| 8570 | rangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) { |
| 8571 | continue; |
| 8572 | } |
| 8573 | params.style = ToStyleLineStyle(rangeStyle); |
| 8574 | relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f; |
| 8575 | } else if (!nsTextPaintStyle::GetSelectionUnderline( |
| 8576 | this, index, nullptr, &relativeSize, ¶ms.style)) { |
| 8577 | continue; |
| 8578 | } |
| 8579 | } |
| 8580 | } |
| 8581 | nsRect decorationArea; |
| 8582 | |
| 8583 | const auto& decThickness = |
| 8584 | style->StyleTextReset()->mTextDecorationThickness; |
| 8585 | params.lineSize.width = aPresContext->AppUnitsToGfxUnits(aRect.width); |
| 8586 | params.defaultLineThickness = ComputeSelectionUnderlineHeight( |
| 8587 | aPresContext, metrics, sd->mSelectionType); |
| 8588 | |
| 8589 | params.lineSize.height = ComputeDecorationLineThickness( |
| 8590 | decThickness, params.defaultLineThickness, metrics, |
| 8591 | aPresContext->AppUnitsPerDevPixel(), this); |
| 8592 | |
| 8593 | bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*style); |
| 8594 | const auto* styleText = style->StyleText(); |
| 8595 | params.offset = ComputeDecorationLineOffset( |
| 8596 | textDecs.HasUnderline() ? StyleTextDecorationLine::UNDERLINE |
| 8597 | : StyleTextDecorationLine::OVERLINE, |
| 8598 | styleText->mTextUnderlinePosition, styleText->mTextUnderlineOffset, |
| 8599 | metrics, aPresContext->AppUnitsPerDevPixel(), this, |
| 8600 | wm.IsCentralBaseline(), swapUnderline); |
| 8601 | |
| 8602 | relativeSize = std::max(relativeSize, 1.0f); |
| 8603 | params.lineSize.height *= relativeSize; |
| 8604 | params.defaultLineThickness *= relativeSize; |
| 8605 | decorationArea = |
| 8606 | nsCSSRendering::GetTextDecorationRect(aPresContext, params); |
| 8607 | aRect.UnionRect(aRect, decorationArea); |
| 8608 | } |
| 8609 | |
| 8610 | return !aRect.IsEmpty() && !givenRect.Contains(aRect); |
| 8611 | } |
| 8612 | |
| 8613 | bool nsTextFrame::IsFrameSelected() const { |
| 8614 | NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(),do { if (!(!GetContent() || GetContent()->IsMaybeSelected( ))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "use the public IsSelected() instead" , "!GetContent() || GetContent()->IsMaybeSelected()", "./../../../layout/generic/nsTextFrame.cpp" , 8615); MOZ_PretendNoReturn(); } } while (0) |
| 8615 | "use the public IsSelected() instead")do { if (!(!GetContent() || GetContent()->IsMaybeSelected( ))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "use the public IsSelected() instead" , "!GetContent() || GetContent()->IsMaybeSelected()", "./../../../layout/generic/nsTextFrame.cpp" , 8615); MOZ_PretendNoReturn(); } } while (0); |
| 8616 | if (mIsSelected == nsTextFrame::SelectionState::Unknown) { |
| 8617 | const bool isSelected = |
| 8618 | GetContent()->IsSelected(GetContentOffset(), GetContentEnd(), |
| 8619 | PresShell()->GetSelectionNodeCache()); |
| 8620 | mIsSelected = isSelected ? nsTextFrame::SelectionState::Selected |
| 8621 | : nsTextFrame::SelectionState::NotSelected; |
| 8622 | } else { |
| 8623 | #ifdef DEBUG1 |
| 8624 | // Assert that the selection caching works. |
| 8625 | const bool isReallySelected = |
| 8626 | GetContent()->IsSelected(GetContentOffset(), GetContentEnd()); |
| 8627 | MOZ_ASSERT((mIsSelected == nsTextFrame::SelectionState::Selected) ==do { static_assert( mozilla::detail::AssertionConditionType< decltype((mIsSelected == nsTextFrame::SelectionState::Selected ) == isReallySelected)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!((mIsSelected == nsTextFrame:: SelectionState::Selected) == isReallySelected))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("(mIsSelected == nsTextFrame::SelectionState::Selected) == isReallySelected" " (" "Should have called InvalidateSelectionState()" ")", "./../../../layout/generic/nsTextFrame.cpp" , 8629); AnnotateMozCrashReason("MOZ_ASSERT" "(" "(mIsSelected == nsTextFrame::SelectionState::Selected) == isReallySelected" ") (" "Should have called InvalidateSelectionState()" ")"); do { MOZ_CrashSequence(__null, 8629); __attribute__((nomerge)) :: abort(); } while (false); } } while (false) |
| 8628 | isReallySelected,do { static_assert( mozilla::detail::AssertionConditionType< decltype((mIsSelected == nsTextFrame::SelectionState::Selected ) == isReallySelected)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!((mIsSelected == nsTextFrame:: SelectionState::Selected) == isReallySelected))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("(mIsSelected == nsTextFrame::SelectionState::Selected) == isReallySelected" " (" "Should have called InvalidateSelectionState()" ")", "./../../../layout/generic/nsTextFrame.cpp" , 8629); AnnotateMozCrashReason("MOZ_ASSERT" "(" "(mIsSelected == nsTextFrame::SelectionState::Selected) == isReallySelected" ") (" "Should have called InvalidateSelectionState()" ")"); do { MOZ_CrashSequence(__null, 8629); __attribute__((nomerge)) :: abort(); } while (false); } } while (false) |
| 8629 | "Should have called InvalidateSelectionState()")do { static_assert( mozilla::detail::AssertionConditionType< decltype((mIsSelected == nsTextFrame::SelectionState::Selected ) == isReallySelected)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!((mIsSelected == nsTextFrame:: SelectionState::Selected) == isReallySelected))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("(mIsSelected == nsTextFrame::SelectionState::Selected) == isReallySelected" " (" "Should have called InvalidateSelectionState()" ")", "./../../../layout/generic/nsTextFrame.cpp" , 8629); AnnotateMozCrashReason("MOZ_ASSERT" "(" "(mIsSelected == nsTextFrame::SelectionState::Selected) == isReallySelected" ") (" "Should have called InvalidateSelectionState()" ")"); do { MOZ_CrashSequence(__null, 8629); __attribute__((nomerge)) :: abort(); } while (false); } } while (false); |
| 8630 | #endif |
| 8631 | } |
| 8632 | |
| 8633 | return mIsSelected == nsTextFrame::SelectionState::Selected; |
| 8634 | } |
| 8635 | |
| 8636 | nsTextFrame* nsTextFrame::FindContinuationForOffset(int32_t aOffset) { |
| 8637 | // Use a continuations array to accelerate finding the first continuation |
| 8638 | // of interest, if possible. |
| 8639 | MOZ_ASSERT(!GetPrevContinuation(), "should be called on the primary frame")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!GetPrevContinuation())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!GetPrevContinuation()))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!GetPrevContinuation()" " (" "should be called on the primary frame" ")", "./../../../layout/generic/nsTextFrame.cpp" , 8639); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation()" ") (" "should be called on the primary frame" ")"); do { MOZ_CrashSequence (__null, 8639); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 8640 | auto* continuations = GetContinuations(); |
| 8641 | nsTextFrame* f = this; |
| 8642 | if (continuations) { |
| 8643 | size_t index; |
| 8644 | if (BinarySearchIf( |
| 8645 | *continuations, 0, continuations->Length(), |
| 8646 | [=](nsTextFrame* aFrame) -> int { |
| 8647 | return aOffset - aFrame->GetContentOffset(); |
| 8648 | }, |
| 8649 | &index)) { |
| 8650 | f = (*continuations)[index]; |
| 8651 | } else { |
| 8652 | f = (*continuations)[index ? index - 1 : 0]; |
| 8653 | } |
| 8654 | } |
| 8655 | |
| 8656 | while (f && f->GetContentEnd() <= aOffset) { |
| 8657 | f = f->GetNextContinuation(); |
| 8658 | } |
| 8659 | |
| 8660 | return f; |
| 8661 | } |
| 8662 | |
| 8663 | void nsTextFrame::SelectionStateChanged(uint32_t aStart, uint32_t aEnd, |
| 8664 | bool aSelected, |
| 8665 | SelectionType aSelectionType) { |
| 8666 | NS_ASSERTION(!GetPrevContinuation(),do { if (!(!GetPrevContinuation())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Should only be called for primary frame", "!GetPrevContinuation()" , "./../../../layout/generic/nsTextFrame.cpp", 8667); MOZ_PretendNoReturn (); } } while (0) |
| 8667 | "Should only be called for primary frame")do { if (!(!GetPrevContinuation())) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Should only be called for primary frame", "!GetPrevContinuation()" , "./../../../layout/generic/nsTextFrame.cpp", 8667); MOZ_PretendNoReturn (); } } while (0); |
| 8668 | DEBUG_VERIFY_NOT_DIRTY(GetStateBits()); |
| 8669 | |
| 8670 | InvalidateSelectionState(); |
| 8671 | |
| 8672 | // Selection is collapsed, which can't affect text frame rendering |
| 8673 | if (aStart == aEnd) { |
| 8674 | return; |
| 8675 | } |
| 8676 | |
| 8677 | nsTextFrame* f = FindContinuationForOffset(aStart); |
| 8678 | |
| 8679 | nsPresContext* presContext = PresContext(); |
| 8680 | while (f && f->GetContentOffset() < int32_t(aEnd)) { |
| 8681 | // We may need to reflow to recompute the overflow area for spellchecking or |
| 8682 | // IME underline if their underline is thicker than the normal decoration |
| 8683 | // line. |
| 8684 | // FIXME(emilio): Text shadows would need similar treatment, wouldn't they? |
| 8685 | if (ToSelectionTypeMask(aSelectionType) & kSelectionTypesWithDecorations && |
| 8686 | !f->HasAnyStateBits(NS_FRAME_IS_DIRTY)) { |
| 8687 | const bool didHaveOverflowingSelection = |
| 8688 | f->HasAnyStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED); |
| 8689 | nsRect r(nsPoint(), GetSize()); |
| 8690 | if (didHaveOverflowingSelection || |
| 8691 | (aSelected && f->CombineSelectionUnderlineRect(presContext, r))) { |
| 8692 | // TODO(emilio): If we allow text to store changes or so, this could use |
| 8693 | // PostRestyleEvent(..., UpdateOverflow); |
| 8694 | presContext->PresShell()->FrameNeedsReflow(f, IntrinsicDirty::None, |
| 8695 | NS_FRAME_IS_DIRTY); |
| 8696 | } |
| 8697 | } |
| 8698 | // Selection might change anything. Invalidate the overflow area. |
| 8699 | f->InvalidateFrame(); |
| 8700 | |
| 8701 | f = f->GetNextContinuation(); |
| 8702 | } |
| 8703 | } |
| 8704 | |
| 8705 | void nsTextFrame::UpdateIteratorFromOffset(const PropertyProvider& aProperties, |
| 8706 | int32_t& aInOffset, |
| 8707 | gfxSkipCharsIterator& aIter) { |
| 8708 | if (aInOffset < GetContentOffset()) { |
| 8709 | NS_WARNING("offset before this frame's content")NS_DebugBreak(NS_DEBUG_WARNING, "offset before this frame's content" , nullptr, "./../../../layout/generic/nsTextFrame.cpp", 8709); |
| 8710 | aInOffset = GetContentOffset(); |
| 8711 | } else if (aInOffset > GetContentEnd()) { |
| 8712 | NS_WARNING("offset after this frame's content")NS_DebugBreak(NS_DEBUG_WARNING, "offset after this frame's content" , nullptr, "./../../../layout/generic/nsTextFrame.cpp", 8712); |
| 8713 | aInOffset = GetContentEnd(); |
| 8714 | } |
| 8715 | |
| 8716 | int32_t trimmedOffset = aProperties.GetStart().GetOriginalOffset(); |
| 8717 | int32_t trimmedEnd = trimmedOffset + aProperties.GetOriginalLength(); |
| 8718 | aInOffset = std::max(aInOffset, trimmedOffset); |
| 8719 | aInOffset = std::min(aInOffset, trimmedEnd); |
| 8720 | |
| 8721 | aIter.SetOriginalOffset(aInOffset); |
| 8722 | |
| 8723 | if (aInOffset < trimmedEnd && !aIter.IsOriginalCharSkipped() && |
| 8724 | !mTextRun->IsClusterStart(aIter.GetSkippedOffset())) { |
| 8725 | // Called for non-cluster boundary |
| 8726 | FindClusterStart(mTextRun, trimmedOffset, &aIter); |
| 8727 | } |
| 8728 | } |
| 8729 | |
| 8730 | nsPoint nsTextFrame::GetPointFromIterator(const gfxSkipCharsIterator& aIter, |
| 8731 | PropertyProvider& aProperties) { |
| 8732 | Range range(aProperties.GetStart().GetSkippedOffset(), |
| 8733 | aIter.GetSkippedOffset()); |
| 8734 | gfxFloat advance = mTextRun->GetAdvanceWidth(range, &aProperties); |
| 8735 | nscoord iSize = NSToCoordCeilClamped(advance); |
| 8736 | nsPoint point; |
| 8737 | |
| 8738 | if (mTextRun->IsVertical()) { |
| 8739 | point.x = 0; |
| 8740 | if (mTextRun->IsInlineReversed()) { |
| 8741 | point.y = mRect.height - iSize; |
| 8742 | } else { |
| 8743 | point.y = iSize; |
| 8744 | } |
| 8745 | } else { |
| 8746 | point.y = 0; |
| 8747 | if (Style()->IsTextCombined()) { |
| 8748 | iSize *= GetTextCombineScale(); |
| 8749 | } |
| 8750 | if (mTextRun->IsInlineReversed()) { |
| 8751 | point.x = mRect.width - iSize; |
| 8752 | } else { |
| 8753 | point.x = iSize; |
| 8754 | } |
| 8755 | } |
| 8756 | return point; |
| 8757 | } |
| 8758 | |
| 8759 | nsresult nsTextFrame::GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) { |
| 8760 | if (!outPoint) { |
| 8761 | return NS_ERROR_NULL_POINTER; |
| 8762 | } |
| 8763 | |
| 8764 | DEBUG_VERIFY_NOT_DIRTY(GetStateBits()); |
| 8765 | if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { |
| 8766 | return NS_ERROR_UNEXPECTED; |
| 8767 | } |
| 8768 | |
| 8769 | if (GetContentLength() <= 0) { |
| 8770 | outPoint->x = 0; |
| 8771 | outPoint->y = 0; |
| 8772 | return NS_OK; |
| 8773 | } |
| 8774 | |
| 8775 | gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); |
| 8776 | if (!mTextRun) { |
| 8777 | return NS_ERROR_FAILURE; |
| 8778 | } |
| 8779 | |
| 8780 | PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics); |
| 8781 | // Don't trim trailing whitespace, we want the caret to appear in the right |
| 8782 | // place if it's positioned there |
| 8783 | properties.InitializeForDisplay(false); |
| 8784 | |
| 8785 | UpdateIteratorFromOffset(properties, inOffset, iter); |
| 8786 | |
| 8787 | *outPoint = GetPointFromIterator(iter, properties); |
| 8788 | |
| 8789 | return NS_OK; |
| 8790 | } |
| 8791 | |
| 8792 | nsresult nsTextFrame::GetCharacterRectsInRange(int32_t aInOffset, |
| 8793 | int32_t aLength, |
| 8794 | nsTArray<nsRect>& aRects) { |
| 8795 | DEBUG_VERIFY_NOT_DIRTY(GetStateBits()); |
| 8796 | if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { |
| 8797 | return NS_ERROR_UNEXPECTED; |
| 8798 | } |
| 8799 | |
| 8800 | if (GetContentLength() <= 0) { |
| 8801 | return NS_OK; |
| 8802 | } |
| 8803 | |
| 8804 | if (!mTextRun) { |
| 8805 | return NS_ERROR_FAILURE; |
| 8806 | } |
| 8807 | |
| 8808 | gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); |
| 8809 | PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics); |
| 8810 | // Don't trim trailing whitespace, we want the caret to appear in the right |
| 8811 | // place if it's positioned there |
| 8812 | properties.InitializeForDisplay(false); |
| 8813 | |
| 8814 | // Initialize iter; this will call FindClusterStart if necessary to align |
| 8815 | // iter to a cluster boundary. |
| 8816 | UpdateIteratorFromOffset(properties, aInOffset, iter); |
| 8817 | nsPoint point = GetPointFromIterator(iter, properties); |
| 8818 | |
| 8819 | const int32_t kContentEnd = GetContentEnd(); |
| 8820 | const int32_t kEndOffset = std::min(aInOffset + aLength, kContentEnd); |
| 8821 | |
| 8822 | if (aInOffset >= kEndOffset) { |
| 8823 | return NS_OK; |
| 8824 | } |
| 8825 | |
| 8826 | if (!aRects.SetCapacity(aRects.Length() + kEndOffset - aInOffset, |
| 8827 | mozilla::fallible)) { |
| 8828 | return NS_ERROR_OUT_OF_MEMORY; |
| 8829 | } |
| 8830 | |
| 8831 | do { |
| 8832 | // We'd like to assert here that |point| matches |
| 8833 | // |GetPointFromIterator(iter, properties)|, which in principle should be |
| 8834 | // true; however, testcases with vast dimensions can lead to coordinate |
| 8835 | // overflow and disrupt the calculations. So we've dropped the assertion |
| 8836 | // to avoid tripping the fuzzer unnecessarily. |
| 8837 | |
| 8838 | // Measure to the end of the cluster. |
| 8839 | nscoord iSize = 0; |
| 8840 | gfxSkipCharsIterator nextIter(iter); |
| 8841 | if (aInOffset < kContentEnd) { |
| 8842 | nextIter.AdvanceOriginal(1); |
| 8843 | if (!nextIter.IsOriginalCharSkipped() && |
| 8844 | !mTextRun->IsClusterStart(nextIter.GetSkippedOffset()) && |
| 8845 | nextIter.GetOriginalOffset() < kContentEnd) { |
| 8846 | FindClusterEnd(mTextRun, kContentEnd, &nextIter); |
| 8847 | } |
| 8848 | |
| 8849 | gfxFloat advance = mTextRun->GetAdvanceWidth( |
| 8850 | Range(iter.GetSkippedOffset(), nextIter.GetSkippedOffset()), |
| 8851 | &properties); |
| 8852 | iSize = NSToCoordCeilClamped(advance); |
| 8853 | } |
| 8854 | |
| 8855 | // Compute the cluster rect, depending on directionality, and update |
| 8856 | // point to the origin we'll need for the next cluster. |
| 8857 | nsRect rect; |
| 8858 | rect.x = point.x; |
| 8859 | rect.y = point.y; |
| 8860 | |
| 8861 | if (mTextRun->IsVertical()) { |
| 8862 | rect.width = mRect.width; |
| 8863 | rect.height = iSize; |
| 8864 | if (mTextRun->IsInlineReversed()) { |
| 8865 | // The iterator above returns a point with the origin at the |
| 8866 | // bottom left instead of the top left. Move the origin to the top left |
| 8867 | // by subtracting the character's height. |
| 8868 | rect.y -= rect.height; |
| 8869 | point.y -= iSize; |
| 8870 | } else { |
| 8871 | point.y += iSize; |
| 8872 | } |
| 8873 | } else { |
| 8874 | if (Style()->IsTextCombined()) { |
| 8875 | // The scale factor applies to the inline advance of the glyphs, so it |
| 8876 | // affects both the rect width and the origin point for the next glyph. |
| 8877 | iSize *= GetTextCombineScale(); |
| 8878 | } |
| 8879 | rect.width = iSize; |
| 8880 | rect.height = mRect.height; |
| 8881 | if (mTextRun->IsInlineReversed()) { |
| 8882 | // The iterator above returns a point with the origin at the |
| 8883 | // top right instead of the top left. Move the origin to the top left by |
| 8884 | // subtracting the character's width. |
| 8885 | rect.x -= iSize; |
| 8886 | point.x -= iSize; |
| 8887 | } else { |
| 8888 | point.x += iSize; |
| 8889 | } |
| 8890 | } |
| 8891 | |
| 8892 | // Set the rect for all characters in the cluster. |
| 8893 | int32_t end = std::min(kEndOffset, nextIter.GetOriginalOffset()); |
| 8894 | while (aInOffset < end) { |
| 8895 | aRects.AppendElement(rect); |
| 8896 | aInOffset++; |
| 8897 | } |
| 8898 | |
| 8899 | // Advance iter for the next cluster. |
| 8900 | iter = nextIter; |
| 8901 | } while (aInOffset < kEndOffset); |
| 8902 | |
| 8903 | return NS_OK; |
| 8904 | } |
| 8905 | |
| 8906 | nsresult nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset, |
| 8907 | bool aHint, |
| 8908 | int32_t* aOutOffset, |
| 8909 | nsIFrame** aOutFrame) { |
| 8910 | DEBUG_VERIFY_NOT_DIRTY(GetStateBits()); |
| 8911 | #if 0 // XXXrbs disable due to bug 310227 |
| 8912 | if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) |
| 8913 | return NS_ERROR_UNEXPECTED; |
| 8914 | #endif |
| 8915 | |
| 8916 | NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters")do { if (!(aOutOffset && aOutFrame)) { NS_DebugBreak( NS_DEBUG_ASSERTION, "Bad out parameters", "aOutOffset && aOutFrame" , "./../../../layout/generic/nsTextFrame.cpp", 8916); MOZ_PretendNoReturn (); } } while (0); |
| 8917 | NS_ASSERTION(aContentOffset >= 0,do { if (!(aContentOffset >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Negative content offset, existing code was very broken!", "aContentOffset >= 0" , "./../../../layout/generic/nsTextFrame.cpp", 8918); MOZ_PretendNoReturn (); } } while (0) |
| 8918 | "Negative content offset, existing code was very broken!")do { if (!(aContentOffset >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "Negative content offset, existing code was very broken!", "aContentOffset >= 0" , "./../../../layout/generic/nsTextFrame.cpp", 8918); MOZ_PretendNoReturn (); } } while (0); |
| 8919 | nsIFrame* primaryFrame = mContent->GetPrimaryFrame(); |
| 8920 | if (this != primaryFrame) { |
| 8921 | // This call needs to happen on the primary frame |
| 8922 | return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint, |
| 8923 | aOutOffset, aOutFrame); |
| 8924 | } |
| 8925 | |
| 8926 | nsTextFrame* f = this; |
| 8927 | int32_t offset = mContentOffset; |
| 8928 | |
| 8929 | // Try to look up the offset to frame property |
| 8930 | nsTextFrame* cachedFrame = GetProperty(OffsetToFrameProperty()); |
| 8931 | |
| 8932 | if (cachedFrame) { |
| 8933 | f = cachedFrame; |
| 8934 | offset = f->GetContentOffset(); |
| 8935 | |
| 8936 | f->RemoveStateBits(TEXT_IN_OFFSET_CACHE); |
| 8937 | } |
| 8938 | |
| 8939 | if ((aContentOffset >= offset) && (aHint || aContentOffset != offset)) { |
| 8940 | while (true) { |
| 8941 | nsTextFrame* next = f->GetNextContinuation(); |
| 8942 | if (!next || aContentOffset < next->GetContentOffset()) { |
| 8943 | break; |
| 8944 | } |
| 8945 | if (aContentOffset == next->GetContentOffset()) { |
| 8946 | if (aHint) { |
| 8947 | f = next; |
| 8948 | if (f->GetContentLength() == 0) { |
| 8949 | continue; // use the last of the empty frames with this offset |
| 8950 | } |
| 8951 | } |
| 8952 | break; |
| 8953 | } |
| 8954 | f = next; |
| 8955 | } |
| 8956 | } else { |
| 8957 | while (true) { |
| 8958 | nsTextFrame* prev = f->GetPrevContinuation(); |
| 8959 | if (!prev || aContentOffset > f->GetContentOffset()) { |
| 8960 | break; |
| 8961 | } |
| 8962 | if (aContentOffset == f->GetContentOffset()) { |
| 8963 | if (!aHint) { |
| 8964 | f = prev; |
| 8965 | if (f->GetContentLength() == 0) { |
| 8966 | continue; // use the first of the empty frames with this offset |
| 8967 | } |
| 8968 | } |
| 8969 | break; |
| 8970 | } |
| 8971 | f = prev; |
| 8972 | } |
| 8973 | } |
| 8974 | |
| 8975 | *aOutOffset = aContentOffset - f->GetContentOffset(); |
| 8976 | *aOutFrame = f; |
| 8977 | |
| 8978 | // cache the frame we found |
| 8979 | SetProperty(OffsetToFrameProperty(), f); |
| 8980 | f->AddStateBits(TEXT_IN_OFFSET_CACHE); |
| 8981 | |
| 8982 | return NS_OK; |
| 8983 | } |
| 8984 | |
| 8985 | nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetNoAmount(bool aForward, |
| 8986 | int32_t* aOffset) { |
| 8987 | NS_ASSERTION(aOffset && *aOffset <= GetContentLength(),do { if (!(aOffset && *aOffset <= GetContentLength ())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aOffset out of range" , "aOffset && *aOffset <= GetContentLength()", "./../../../layout/generic/nsTextFrame.cpp" , 8988); MOZ_PretendNoReturn(); } } while (0) |
| 8988 | "aOffset out of range")do { if (!(aOffset && *aOffset <= GetContentLength ())) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aOffset out of range" , "aOffset && *aOffset <= GetContentLength()", "./../../../layout/generic/nsTextFrame.cpp" , 8988); MOZ_PretendNoReturn(); } } while (0); |
| 8989 | |
| 8990 | gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); |
| 8991 | if (!mTextRun) { |
| 8992 | return CONTINUE_EMPTY; |
| 8993 | } |
| 8994 | |
| 8995 | TrimmedOffsets trimmed = GetTrimmedOffsets(CharacterDataBuffer()); |
| 8996 | // Check whether there are nonskipped characters in the trimmmed range |
| 8997 | return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) > |
| 8998 | iter.ConvertOriginalToSkipped(trimmed.mStart)) |
| 8999 | ? FOUND |
| 9000 | : CONTINUE; |
| 9001 | } |
| 9002 | |
| 9003 | /** |
| 9004 | * This class iterates through the clusters before or after the given |
| 9005 | * aPosition (which is a content offset). You can test each cluster |
| 9006 | * to see if it's whitespace (as far as selection/caret movement is concerned), |
| 9007 | * or punctuation, or if there is a word break before the cluster. ("Before" |
| 9008 | * is interpreted according to aDirection, so if aDirection is -1, "before" |
| 9009 | * means actually *after* the cluster content.) |
| 9010 | */ |
| 9011 | class MOZ_STACK_CLASS ClusterIterator { |
| 9012 | public: |
| 9013 | ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, |
| 9014 | int32_t aDirection, nsString& aContext, |
| 9015 | bool aTrimSpaces = true); |
| 9016 | |
| 9017 | bool NextCluster(); |
| 9018 | bool IsInlineWhitespace() const; |
| 9019 | bool IsNewline() const; |
| 9020 | bool IsPunctuation() const; |
| 9021 | intl::Script ScriptCode() const; |
| 9022 | bool HaveWordBreakBefore() const { return mHaveWordBreak; } |
| 9023 | |
| 9024 | // Get the charIndex that corresponds to the "before" side of the current |
| 9025 | // character, according to the direction of iteration: so for a forward |
| 9026 | // iterator, this is simply mCharIndex, while for a reverse iterator it will |
| 9027 | // be mCharIndex + <number of code units in the character>. |
| 9028 | int32_t GetBeforeOffset() const { |
| 9029 | MOZ_ASSERT(mCharIndex >= 0)do { static_assert( mozilla::detail::AssertionConditionType< decltype(mCharIndex >= 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mCharIndex >= 0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mCharIndex >= 0" , "./../../../layout/generic/nsTextFrame.cpp", 9029); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "mCharIndex >= 0" ")"); do { MOZ_CrashSequence (__null, 9029); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 9030 | return mDirection < 0 ? GetAfterInternal() : mCharIndex; |
| 9031 | } |
| 9032 | // Get the charIndex that corresponds to the "before" side of the current |
| 9033 | // character, according to the direction of iteration: the opposite side |
| 9034 | // to what GetBeforeOffset returns. |
| 9035 | int32_t GetAfterOffset() const { |
| 9036 | MOZ_ASSERT(mCharIndex >= 0)do { static_assert( mozilla::detail::AssertionConditionType< decltype(mCharIndex >= 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mCharIndex >= 0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mCharIndex >= 0" , "./../../../layout/generic/nsTextFrame.cpp", 9036); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "mCharIndex >= 0" ")"); do { MOZ_CrashSequence (__null, 9036); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 9037 | return mDirection > 0 ? GetAfterInternal() : mCharIndex; |
| 9038 | } |
| 9039 | |
| 9040 | private: |
| 9041 | // Helper for Get{After,Before}Offset; returns the charIndex after the |
| 9042 | // current position in the text, accounting for surrogate pairs. |
| 9043 | int32_t GetAfterInternal() const; |
| 9044 | |
| 9045 | gfxSkipCharsIterator mIterator; |
| 9046 | // Usually, mCharacterDataBuffer is pointer to `dom::CharacterData::mText`. |
| 9047 | // However, if we're in a password field, this points to `mMaskedBuffer`. |
| 9048 | const CharacterDataBuffer* mCharacterDataBuffer; |
| 9049 | // If we're in a password field, this is initialized with mask characters. |
| 9050 | CharacterDataBuffer mMaskedBuffer; |
| 9051 | nsTextFrame* mTextFrame; |
| 9052 | int32_t mDirection; // +1 or -1, or 0 to indicate failure |
| 9053 | int32_t mCharIndex; |
| 9054 | nsTextFrame::TrimmedOffsets mTrimmed; |
| 9055 | nsTArray<bool> mWordBreaks; |
| 9056 | bool mHaveWordBreak; |
| 9057 | }; |
| 9058 | |
| 9059 | static bool IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter, |
| 9060 | bool aRespectClusters, |
| 9061 | const gfxTextRun* aTextRun, |
| 9062 | nsTextFrame* aFrame) { |
| 9063 | if (aIter.IsOriginalCharSkipped()) { |
| 9064 | return false; |
| 9065 | } |
| 9066 | uint32_t index = aIter.GetSkippedOffset(); |
| 9067 | if (aRespectClusters && !aTextRun->IsClusterStart(index)) { |
| 9068 | return false; |
| 9069 | } |
| 9070 | if (index > 0) { |
| 9071 | // Check whether the proposed position is in between the two halves of a |
| 9072 | // surrogate pair, before a Variation Selector character, or within a |
| 9073 | // ligated emoji sequence; if so, this is not a valid character boundary. |
| 9074 | // (In the case where we are respecting clusters, we won't actually get |
| 9075 | // this far because the low surrogate is also marked as non-clusterStart |
| 9076 | // so we'll return FALSE above.) |
| 9077 | const uint32_t offs = AssertedCast<uint32_t>(aIter.GetOriginalOffset()); |
| 9078 | const CharacterDataBuffer& characterDataBuffer = |
| 9079 | aFrame->CharacterDataBuffer(); |
| 9080 | const char16_t ch = characterDataBuffer.CharAt(offs); |
| 9081 | |
| 9082 | if (gfxFontUtils::IsVarSelector(ch) || |
| 9083 | characterDataBuffer.IsLowSurrogateFollowingHighSurrogateAt(offs) || |
| 9084 | (!aTextRun->IsLigatureGroupStart(index) && |
| 9085 | (unicode::GetEmojiPresentation(ch) == unicode::EmojiDefault || |
| 9086 | (unicode::GetEmojiPresentation(ch) == unicode::TextDefault && |
| 9087 | offs + 1 < characterDataBuffer.GetLength() && |
| 9088 | characterDataBuffer.CharAt(offs + 1) == |
| 9089 | gfxFontUtils::kUnicodeVS16)))) { |
| 9090 | return false; |
| 9091 | } |
| 9092 | |
| 9093 | // If the proposed position is before a high surrogate, we need to decode |
| 9094 | // the surrogate pair (if valid) and check the resulting character. |
| 9095 | if (IsHighSurrogate(ch)) { |
| 9096 | if (const char32_t ucs4 = characterDataBuffer.ScalarValueAt(offs)) { |
| 9097 | // If the character is a (Plane-14) variation selector, |
| 9098 | // or an emoji character that is ligated with the previous |
| 9099 | // character (i.e. part of a Regional-Indicator flag pair, |
| 9100 | // or an emoji-ZWJ sequence), this is not a valid boundary. |
| 9101 | if (gfxFontUtils::IsVarSelector(ucs4) || |
| 9102 | (!aTextRun->IsLigatureGroupStart(index) && |
| 9103 | unicode::GetEmojiPresentation(ucs4) == unicode::EmojiDefault)) { |
| 9104 | return false; |
| 9105 | } |
| 9106 | } |
| 9107 | } |
| 9108 | } |
| 9109 | return true; |
| 9110 | } |
| 9111 | |
| 9112 | nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetCharacter( |
| 9113 | bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) { |
| 9114 | const int32_t contentLengthInFrame = GetContentLength(); |
| 9115 | NS_ASSERTION(aOffset && *aOffset <= contentLengthInFrame,do { if (!(aOffset && *aOffset <= contentLengthInFrame )) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aOffset out of range" , "aOffset && *aOffset <= contentLengthInFrame", "./../../../layout/generic/nsTextFrame.cpp" , 9116); MOZ_PretendNoReturn(); } } while (0) |
| 9116 | "aOffset out of range")do { if (!(aOffset && *aOffset <= contentLengthInFrame )) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aOffset out of range" , "aOffset && *aOffset <= contentLengthInFrame", "./../../../layout/generic/nsTextFrame.cpp" , 9116); MOZ_PretendNoReturn(); } } while (0); |
| 9117 | |
| 9118 | if (!aOptions.mIgnoreUserStyleAll) { |
| 9119 | StyleUserSelect selectStyle; |
| 9120 | (void)IsSelectable(&selectStyle); |
| 9121 | if (selectStyle == StyleUserSelect::All) { |
| 9122 | return CONTINUE_UNSELECTABLE; |
| 9123 | } |
| 9124 | } |
| 9125 | |
| 9126 | gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); |
| 9127 | if (!mTextRun) { |
| 9128 | return CONTINUE_EMPTY; |
| 9129 | } |
| 9130 | const TrimmedOffsets trimmed = |
| 9131 | GetTrimmedOffsets(CharacterDataBuffer(), TrimmedOffsetFlags::NoTrimAfter); |
| 9132 | |
| 9133 | // A negative offset means "end of frame". |
| 9134 | const int32_t offset = |
| 9135 | GetContentOffset() + (*aOffset < 0 ? contentLengthInFrame : *aOffset); |
| 9136 | |
| 9137 | if (!aForward) { |
| 9138 | const int32_t endOffset = [&]() -> int32_t { |
| 9139 | const int32_t minEndOffset = std::min(trimmed.GetEnd(), offset); |
| 9140 | if (minEndOffset <= trimmed.mStart || |
| 9141 | minEndOffset + 1 >= trimmed.GetEnd()) { |
| 9142 | return minEndOffset; |
| 9143 | } |
| 9144 | // If the end offset points a character in this frame and it's skipped |
| 9145 | // character, we want to scan previous character of the prceding first |
| 9146 | // non-skipped character. |
| 9147 | for (const int32_t i : |
| 9148 | Reversed(IntegerRange(trimmed.mStart, minEndOffset + 1))) { |
| 9149 | iter.SetOriginalOffset(i); |
| 9150 | if (!iter.IsOriginalCharSkipped()) { |
| 9151 | return i; |
| 9152 | } |
| 9153 | } |
| 9154 | return trimmed.mStart; |
| 9155 | }(); |
| 9156 | // If we're at the start of a line, look at the next continuation |
| 9157 | if (endOffset <= trimmed.mStart) { |
| 9158 | *aOffset = 0; |
| 9159 | return CONTINUE; |
| 9160 | } |
| 9161 | // If at the beginning of the line, look at the previous continuation |
| 9162 | for (const int32_t i : Reversed(IntegerRange(trimmed.mStart, endOffset))) { |
| 9163 | iter.SetOriginalOffset(i); |
| 9164 | // If we entered into a skipped char range again, we should skip all |
| 9165 | // of them. However, we cannot know the number of preceding skipped |
| 9166 | // chars. Therefore, we cannot skip all of them once. |
| 9167 | if (iter.IsOriginalCharSkipped()) { |
| 9168 | continue; |
| 9169 | } |
| 9170 | if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun, |
| 9171 | this)) { |
| 9172 | *aOffset = i - mContentOffset; |
| 9173 | return FOUND; |
| 9174 | } |
| 9175 | } |
| 9176 | *aOffset = 0; |
| 9177 | return CONTINUE; |
| 9178 | } |
| 9179 | |
| 9180 | // If we're at the end of a line, look at the next continuation |
| 9181 | if (offset + 1 > trimmed.GetEnd()) { |
| 9182 | *aOffset = contentLengthInFrame; |
| 9183 | return CONTINUE; |
| 9184 | } |
| 9185 | |
| 9186 | iter.SetOriginalOffset(offset); |
| 9187 | |
| 9188 | // If we're at a preformatted linefeed, look at the next continutation |
| 9189 | if (offset < trimmed.GetEnd() && StyleText()->NewlineIsSignificant(this) && |
| 9190 | iter.GetSkippedOffset() < mTextRun->GetLength() && |
| 9191 | mTextRun->CharIsNewline(iter.GetSkippedOffset())) { |
| 9192 | *aOffset = contentLengthInFrame; |
| 9193 | return CONTINUE; |
| 9194 | } |
| 9195 | |
| 9196 | const int32_t scanStartOffset = [&]() -> int32_t { |
| 9197 | // If current char is skipped, scan starting from the following |
| 9198 | // non-skipped char. |
| 9199 | int32_t skippedLength = 0; |
| 9200 | if (iter.IsOriginalCharSkipped(&skippedLength)) { |
| 9201 | const int32_t skippedLengthInFrame = |
| 9202 | std::min(skippedLength, trimmed.GetEnd() - iter.GetOriginalOffset()); |
| 9203 | return iter.GetOriginalOffset() + skippedLengthInFrame + 1; |
| 9204 | } |
| 9205 | return iter.GetOriginalOffset() + 1; |
| 9206 | }(); |
| 9207 | |
| 9208 | for (int32_t i = scanStartOffset; i < trimmed.GetEnd(); i++) { |
| 9209 | iter.SetOriginalOffset(i); |
| 9210 | // If we entered into a skipped char range again, we should skip all |
| 9211 | // of them. |
| 9212 | int32_t skippedLength = 0; |
| 9213 | if (iter.IsOriginalCharSkipped(&skippedLength)) { |
| 9214 | const int32_t skippedLengthInFrame = |
| 9215 | std::min(skippedLength, trimmed.GetEnd() - iter.GetOriginalOffset()); |
| 9216 | if (skippedLengthInFrame) { |
| 9217 | i += skippedLengthInFrame - 1; |
| 9218 | } |
| 9219 | continue; |
| 9220 | } |
| 9221 | if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun, |
| 9222 | this)) { |
| 9223 | *aOffset = i - mContentOffset; |
| 9224 | return FOUND; |
| 9225 | } |
| 9226 | } |
| 9227 | |
| 9228 | *aOffset = trimmed.GetEnd() - mContentOffset; |
| 9229 | return FOUND; |
| 9230 | } |
| 9231 | |
| 9232 | bool ClusterIterator::IsInlineWhitespace() const { |
| 9233 | NS_ASSERTION(mCharIndex >= 0, "No cluster selected")do { if (!(mCharIndex >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "No cluster selected", "mCharIndex >= 0", "./../../../layout/generic/nsTextFrame.cpp" , 9233); MOZ_PretendNoReturn(); } } while (0); |
| 9234 | return IsSelectionInlineWhitespace(*mCharacterDataBuffer, mCharIndex); |
| 9235 | } |
| 9236 | |
| 9237 | bool ClusterIterator::IsNewline() const { |
| 9238 | NS_ASSERTION(mCharIndex >= 0, "No cluster selected")do { if (!(mCharIndex >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "No cluster selected", "mCharIndex >= 0", "./../../../layout/generic/nsTextFrame.cpp" , 9238); MOZ_PretendNoReturn(); } } while (0); |
| 9239 | return IsSelectionNewline(*mCharacterDataBuffer, mCharIndex); |
| 9240 | } |
| 9241 | |
| 9242 | bool ClusterIterator::IsPunctuation() const { |
| 9243 | NS_ASSERTION(mCharIndex >= 0, "No cluster selected")do { if (!(mCharIndex >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "No cluster selected", "mCharIndex >= 0", "./../../../layout/generic/nsTextFrame.cpp" , 9243); MOZ_PretendNoReturn(); } } while (0); |
| 9244 | const char16_t ch = |
| 9245 | mCharacterDataBuffer->CharAt(AssertedCast<uint32_t>(mCharIndex)); |
| 9246 | return IsPunctuationForWordSelect(ch); |
| 9247 | } |
| 9248 | |
| 9249 | intl::Script ClusterIterator::ScriptCode() const { |
| 9250 | NS_ASSERTION(mCharIndex >= 0, "No cluster selected")do { if (!(mCharIndex >= 0)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "No cluster selected", "mCharIndex >= 0", "./../../../layout/generic/nsTextFrame.cpp" , 9250); MOZ_PretendNoReturn(); } } while (0); |
| 9251 | const char16_t ch = |
| 9252 | mCharacterDataBuffer->CharAt(AssertedCast<uint32_t>(mCharIndex)); |
| 9253 | return intl::UnicodeProperties::GetScriptCode(ch); |
| 9254 | } |
| 9255 | |
| 9256 | static inline bool IsKorean(intl::Script aScript) { |
| 9257 | // We only need to check for HANGUL script code; there is a script code |
| 9258 | // KOREAN but this is not assigned to any codepoints. (If that ever changes, |
| 9259 | // we could check for both codes here.) |
| 9260 | MOZ_ASSERT(aScript != intl::Script::KOREAN, "unexpected script code")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aScript != intl::Script::KOREAN)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aScript != intl::Script::KOREAN ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "aScript != intl::Script::KOREAN" " (" "unexpected script code" ")", "./../../../layout/generic/nsTextFrame.cpp", 9260); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aScript != intl::Script::KOREAN" ") (" "unexpected script code" ")"); do { MOZ_CrashSequence(__null, 9260); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 9261 | return aScript == intl::Script::HANGUL; |
| 9262 | } |
| 9263 | |
| 9264 | int32_t ClusterIterator::GetAfterInternal() const { |
| 9265 | if (mCharacterDataBuffer->IsHighSurrogateFollowedByLowSurrogateAt( |
| 9266 | AssertedCast<uint32_t>(mCharIndex))) { |
| 9267 | return mCharIndex + 2; |
| 9268 | } |
| 9269 | return mCharIndex + 1; |
| 9270 | } |
| 9271 | |
| 9272 | bool ClusterIterator::NextCluster() { |
| 9273 | if (!mDirection) { |
| 9274 | return false; |
| 9275 | } |
| 9276 | const gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated); |
| 9277 | |
| 9278 | mHaveWordBreak = false; |
| 9279 | while (true) { |
| 9280 | bool keepGoing = false; |
| 9281 | if (mDirection > 0) { |
| 9282 | if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd()) { |
| 9283 | return false; |
| 9284 | } |
| 9285 | keepGoing = mIterator.IsOriginalCharSkipped() || |
| 9286 | mIterator.GetOriginalOffset() < mTrimmed.mStart || |
| 9287 | !textRun->IsClusterStart(mIterator.GetSkippedOffset()); |
| 9288 | mCharIndex = mIterator.GetOriginalOffset(); |
| 9289 | mIterator.AdvanceOriginal(1); |
| 9290 | } else { |
| 9291 | if (mIterator.GetOriginalOffset() <= mTrimmed.mStart) { |
| 9292 | // Trimming can skip backward word breakers, see bug 1667138 |
| 9293 | return mHaveWordBreak; |
| 9294 | } |
| 9295 | mIterator.AdvanceOriginal(-1); |
| 9296 | keepGoing = mIterator.IsOriginalCharSkipped() || |
| 9297 | mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() || |
| 9298 | !textRun->IsClusterStart(mIterator.GetSkippedOffset()); |
| 9299 | mCharIndex = mIterator.GetOriginalOffset(); |
| 9300 | } |
| 9301 | |
| 9302 | if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) { |
| 9303 | mHaveWordBreak = true; |
| 9304 | } |
| 9305 | if (!keepGoing) { |
| 9306 | return true; |
| 9307 | } |
| 9308 | } |
| 9309 | } |
| 9310 | |
| 9311 | ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, |
| 9312 | int32_t aDirection, nsString& aContext, |
| 9313 | bool aTrimSpaces) |
| 9314 | : mIterator(aTextFrame->EnsureTextRun(nsTextFrame::eInflated)), |
| 9315 | mTextFrame(aTextFrame), |
| 9316 | mDirection(aDirection), |
| 9317 | mCharIndex(-1), |
| 9318 | mHaveWordBreak(false) { |
| 9319 | gfxTextRun* textRun = aTextFrame->GetTextRun(nsTextFrame::eInflated); |
| 9320 | if (!textRun) { |
| 9321 | mDirection = 0; // signal failure |
| 9322 | return; |
| 9323 | } |
| 9324 | |
| 9325 | mCharacterDataBuffer = &aTextFrame->CharacterDataBuffer(); |
| 9326 | |
| 9327 | const uint32_t textOffset = |
| 9328 | AssertedCast<uint32_t>(aTextFrame->GetContentOffset()); |
| 9329 | const uint32_t textLen = |
| 9330 | AssertedCast<uint32_t>(aTextFrame->GetContentLength()); |
| 9331 | |
| 9332 | // If we're in a password field, some characters may be masked. In such |
| 9333 | // case, we need to treat each masked character as a mask character since |
| 9334 | // we shouldn't expose word boundary which is hidden by the masking. |
| 9335 | if (aTextFrame->GetContent() && mCharacterDataBuffer->GetLength() > 0 && |
| 9336 | aTextFrame->GetContent()->HasFlag(NS_MAYBE_MASKED) && |
| 9337 | (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed)) { |
| 9338 | const char16_t kPasswordMask = TextEditor::PasswordMask(); |
| 9339 | const nsTransformedTextRun* transformedTextRun = |
| 9340 | static_cast<const nsTransformedTextRun*>(textRun); |
| 9341 | // Use nsString and not nsAutoString so that we get a nsStringBuffer which |
| 9342 | // can be just AddRefed in `mMaskedFrag`. |
| 9343 | nsString maskedText; |
| 9344 | maskedText.SetCapacity(mCharacterDataBuffer->GetLength()); |
| 9345 | // Note that aTextFrame may not cover the whole of mFrag (in cases with |
| 9346 | // bidi continuations), so we cannot rely on its textrun (and associated |
| 9347 | // styles) being available for the entire fragment. |
| 9348 | uint32_t i = 0; |
| 9349 | // Just copy any text that precedes what aTextFrame covers. |
| 9350 | while (i < textOffset) { |
| 9351 | maskedText.Append(mCharacterDataBuffer->CharAt(i++)); |
| 9352 | } |
| 9353 | // For the range covered by aTextFrame, mask chars if appropriate. |
| 9354 | while (i < textOffset + textLen) { |
| 9355 | uint32_t skippedOffset = mIterator.ConvertOriginalToSkipped(i); |
| 9356 | bool mask = |
| 9357 | skippedOffset < transformedTextRun->GetLength() |
| 9358 | ? transformedTextRun->mStyles[skippedOffset]->mMaskPassword |
| 9359 | : false; |
| 9360 | if (mCharacterDataBuffer->IsHighSurrogateFollowedByLowSurrogateAt(i)) { |
| 9361 | if (mask) { |
| 9362 | maskedText.Append(kPasswordMask); |
| 9363 | maskedText.Append(kPasswordMask); |
| 9364 | } else { |
| 9365 | maskedText.Append(mCharacterDataBuffer->CharAt(i)); |
| 9366 | maskedText.Append(mCharacterDataBuffer->CharAt(i + 1)); |
| 9367 | } |
| 9368 | i += 2; |
| 9369 | } else { |
| 9370 | maskedText.Append(mask ? kPasswordMask |
| 9371 | : mCharacterDataBuffer->CharAt(i)); |
| 9372 | ++i; |
| 9373 | } |
| 9374 | } |
| 9375 | // Copy any trailing text from the fragment. |
| 9376 | while (i < mCharacterDataBuffer->GetLength()) { |
| 9377 | maskedText.Append(mCharacterDataBuffer->CharAt(i++)); |
| 9378 | } |
| 9379 | mMaskedBuffer.SetTo(maskedText, mCharacterDataBuffer->IsBidi(), true); |
| 9380 | mCharacterDataBuffer = &mMaskedBuffer; |
| 9381 | } |
| 9382 | |
| 9383 | mIterator.SetOriginalOffset(aPosition); |
| 9384 | mTrimmed = aTextFrame->GetTrimmedOffsets( |
| 9385 | *mCharacterDataBuffer, |
| 9386 | aTrimSpaces ? nsTextFrame::TrimmedOffsetFlags::Default |
| 9387 | : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter | |
| 9388 | nsTextFrame::TrimmedOffsetFlags::NoTrimBefore); |
| 9389 | |
| 9390 | // Allocate an extra element to record the word break at the end of the line |
| 9391 | // or text run in mWordBreak[textLen]. |
| 9392 | mWordBreaks.AppendElements(textLen + 1); |
| 9393 | PodZero(mWordBreaks.Elements(), textLen + 1); |
| 9394 | uint32_t textStart; |
| 9395 | if (aDirection > 0) { |
| 9396 | if (aContext.IsEmpty()) { |
| 9397 | // No previous context, so it must be the start of a line or text run |
| 9398 | mWordBreaks[0] = true; |
| 9399 | } |
| 9400 | textStart = aContext.Length(); |
| 9401 | mCharacterDataBuffer->AppendTo(aContext, textOffset, textLen); |
| 9402 | } else { |
| 9403 | if (aContext.IsEmpty()) { |
| 9404 | // No following context, so it must be the end of a line or text run |
| 9405 | mWordBreaks[textLen] = true; |
| 9406 | } |
| 9407 | textStart = 0; |
| 9408 | nsAutoString str; |
| 9409 | mCharacterDataBuffer->AppendTo(str, textOffset, textLen); |
| 9410 | aContext.Insert(str, 0); |
| 9411 | } |
| 9412 | |
| 9413 | const uint32_t textEnd = textStart + textLen; |
| 9414 | intl::WordBreakIteratorUtf16 wordBreakIter(aContext); |
| 9415 | Maybe<uint32_t> nextBreak = |
| 9416 | wordBreakIter.Seek(textStart > 0 ? textStart - 1 : textStart); |
| 9417 | while (nextBreak && *nextBreak <= textEnd) { |
| 9418 | mWordBreaks[*nextBreak - textStart] = true; |
| 9419 | nextBreak = wordBreakIter.Next(); |
| 9420 | } |
| 9421 | |
| 9422 | MOZ_ASSERT(textEnd != aContext.Length() || mWordBreaks[textLen],do { static_assert( mozilla::detail::AssertionConditionType< decltype(textEnd != aContext.Length() || mWordBreaks[textLen] )>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(textEnd != aContext.Length() || mWordBreaks[textLen] ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "textEnd != aContext.Length() || mWordBreaks[textLen]" " (" "There should be a word break at the end of a line or text run!" ")", "./../../../layout/generic/nsTextFrame.cpp", 9423); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "textEnd != aContext.Length() || mWordBreaks[textLen]" ") (" "There should be a word break at the end of a line or text run!" ")"); do { MOZ_CrashSequence(__null, 9423); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
| 9423 | "There should be a word break at the end of a line or text run!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(textEnd != aContext.Length() || mWordBreaks[textLen] )>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(textEnd != aContext.Length() || mWordBreaks[textLen] ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "textEnd != aContext.Length() || mWordBreaks[textLen]" " (" "There should be a word break at the end of a line or text run!" ")", "./../../../layout/generic/nsTextFrame.cpp", 9423); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "textEnd != aContext.Length() || mWordBreaks[textLen]" ") (" "There should be a word break at the end of a line or text run!" ")"); do { MOZ_CrashSequence(__null, 9423); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 9424 | } |
| 9425 | |
| 9426 | nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetWord( |
| 9427 | bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect, |
| 9428 | int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) { |
| 9429 | int32_t contentLength = GetContentLength(); |
| 9430 | NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range")do { if (!(aOffset && *aOffset <= contentLength)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aOffset out of range", "aOffset && *aOffset <= contentLength" , "./../../../layout/generic/nsTextFrame.cpp", 9430); MOZ_PretendNoReturn (); } } while (0); |
| 9431 | |
| 9432 | StyleUserSelect selectStyle; |
| 9433 | (void)IsSelectable(&selectStyle); |
| 9434 | if (selectStyle == StyleUserSelect::All) { |
| 9435 | return CONTINUE_UNSELECTABLE; |
| 9436 | } |
| 9437 | |
| 9438 | int32_t offset = |
| 9439 | GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset); |
| 9440 | ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext, |
| 9441 | aTrimSpaces); |
| 9442 | |
| 9443 | if (!cIter.NextCluster()) { |
| 9444 | return CONTINUE_EMPTY; |
| 9445 | } |
| 9446 | |
| 9447 | // Do we need to check for Korean characters? |
| 9448 | bool is2b = CharacterDataBuffer().Is2b(); |
| 9449 | do { |
| 9450 | bool isPunctuation = cIter.IsPunctuation(); |
| 9451 | bool isInlineWhitespace = cIter.IsInlineWhitespace(); |
| 9452 | bool isWhitespace = isInlineWhitespace || cIter.IsNewline(); |
| 9453 | bool isWordBreakBefore = cIter.HaveWordBreakBefore(); |
| 9454 | // If the text is one-byte, we don't actually care about script code as |
| 9455 | // there cannot be any Korean in the frame. |
| 9456 | intl::Script scriptCode = is2b ? cIter.ScriptCode() : intl::Script::COMMON; |
| 9457 | if (!isWhitespace || isInlineWhitespace) { |
| 9458 | aState->SetSawInlineCharacter(); |
| 9459 | } |
| 9460 | if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) { |
| 9461 | aState->SetSawBeforeType(); |
| 9462 | aState->Update(isPunctuation, isWhitespace, scriptCode); |
| 9463 | continue; |
| 9464 | } |
| 9465 | // See if we can break before the current cluster |
| 9466 | if (!aState->mAtStart) { |
| 9467 | bool canBreak; |
| 9468 | if (isPunctuation != aState->mLastCharWasPunctuation) { |
| 9469 | canBreak = BreakWordBetweenPunctuation(aState, aForward, isPunctuation, |
| 9470 | isWhitespace, aIsKeyboardSelect); |
| 9471 | } else if (!aState->mLastCharWasWhitespace && !isWhitespace && |
| 9472 | !isPunctuation && isWordBreakBefore) { |
| 9473 | // if both the previous and the current character are not white |
| 9474 | // space but this can be word break before, we don't need to eat |
| 9475 | // a white space in this case. This case happens in some languages |
| 9476 | // that their words are not separated by white spaces. E.g., |
| 9477 | // Japanese and Chinese. |
| 9478 | canBreak = true; |
| 9479 | } else { |
| 9480 | canBreak = isWordBreakBefore && aState->mSawBeforeType && |
| 9481 | (aWordSelectEatSpace != isWhitespace); |
| 9482 | } |
| 9483 | // Special-case for Korean: treat a boundary between Hangul & non-Hangul |
| 9484 | // characters as a word boundary (see bug 1973393 and UAX#29). |
| 9485 | if (!canBreak && is2b && aState->mLastScript != intl::Script::INVALID && |
| 9486 | IsKorean(aState->mLastScript) != IsKorean(scriptCode)) { |
| 9487 | canBreak = true; |
| 9488 | } |
| 9489 | if (canBreak) { |
| 9490 | *aOffset = cIter.GetBeforeOffset() - mContentOffset; |
| 9491 | return FOUND; |
| 9492 | } |
| 9493 | } |
| 9494 | aState->Update(isPunctuation, isWhitespace, scriptCode); |
| 9495 | } while (cIter.NextCluster()); |
| 9496 | |
| 9497 | *aOffset = cIter.GetAfterOffset() - mContentOffset; |
| 9498 | return CONTINUE; |
| 9499 | } |
| 9500 | |
| 9501 | bool nsTextFrame::HasVisibleText() { |
| 9502 | // Text in the range is visible if there is at least one character in the |
| 9503 | // range that is not skipped and is mapped by this frame (which is the primary |
| 9504 | // frame) or one of its continuations. |
| 9505 | for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) { |
| 9506 | int32_t dummyOffset = 0; |
| 9507 | if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) { |
| 9508 | return true; |
| 9509 | } |
| 9510 | } |
| 9511 | return false; |
| 9512 | } |
| 9513 | |
| 9514 | std::pair<int32_t, int32_t> nsTextFrame::GetOffsets() const { |
| 9515 | return std::make_pair(GetContentOffset(), GetContentEnd()); |
| 9516 | } |
| 9517 | |
| 9518 | static bool IsFirstLetterPrefixPunctuation(uint32_t aChar) { |
| 9519 | switch (mozilla::unicode::GetGeneralCategory(aChar)) { |
| 9520 | case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */ |
| 9521 | case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION: /* Pd */ |
| 9522 | case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: /* Pe */ |
| 9523 | case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: /* Pf */ |
| 9524 | case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */ |
| 9525 | case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: /* Po */ |
| 9526 | case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION: /* Ps */ |
| 9527 | return true; |
| 9528 | default: |
| 9529 | return false; |
| 9530 | } |
| 9531 | } |
| 9532 | |
| 9533 | static bool IsFirstLetterSuffixPunctuation(uint32_t aChar) { |
| 9534 | switch (mozilla::unicode::GetGeneralCategory(aChar)) { |
| 9535 | case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */ |
| 9536 | case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: /* Pe */ |
| 9537 | case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: /* Pf */ |
| 9538 | case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */ |
| 9539 | case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: /* Po */ |
| 9540 | return true; |
| 9541 | default: |
| 9542 | return false; |
| 9543 | } |
| 9544 | } |
| 9545 | |
| 9546 | static int32_t FindEndOfPrefixPunctuationRun(const CharacterDataBuffer& aBuffer, |
| 9547 | const gfxTextRun* aTextRun, |
| 9548 | gfxSkipCharsIterator* aIter, |
| 9549 | int32_t aOffset, int32_t aStart, |
| 9550 | int32_t aEnd) { |
| 9551 | int32_t i; |
| 9552 | for (i = aStart; i < aEnd - aOffset; ++i) { |
| 9553 | if (IsFirstLetterPrefixPunctuation( |
| 9554 | aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)))) { |
| 9555 | aIter->SetOriginalOffset(aOffset + i); |
| 9556 | FindClusterEnd(aTextRun, aEnd, aIter); |
| 9557 | i = aIter->GetOriginalOffset() - aOffset; |
| 9558 | } else { |
| 9559 | break; |
| 9560 | } |
| 9561 | } |
| 9562 | return i; |
| 9563 | } |
| 9564 | |
| 9565 | static int32_t FindEndOfSuffixPunctuationRun(const CharacterDataBuffer& aBuffer, |
| 9566 | const gfxTextRun* aTextRun, |
| 9567 | gfxSkipCharsIterator* aIter, |
| 9568 | int32_t aOffset, int32_t aStart, |
| 9569 | int32_t aEnd) { |
| 9570 | int32_t i; |
| 9571 | for (i = aStart; i < aEnd - aOffset; ++i) { |
| 9572 | if (IsFirstLetterSuffixPunctuation( |
| 9573 | aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)))) { |
| 9574 | aIter->SetOriginalOffset(aOffset + i); |
| 9575 | FindClusterEnd(aTextRun, aEnd, aIter); |
| 9576 | i = aIter->GetOriginalOffset() - aOffset; |
| 9577 | } else { |
| 9578 | break; |
| 9579 | } |
| 9580 | } |
| 9581 | return i; |
| 9582 | } |
| 9583 | |
| 9584 | /** |
| 9585 | * Returns true if this text frame completes the first-letter, false |
| 9586 | * if it does not contain a true "letter". |
| 9587 | * If returns true, then it also updates aLength to cover just the first-letter |
| 9588 | * text. |
| 9589 | * |
| 9590 | * XXX :first-letter should be handled during frame construction |
| 9591 | * (and it has a good bit in common with nextBidi) |
| 9592 | * |
| 9593 | * @param aLength an in/out parameter: on entry contains the maximum length to |
| 9594 | * return, on exit returns length of the first-letter fragment (which may |
| 9595 | * include leading and trailing punctuation, for example) |
| 9596 | */ |
| 9597 | static bool FindFirstLetterRange(const CharacterDataBuffer& aBuffer, |
| 9598 | const nsAtom* aLang, |
| 9599 | const gfxTextRun* aTextRun, int32_t aOffset, |
| 9600 | const gfxSkipCharsIterator& aIter, |
| 9601 | int32_t* aLength) { |
| 9602 | int32_t length = *aLength; |
| 9603 | int32_t endOffset = aOffset + length; |
| 9604 | gfxSkipCharsIterator iter(aIter); |
| 9605 | |
| 9606 | // Currently the only language-specific special case we handle here is the |
| 9607 | // Dutch "IJ" digraph. |
| 9608 | auto LangTagIsDutch = [](const nsAtom* aLang) -> bool { |
| 9609 | if (!aLang) { |
| 9610 | return false; |
| 9611 | } |
| 9612 | if (aLang == nsGkAtoms::nl) { |
| 9613 | return true; |
| 9614 | } |
| 9615 | // We don't need to fully parse as a Locale; just check the initial subtag. |
| 9616 | nsDependentAtomString langStr(aLang); |
| 9617 | int32_t index = langStr.FindChar('-'); |
| 9618 | if (index > 0) { |
| 9619 | langStr.Truncate(index); |
| 9620 | return langStr.EqualsLiteral("nl"); |
| 9621 | } |
| 9622 | return false; |
| 9623 | }; |
| 9624 | |
| 9625 | // Skip any trimmable leading whitespace. |
| 9626 | int32_t i = GetTrimmableWhitespaceCount(aBuffer, aOffset, length, 1); |
| 9627 | while (true) { |
| 9628 | // Scan past any leading punctuation. This leaves `j` at the first |
| 9629 | // non-punctuation character. |
| 9630 | int32_t j = FindEndOfPrefixPunctuationRun(aBuffer, aTextRun, &iter, aOffset, |
| 9631 | i, endOffset); |
| 9632 | if (j == length) { |
| 9633 | return false; |
| 9634 | } |
| 9635 | |
| 9636 | // Scan past any Unicode whitespace characters after punctuation. |
| 9637 | while (j < length) { |
| 9638 | char16_t ch = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + j)); |
| 9639 | // The spec says to allow "characters that belong to the `Zs` Unicode |
| 9640 | // general category _other than_ U+3000" here. |
| 9641 | if (unicode::GetGeneralCategory(ch) == |
| 9642 | HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR && |
| 9643 | ch != 0x3000) { |
| 9644 | ++j; |
| 9645 | } else { |
| 9646 | break; |
| 9647 | } |
| 9648 | } |
| 9649 | if (j == length) { |
| 9650 | return false; |
| 9651 | } |
| 9652 | if (j == i) { |
| 9653 | // If no whitespace was found, we've finished the first-letter prefix; |
| 9654 | // if there was some, then go back to check for more punctuation. |
| 9655 | break; |
| 9656 | } |
| 9657 | i = j; |
| 9658 | } |
| 9659 | |
| 9660 | // If the next character is not a letter, number or symbol, there is no |
| 9661 | // first-letter. |
| 9662 | // Return true so that we don't go on looking, but set aLength to 0. |
| 9663 | const char32_t usv = |
| 9664 | aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)); |
| 9665 | if (!nsContentUtils::IsAlphanumericOrSymbol(usv)) { |
| 9666 | *aLength = 0; |
| 9667 | return true; |
| 9668 | } |
| 9669 | |
| 9670 | // Consume another cluster (the actual first letter): |
| 9671 | |
| 9672 | // For complex scripts such as Indic and SEAsian, where first-letter |
| 9673 | // should extend to entire orthographic "syllable" clusters, we don't |
| 9674 | // want to allow this to split a ligature. |
| 9675 | bool allowSplitLigature; |
| 9676 | bool usesIndicHalfForms = false; |
| 9677 | |
| 9678 | typedef intl::Script Script; |
| 9679 | Script script = intl::UnicodeProperties::GetScriptCode(usv); |
| 9680 | switch (script) { |
| 9681 | default: |
| 9682 | allowSplitLigature = true; |
| 9683 | break; |
| 9684 | |
| 9685 | // Don't break regional-indicator ligatures. |
| 9686 | case Script::COMMON: |
| 9687 | allowSplitLigature = !gfxFontUtils::IsRegionalIndicator(usv); |
| 9688 | break; |
| 9689 | |
| 9690 | // For now, lacking any definitive specification of when to apply this |
| 9691 | // behavior, we'll base the decision on the HarfBuzz shaping engine |
| 9692 | // used for each script: those that are handled by the Indic, Tibetan, |
| 9693 | // Myanmar and SEAsian shapers will apply the "don't split ligatures" |
| 9694 | // rule. |
| 9695 | |
| 9696 | // Indic |
| 9697 | case Script::BENGALI: |
| 9698 | case Script::DEVANAGARI: |
| 9699 | case Script::GUJARATI: |
| 9700 | usesIndicHalfForms = true; |
| 9701 | [[fallthrough]]; |
| 9702 | |
| 9703 | case Script::GURMUKHI: |
| 9704 | case Script::KANNADA: |
| 9705 | case Script::MALAYALAM: |
| 9706 | case Script::ORIYA: |
| 9707 | case Script::TAMIL: |
| 9708 | case Script::TELUGU: |
| 9709 | case Script::SINHALA: |
| 9710 | case Script::BALINESE: |
| 9711 | case Script::LEPCHA: |
| 9712 | case Script::REJANG: |
| 9713 | case Script::SUNDANESE: |
| 9714 | case Script::JAVANESE: |
| 9715 | case Script::KAITHI: |
| 9716 | case Script::MEETEI_MAYEK: |
| 9717 | case Script::CHAKMA: |
| 9718 | case Script::SHARADA: |
| 9719 | case Script::TAKRI: |
| 9720 | case Script::KHMER: |
| 9721 | |
| 9722 | // Tibetan |
| 9723 | case Script::TIBETAN: |
| 9724 | |
| 9725 | // Myanmar |
| 9726 | case Script::MYANMAR: |
| 9727 | |
| 9728 | // Other SEAsian |
| 9729 | case Script::BUGINESE: |
| 9730 | case Script::NEW_TAI_LUE: |
| 9731 | case Script::CHAM: |
| 9732 | case Script::TAI_THAM: |
| 9733 | |
| 9734 | // What about Thai/Lao - any special handling needed? |
| 9735 | // Should we special-case Arabic lam-alef? |
| 9736 | |
| 9737 | allowSplitLigature = false; |
| 9738 | break; |
| 9739 | } |
| 9740 | |
| 9741 | // NOTE that FindClusterEnd sets the iterator to the last character that is |
| 9742 | // part of the cluster, NOT to the first character beyond it. |
| 9743 | iter.SetOriginalOffset(aOffset + i); |
| 9744 | FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature); |
| 9745 | |
| 9746 | // Index of the last character included in the first-letter cluster. |
| 9747 | i = iter.GetOriginalOffset() - aOffset; |
| 9748 | |
| 9749 | // Heuristic for Indic scripts that like to form conjuncts: |
| 9750 | // If we ended at a virama that is ligated with the preceding character |
| 9751 | // (e.g. creating a half-form), then don't stop here; include the next |
| 9752 | // cluster as well so that we don't break a conjunct. |
| 9753 | // |
| 9754 | // Unfortunately this cannot distinguish between a letter+virama that ligate |
| 9755 | // to create a half-form (in which case we have a conjunct that should not |
| 9756 | // be broken) and a letter+virama that ligate purely for presentational |
| 9757 | // reasons to position the (visible) virama component (in which case breaking |
| 9758 | // after the virama would be acceptable). So results may be imperfect, |
| 9759 | // depending how the font has chosen to implement visible viramas. |
| 9760 | if (usesIndicHalfForms) { |
| 9761 | while (i + 1 < length && iter.GetSkippedOffset() < aTextRun->GetLength() && |
| 9762 | !aTextRun->IsLigatureGroupStart(iter.GetSkippedOffset())) { |
| 9763 | char32_t c = aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)); |
| 9764 | if (intl::UnicodeProperties::GetCombiningClass(c) == |
| 9765 | HB_UNICODE_COMBINING_CLASS_VIRAMA) { |
| 9766 | iter.AdvanceOriginal(1); |
| 9767 | FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature); |
| 9768 | i = iter.GetOriginalOffset() - aOffset; |
| 9769 | } else { |
| 9770 | break; |
| 9771 | } |
| 9772 | } |
| 9773 | } |
| 9774 | |
| 9775 | if (i + 1 == length) { |
| 9776 | return true; |
| 9777 | } |
| 9778 | |
| 9779 | // Check for Dutch "ij" digraph special case, but only if both letters have |
| 9780 | // the same case. |
| 9781 | if (script == Script::LATIN && LangTagIsDutch(aLang)) { |
| 9782 | char16_t ch1 = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + i)); |
| 9783 | char16_t ch2 = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + i + 1)); |
| 9784 | if ((ch1 == 'i' && ch2 == 'j') || (ch1 == 'I' && ch2 == 'J')) { |
| 9785 | iter.SetOriginalOffset(aOffset + i + 1); |
| 9786 | FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature); |
| 9787 | i = iter.GetOriginalOffset() - aOffset; |
| 9788 | if (i + 1 == length) { |
| 9789 | return true; |
| 9790 | } |
| 9791 | } |
| 9792 | } |
| 9793 | |
| 9794 | // When we reach here, `i` points to the last character of the first-letter |
| 9795 | // cluster, NOT to the first character beyond it. Advance to the next char, |
| 9796 | // ready to check for following whitespace/punctuation: |
| 9797 | ++i; |
| 9798 | |
| 9799 | while (i < length) { |
| 9800 | // Skip over whitespace, except for word separator characters, before the |
| 9801 | // check for following punctuation. But remember the position before the |
| 9802 | // whitespace, in case we need to reset. |
| 9803 | const int32_t preWS = i; |
| 9804 | while (i < length) { |
| 9805 | char16_t ch = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + i)); |
| 9806 | // The spec says the first-letter suffix includes "any intervening |
| 9807 | // typographic space -- characters belonging to the Zs Unicode general |
| 9808 | // category other than U+3000 IDEOGRAPHIC SPACE or a word separator", |
| 9809 | // where "word separator" includes U+0020 and U+00A0. |
| 9810 | if (ch == 0x0020 || ch == 0x00A0 || ch == 0x3000 || |
| 9811 | unicode::GetGeneralCategory(ch) != |
| 9812 | HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR) { |
| 9813 | break; |
| 9814 | } else { |
| 9815 | ++i; |
| 9816 | } |
| 9817 | } |
| 9818 | |
| 9819 | // Consume clusters that start with punctuation. |
| 9820 | const int32_t prePunct = i; |
| 9821 | i = FindEndOfSuffixPunctuationRun(aBuffer, aTextRun, &iter, aOffset, i, |
| 9822 | endOffset); |
| 9823 | |
| 9824 | // If we didn't find punctuation here, then we also don't want to include |
| 9825 | // any preceding whitespace, so reset our index. |
| 9826 | if (i == prePunct) { |
| 9827 | i = preWS; |
| 9828 | break; |
| 9829 | } |
| 9830 | } |
| 9831 | |
| 9832 | if (i < length) { |
| 9833 | *aLength = i; |
| 9834 | } |
| 9835 | return true; |
| 9836 | } |
| 9837 | |
| 9838 | static uint32_t FindStartAfterSkippingWhitespace( |
| 9839 | nsTextFrame::PropertyProvider* aProvider, |
| 9840 | nsIFrame::InlineIntrinsicISizeData* aData, const nsStyleText* aTextStyle, |
| 9841 | gfxSkipCharsIterator* aIterator, uint32_t aFlowEndInTextRun) { |
| 9842 | if (aData->mSkipWhitespace) { |
| 9843 | while (aIterator->GetSkippedOffset() < aFlowEndInTextRun && |
| 9844 | IsTrimmableSpace(aProvider->GetCharacterDataBuffer(), |
| 9845 | aIterator->GetOriginalOffset(), aTextStyle)) { |
| 9846 | aIterator->AdvanceOriginal(1); |
| 9847 | } |
| 9848 | } |
| 9849 | return aIterator->GetSkippedOffset(); |
| 9850 | } |
| 9851 | |
| 9852 | float nsTextFrame::GetFontSizeInflation() const { |
| 9853 | if (!HasFontSizeInflation()) { |
| 9854 | return 1.0f; |
| 9855 | } |
| 9856 | return GetProperty(FontSizeInflationProperty()); |
| 9857 | } |
| 9858 | |
| 9859 | void nsTextFrame::SetFontSizeInflation(float aInflation) { |
| 9860 | if (aInflation == 1.0f) { |
| 9861 | if (HasFontSizeInflation()) { |
| 9862 | RemoveStateBits(TEXT_HAS_FONT_INFLATION); |
| 9863 | RemoveProperty(FontSizeInflationProperty()); |
| 9864 | } |
| 9865 | return; |
| 9866 | } |
| 9867 | |
| 9868 | AddStateBits(TEXT_HAS_FONT_INFLATION); |
| 9869 | SetProperty(FontSizeInflationProperty(), aInflation); |
| 9870 | } |
| 9871 | |
| 9872 | void nsTextFrame::SetHangableISize(nscoord aISize) { |
| 9873 | MOZ_ASSERT(aISize >= 0, "unexpected negative hangable advance")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aISize >= 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aISize >= 0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aISize >= 0" " (" "unexpected negative hangable advance" ")", "./../../../layout/generic/nsTextFrame.cpp" , 9873); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aISize >= 0" ") (" "unexpected negative hangable advance" ")"); do { MOZ_CrashSequence (__null, 9873); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 9874 | if (aISize <= 0) { |
| 9875 | ClearHangableISize(); |
| 9876 | return; |
| 9877 | } |
| 9878 | SetProperty(HangableWhitespaceProperty(), aISize); |
| 9879 | mPropertyFlags |= PropertyFlags::HangableWS; |
| 9880 | } |
| 9881 | |
| 9882 | nscoord nsTextFrame::GetHangableISize() const { |
| 9883 | MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::HangableWS) ==do { static_assert( mozilla::detail::AssertionConditionType< decltype(!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty()))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!!(mPropertyFlags & PropertyFlags ::HangableWS) == HasProperty(HangableWhitespaceProperty())))) , 0))) { do { } while (false); MOZ_ReportAssertionFailure("!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty())" " (" "flag/property mismatch!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 9885); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty())" ") (" "flag/property mismatch!" ")"); do { MOZ_CrashSequence (__null, 9885); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 9884 | HasProperty(HangableWhitespaceProperty()),do { static_assert( mozilla::detail::AssertionConditionType< decltype(!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty()))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!!(mPropertyFlags & PropertyFlags ::HangableWS) == HasProperty(HangableWhitespaceProperty())))) , 0))) { do { } while (false); MOZ_ReportAssertionFailure("!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty())" " (" "flag/property mismatch!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 9885); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty())" ") (" "flag/property mismatch!" ")"); do { MOZ_CrashSequence (__null, 9885); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 9885 | "flag/property mismatch!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty()))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!!(mPropertyFlags & PropertyFlags ::HangableWS) == HasProperty(HangableWhitespaceProperty())))) , 0))) { do { } while (false); MOZ_ReportAssertionFailure("!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty())" " (" "flag/property mismatch!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 9885); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!!(mPropertyFlags & PropertyFlags::HangableWS) == HasProperty(HangableWhitespaceProperty())" ") (" "flag/property mismatch!" ")"); do { MOZ_CrashSequence (__null, 9885); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 9886 | return (mPropertyFlags & PropertyFlags::HangableWS) |
| 9887 | ? GetProperty(HangableWhitespaceProperty()) |
| 9888 | : 0; |
| 9889 | } |
| 9890 | |
| 9891 | void nsTextFrame::ClearHangableISize() { |
| 9892 | if (mPropertyFlags & PropertyFlags::HangableWS) { |
| 9893 | RemoveProperty(HangableWhitespaceProperty()); |
| 9894 | mPropertyFlags &= ~PropertyFlags::HangableWS; |
| 9895 | } |
| 9896 | } |
| 9897 | |
| 9898 | void nsTextFrame::SetTrimmableWS(gfxTextRun::TrimmableWS aTrimmableWS) { |
| 9899 | MOZ_ASSERT(aTrimmableWS.mAdvance >= 0, "negative trimmable size")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aTrimmableWS.mAdvance >= 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aTrimmableWS.mAdvance >= 0 ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "aTrimmableWS.mAdvance >= 0" " (" "negative trimmable size" ")", "./../../../layout/generic/nsTextFrame.cpp", 9899); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aTrimmableWS.mAdvance >= 0" ") (" "negative trimmable size" ")"); do { MOZ_CrashSequence(__null, 9899); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 9900 | if (aTrimmableWS.mAdvance <= 0) { |
| 9901 | ClearTrimmableWS(); |
| 9902 | return; |
| 9903 | } |
| 9904 | SetProperty(TrimmableWhitespaceProperty(), aTrimmableWS); |
| 9905 | mPropertyFlags |= PropertyFlags::TrimmableWS; |
| 9906 | } |
| 9907 | |
| 9908 | gfxTextRun::TrimmableWS nsTextFrame::GetTrimmableWS() const { |
| 9909 | MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::TrimmableWS) ==do { static_assert( mozilla::detail::AssertionConditionType< decltype(!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty()))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!!(mPropertyFlags & PropertyFlags ::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())) )), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())" " (" "flag/property mismatch!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 9911); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())" ") (" "flag/property mismatch!" ")"); do { MOZ_CrashSequence (__null, 9911); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 9910 | HasProperty(TrimmableWhitespaceProperty()),do { static_assert( mozilla::detail::AssertionConditionType< decltype(!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty()))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!!(mPropertyFlags & PropertyFlags ::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())) )), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())" " (" "flag/property mismatch!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 9911); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())" ") (" "flag/property mismatch!" ")"); do { MOZ_CrashSequence (__null, 9911); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 9911 | "flag/property mismatch!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty()))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!!(mPropertyFlags & PropertyFlags ::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())) )), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())" " (" "flag/property mismatch!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 9911); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!!(mPropertyFlags & PropertyFlags::TrimmableWS) == HasProperty(TrimmableWhitespaceProperty())" ") (" "flag/property mismatch!" ")"); do { MOZ_CrashSequence (__null, 9911); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 9912 | return (mPropertyFlags & PropertyFlags::TrimmableWS) |
| 9913 | ? GetProperty(TrimmableWhitespaceProperty()) |
| 9914 | : gfxTextRun::TrimmableWS{}; |
| 9915 | } |
| 9916 | |
| 9917 | void nsTextFrame::ClearTrimmableWS() { |
| 9918 | if (mPropertyFlags & PropertyFlags::TrimmableWS) { |
| 9919 | RemoveProperty(TrimmableWhitespaceProperty()); |
| 9920 | mPropertyFlags &= ~PropertyFlags::TrimmableWS; |
| 9921 | } |
| 9922 | } |
| 9923 | |
| 9924 | /* virtual */ |
| 9925 | void nsTextFrame::MarkIntrinsicISizesDirty() { |
| 9926 | ClearTextRuns(); |
| 9927 | nsIFrame::MarkIntrinsicISizesDirty(); |
| 9928 | } |
| 9929 | |
| 9930 | // XXX this doesn't handle characters shaped by line endings. We need to |
| 9931 | // temporarily override the "current line ending" settings. |
| 9932 | void nsTextFrame::AddInlineMinISizeForFlow(gfxContext* aRenderingContext, |
| 9933 | InlineMinISizeData* aData, |
| 9934 | TextRunType aTextRunType) { |
| 9935 | uint32_t flowEndInTextRun; |
| 9936 | gfxSkipCharsIterator iter = |
| 9937 | EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(), |
| 9938 | aData->LineContainer(), aData->mLine, &flowEndInTextRun); |
| 9939 | gfxTextRun* textRun = GetTextRun(aTextRunType); |
| 9940 | if (!textRun) { |
| 9941 | return; |
| 9942 | } |
| 9943 | |
| 9944 | // Pass null for the line container. This will disable tab spacing, but that's |
| 9945 | // OK since we can't really handle tabs for intrinsic sizing anyway. |
| 9946 | const nsStyleText* textStyle = StyleText(); |
| 9947 | const auto& characterDataBuffer = CharacterDataBuffer(); |
| 9948 | |
| 9949 | // If we're hyphenating, the PropertyProvider needs the actual length; |
| 9950 | // otherwise we can just pass INT32_MAX to mean "all the text" |
| 9951 | int32_t len = INT32_MAX(2147483647); |
| 9952 | bool hyphenating = characterDataBuffer.GetLength() > 0 && |
| 9953 | (textStyle->mHyphens == StyleHyphens::Auto || |
| 9954 | (textStyle->mHyphens == StyleHyphens::Manual && |
| 9955 | !!(textRun->GetFlags() & |
| 9956 | gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS))); |
| 9957 | if (hyphenating) { |
| 9958 | gfxSkipCharsIterator tmp(iter); |
| 9959 | len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(), |
| 9960 | tmp.ConvertSkippedToOriginal(flowEndInTextRun)) - |
| 9961 | iter.GetOriginalOffset(); |
| 9962 | } |
| 9963 | PropertyProvider provider(textRun, textStyle, characterDataBuffer, this, iter, |
| 9964 | len, nullptr, 0, aTextRunType, |
| 9965 | aData->mAtStartOfLine); |
| 9966 | |
| 9967 | const bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant(); |
| 9968 | const bool preformatNewlines = textStyle->NewlineIsSignificant(this); |
| 9969 | const bool preformatTabs = textStyle->WhiteSpaceIsSignificant(); |
| 9970 | const bool whitespaceCanHang = |
| 9971 | textStyle->WhiteSpaceCanHangOrVisuallyCollapse(); |
| 9972 | gfxFloat tabWidth = -1; |
| 9973 | const uint32_t start = FindStartAfterSkippingWhitespace( |
| 9974 | &provider, aData, textStyle, &iter, flowEndInTextRun); |
| 9975 | |
| 9976 | // text-combine-upright frame is constantly 1em on inline-axis; but if it has |
| 9977 | // a following sibling with the same style, it contributes nothing. |
| 9978 | if (Style()->IsTextCombined()) { |
| 9979 | if (start < flowEndInTextRun && textRun->CanBreakLineBefore(start)) { |
| 9980 | aData->OptionallyBreak(); |
| 9981 | } |
| 9982 | if (!GetNextSibling() || GetNextSibling()->Style() != Style()) { |
| 9983 | aData->mCurrentLine += provider.GetFontMetrics()->EmHeight(); |
| 9984 | } |
| 9985 | aData->mTrailingWhitespace = 0; |
| 9986 | return; |
| 9987 | } |
| 9988 | |
| 9989 | if (textStyle->EffectiveOverflowWrap() == StyleOverflowWrap::Anywhere && |
| 9990 | textStyle->WordCanWrap(this)) { |
| 9991 | aData->OptionallyBreak(); |
| 9992 | aData->mCurrentLine += textRun->GetMinAdvanceWidth( |
| 9993 | Range(start, flowEndInTextRun), provider.LetterSpacing()); |
| 9994 | aData->mTrailingWhitespace = 0; |
| 9995 | aData->mAtStartOfLine = false; |
| 9996 | aData->OptionallyBreak(); |
| 9997 | return; |
| 9998 | } |
| 9999 | |
| 10000 | AutoTArray<gfxTextRun::HyphenType, BIG_TEXT_NODE_SIZE4096> hyphBuffer; |
| 10001 | if (hyphenating) { |
| 10002 | if (hyphBuffer.AppendElements(flowEndInTextRun - start, fallible)) { |
| 10003 | provider.GetHyphenationBreaks(Range(start, flowEndInTextRun), |
| 10004 | hyphBuffer.Elements()); |
| 10005 | } else { |
| 10006 | hyphenating = false; |
| 10007 | } |
| 10008 | } |
| 10009 | |
| 10010 | const auto* glyphs = textRun->GetCharacterGlyphs(); |
| 10011 | const auto* limit = glyphs + flowEndInTextRun; |
| 10012 | const bool hasSpacing = provider.HasSpacing(); |
| 10013 | nscoord wordAdvance = 0; |
| 10014 | uint32_t wordStart = start; |
| 10015 | for (const auto* g = glyphs + start; g <= limit; ++g) { |
| 10016 | if (!hyphenating) { |
| 10017 | while (g < limit && g->IsSimpleGlyphNoBreakBefore()) { |
| 10018 | wordAdvance += g->GetSimpleAdvance(); |
| 10019 | ++g; |
| 10020 | } |
| 10021 | } |
| 10022 | |
| 10023 | bool preformattedNewline = false; |
| 10024 | bool preformattedTab = false; |
| 10025 | if (g < limit) { |
| 10026 | // XXXldb Shouldn't we be including the newline as part of the |
| 10027 | // segment that it ends rather than part of the segment that it |
| 10028 | // starts? |
| 10029 | using CompressedGlyph = gfxTextRun::CompressedGlyph; |
| 10030 | preformattedNewline = preformatNewlines && g->CharIsNewline(); |
| 10031 | preformattedTab = preformatTabs && g->CharIsTab(); |
| 10032 | if (g->CanBreakBefore() != CompressedGlyph::FLAG_BREAK_TYPE_NORMAL && |
| 10033 | !preformattedNewline && !preformattedTab && |
| 10034 | (!hyphenating || !gfxTextRun::IsOptionalHyphenBreak( |
| 10035 | hyphBuffer[g - glyphs - start]))) { |
| 10036 | // we can't break here (and it's not the end of the flow) |
| 10037 | if (g->IsSimpleGlyph()) { |
| 10038 | wordAdvance += g->GetSimpleAdvance(); |
| 10039 | } else if (!hasSpacing) { |
| 10040 | if (uint32_t count = g->GetGlyphCount()) { |
| 10041 | const auto* details = textRun->GetDetailedGlyphs(g - glyphs, count); |
| 10042 | while (count--) { |
| 10043 | wordAdvance += details->mAdvance; |
| 10044 | ++details; |
| 10045 | } |
| 10046 | } |
| 10047 | } |
| 10048 | continue; |
| 10049 | } |
| 10050 | } |
| 10051 | |
| 10052 | const uint32_t wordEnd = g - glyphs; |
| 10053 | if (wordEnd > wordStart) { |
| 10054 | nscoord width = !hasSpacing && |
| 10055 | (glyphs + wordStart)->IsLigatureGroupStart() && |
| 10056 | (wordEnd == flowEndInTextRun || |
| 10057 | (glyphs + wordEnd)->IsLigatureGroupStart()) |
| 10058 | ? wordAdvance |
| 10059 | : NSToCoordCeilClamped(textRun->GetAdvanceWidth( |
| 10060 | Range(wordStart, wordEnd), &provider)); |
| 10061 | width = std::max(0, width); |
| 10062 | aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width); |
| 10063 | aData->mAtStartOfLine = false; |
| 10064 | |
| 10065 | if (collapseWhitespace || whitespaceCanHang) { |
| 10066 | uint32_t trimStart = |
| 10067 | GetEndOfTrimmedText(characterDataBuffer, textStyle, wordStart, |
| 10068 | wordEnd, &iter, whitespaceCanHang); |
| 10069 | if (trimStart == start) { |
| 10070 | // This is *all* trimmable whitespace, so whatever trailingWhitespace |
| 10071 | // we saw previously is still trailing... |
| 10072 | aData->mTrailingWhitespace += width; |
| 10073 | } else { |
| 10074 | // Some non-whitespace so the old trailingWhitespace is no longer |
| 10075 | // trailing |
| 10076 | nscoord wsWidth = NSToCoordCeilClamped( |
| 10077 | textRun->GetAdvanceWidth(Range(trimStart, wordEnd), &provider)); |
| 10078 | aData->mTrailingWhitespace = std::max(0, wsWidth); |
| 10079 | } |
| 10080 | } else { |
| 10081 | aData->mTrailingWhitespace = 0; |
| 10082 | } |
| 10083 | } |
| 10084 | |
| 10085 | if (g < limit) { |
| 10086 | if (g->IsSimpleGlyph()) { |
| 10087 | wordAdvance = g->GetSimpleAdvance(); |
| 10088 | } else { |
| 10089 | wordAdvance = 0; |
| 10090 | if (!preformattedNewline && !preformattedTab && !hasSpacing) { |
| 10091 | if (uint32_t count = g->GetGlyphCount()) { |
| 10092 | const auto* details = textRun->GetDetailedGlyphs(g - glyphs, count); |
| 10093 | while (count--) { |
| 10094 | wordAdvance += details->mAdvance; |
| 10095 | ++details; |
| 10096 | } |
| 10097 | } |
| 10098 | } |
| 10099 | } |
| 10100 | } |
| 10101 | |
| 10102 | if (preformattedTab) { |
| 10103 | PropertyProvider::Spacing spacing; |
| 10104 | provider.GetSpacing(Range(wordEnd, wordEnd + 1), &spacing); |
| 10105 | aData->mCurrentLine += nscoord(spacing.mBefore); |
| 10106 | if (tabWidth < 0) { |
| 10107 | tabWidth = ComputeTabWidthAppUnits(this); |
| 10108 | } |
| 10109 | gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth, |
| 10110 | provider.MinTabAdvance()); |
| 10111 | aData->mCurrentLine = nscoord(afterTab + spacing.mAfter); |
| 10112 | wordStart = wordEnd + 1; |
| 10113 | } else if (wordEnd < flowEndInTextRun || |
| 10114 | (wordEnd == textRun->GetLength() && |
| 10115 | (textRun->GetFlags2() & |
| 10116 | nsTextFrameUtils::Flags::HasTrailingBreak))) { |
| 10117 | if (preformattedNewline) { |
| 10118 | aData->ForceBreak(); |
| 10119 | } else if (wordEnd < flowEndInTextRun && hyphenating && |
| 10120 | gfxTextRun::IsOptionalHyphenBreak( |
| 10121 | hyphBuffer[wordEnd - start])) { |
| 10122 | aData->OptionallyBreak(NSToCoordRound(provider.GetHyphenWidth())); |
| 10123 | } else { |
| 10124 | aData->OptionallyBreak(); |
| 10125 | } |
| 10126 | if (aData->mSkipWhitespace) { |
| 10127 | iter.SetSkippedOffset(wordEnd); |
| 10128 | wordStart = FindStartAfterSkippingWhitespace( |
| 10129 | &provider, aData, textStyle, &iter, flowEndInTextRun); |
| 10130 | } else { |
| 10131 | wordStart = wordEnd; |
| 10132 | } |
| 10133 | provider.SetStartOfLine(iter); |
| 10134 | } |
| 10135 | } |
| 10136 | |
| 10137 | if (start < flowEndInTextRun) { |
| 10138 | // Check if we have collapsible whitespace at the end |
| 10139 | aData->mSkipWhitespace = IsTrimmableSpace( |
| 10140 | provider.GetCharacterDataBuffer(), |
| 10141 | iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle); |
| 10142 | } |
| 10143 | } |
| 10144 | |
| 10145 | bool nsTextFrame::IsCurrentFontInflation(float aInflation) const { |
| 10146 | return fabsf(aInflation - GetFontSizeInflation()) < 1e-6; |
| 10147 | } |
| 10148 | |
| 10149 | void nsTextFrame::MaybeSplitFramesForFirstLetter() { |
| 10150 | if (!StaticPrefs::layout_css_intrinsic_size_first_letter_enabled()) { |
| 10151 | return; |
| 10152 | } |
| 10153 | |
| 10154 | if (GetParent()->IsFloating() && GetContentLength() > 0) { |
| 10155 | // We've already claimed our first-letter content, don't try again. |
| 10156 | return; |
| 10157 | } |
| 10158 | if (GetPrevContinuation()) { |
| 10159 | // This isn't the first part of the first-letter. |
| 10160 | return; |
| 10161 | } |
| 10162 | |
| 10163 | // Find the length of the first-letter. We need a textrun for this; just bail |
| 10164 | // out if we fail to create it. |
| 10165 | // But in the floating first-letter case, the text is initially all in our |
| 10166 | // next-in-flow, and the float itself is empty. So we need to look at that |
| 10167 | // textrun instead of our own during FindFirstLetterRange. |
| 10168 | nsTextFrame* f = GetParent()->IsFloating() ? GetNextInFlow() : this; |
| 10169 | gfxSkipCharsIterator iter = f->EnsureTextRun(nsTextFrame::eInflated); |
| 10170 | const gfxTextRun* textRun = f->GetTextRun(nsTextFrame::eInflated); |
| 10171 | |
| 10172 | const auto& characterDataBuffer = CharacterDataBuffer(); |
| 10173 | const int32_t length = GetInFlowContentLength(); |
| 10174 | const int32_t offset = GetContentOffset(); |
| 10175 | int32_t firstLetterLength = length; |
| 10176 | NewlineProperty* cachedNewlineOffset = nullptr; |
| 10177 | int32_t newLineOffset = -1; // this will be -1 or a content offset |
| 10178 | // This will just return -1 if newlines are not significant. |
| 10179 | int32_t contentNewLineOffset = |
| 10180 | GetContentNewLineOffset(offset, cachedNewlineOffset); |
| 10181 | if (contentNewLineOffset < offset + length) { |
| 10182 | // The new line offset could be outside this frame if the frame has been |
| 10183 | // split by bidi resolution. In that case we won't use it in this reflow |
| 10184 | // (newLineOffset will remain -1), but we will still cache it in mContent |
| 10185 | newLineOffset = contentNewLineOffset; |
| 10186 | if (newLineOffset >= 0) { |
| 10187 | firstLetterLength = newLineOffset - offset; |
| 10188 | } |
| 10189 | } |
| 10190 | |
| 10191 | if (contentNewLineOffset >= 0 && contentNewLineOffset < offset) { |
| 10192 | // We're in a first-letter frame's first in flow, so if there |
| 10193 | // was a first-letter, we'd be it. However, for one reason |
| 10194 | // or another (e.g., preformatted line break before this text), |
| 10195 | // we're not actually supposed to have first-letter style. So |
| 10196 | // just make a zero-length first-letter. |
| 10197 | firstLetterLength = 0; |
| 10198 | } else { |
| 10199 | // We only pass a language code to FindFirstLetterRange if it was |
| 10200 | // explicit in the content. |
| 10201 | const nsStyleFont* styleFont = StyleFont(); |
| 10202 | const nsAtom* lang = |
| 10203 | styleFont->mExplicitLanguage ? styleFont->GetLangAtom() : nullptr; |
| 10204 | FindFirstLetterRange(characterDataBuffer, lang, textRun, offset, iter, |
| 10205 | &firstLetterLength); |
| 10206 | if (newLineOffset >= 0) { |
| 10207 | // Don't allow a preformatted newline to be part of a first-letter. |
| 10208 | firstLetterLength = std::min(firstLetterLength, length - 1); |
| 10209 | } |
| 10210 | } |
| 10211 | if (firstLetterLength) { |
| 10212 | AddStateBits(TEXT_FIRST_LETTER); |
| 10213 | } |
| 10214 | |
| 10215 | // Change this frame's length to the first-letter length right now |
| 10216 | // so that when we rebuild the textrun it will be built with the |
| 10217 | // right first-letter boundary. |
| 10218 | SetFirstLetterLength(firstLetterLength); |
| 10219 | } |
| 10220 | |
| 10221 | static bool IsUnreflowedLetterFrame(nsIFrame* aFrame) { |
| 10222 | return aFrame->IsLetterFrame() && |
| 10223 | aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); |
| 10224 | } |
| 10225 | |
| 10226 | // XXX Need to do something here to avoid incremental reflow bugs due to |
| 10227 | // first-line changing min-width |
| 10228 | /* virtual */ |
| 10229 | void nsTextFrame::AddInlineMinISize(const IntrinsicSizeInput& aInput, |
| 10230 | InlineMinISizeData* aData) { |
| 10231 | // Check if this textframe belongs to a first-letter frame that has not yet |
| 10232 | // been reflowed; if so, we need to deal with splitting off a continuation |
| 10233 | // before we can measure the advance correctly. |
| 10234 | if (IsUnreflowedLetterFrame(GetParent())) { |
| 10235 | MaybeSplitFramesForFirstLetter(); |
| 10236 | } |
| 10237 | |
| 10238 | float inflation = nsLayoutUtils::FontSizeInflationFor(this); |
| 10239 | TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated; |
| 10240 | |
| 10241 | if (trtype == eInflated && !IsCurrentFontInflation(inflation)) { |
| 10242 | // FIXME: Ideally, if we already have a text run, we'd move it to be |
| 10243 | // the uninflated text run. |
| 10244 | ClearTextRun(nullptr, nsTextFrame::eInflated); |
| 10245 | mFontMetrics = nullptr; |
| 10246 | } |
| 10247 | |
| 10248 | nsTextFrame* f; |
| 10249 | const gfxTextRun* lastTextRun = nullptr; |
| 10250 | // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames |
| 10251 | // in the flow are handled right here. |
| 10252 | for (f = this; f; f = f->GetNextContinuation()) { |
| 10253 | // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we |
| 10254 | // haven't set up textruns yet for f. Except in OOM situations, |
| 10255 | // lastTextRun will only be null for the first text frame. |
| 10256 | if (f == this || f->GetTextRun(trtype) != lastTextRun) { |
| 10257 | nsIFrame* lc; |
| 10258 | if (aData->LineContainer() && |
| 10259 | aData->LineContainer() != (lc = f->FindLineContainer())) { |
| 10260 | NS_ASSERTION(f != this,do { if (!(f != this)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "wrong InlineMinISizeData container" " for first continuation", "f != this", "./../../../layout/generic/nsTextFrame.cpp" , 10262); MOZ_PretendNoReturn(); } } while (0) |
| 10261 | "wrong InlineMinISizeData container"do { if (!(f != this)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "wrong InlineMinISizeData container" " for first continuation", "f != this", "./../../../layout/generic/nsTextFrame.cpp" , 10262); MOZ_PretendNoReturn(); } } while (0) |
| 10262 | " for first continuation")do { if (!(f != this)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "wrong InlineMinISizeData container" " for first continuation", "f != this", "./../../../layout/generic/nsTextFrame.cpp" , 10262); MOZ_PretendNoReturn(); } } while (0); |
| 10263 | aData->mLine = nullptr; |
| 10264 | aData->SetLineContainer(lc); |
| 10265 | } |
| 10266 | |
| 10267 | // This will process all the text frames that share the same textrun as f. |
| 10268 | f->AddInlineMinISizeForFlow(aInput.mContext, aData, trtype); |
| 10269 | lastTextRun = f->GetTextRun(trtype); |
| 10270 | } |
| 10271 | } |
| 10272 | } |
| 10273 | |
| 10274 | // XXX this doesn't handle characters shaped by line endings. We need to |
| 10275 | // temporarily override the "current line ending" settings. |
| 10276 | void nsTextFrame::AddInlinePrefISizeForFlow(gfxContext* aRenderingContext, |
| 10277 | InlinePrefISizeData* aData, |
| 10278 | TextRunType aTextRunType) { |
| 10279 | if (IsUnreflowedLetterFrame(GetParent())) { |
| 10280 | MaybeSplitFramesForFirstLetter(); |
| 10281 | } |
| 10282 | |
| 10283 | uint32_t flowEndInTextRun; |
| 10284 | gfxSkipCharsIterator iter = |
| 10285 | EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(), |
| 10286 | aData->LineContainer(), aData->mLine, &flowEndInTextRun); |
| 10287 | gfxTextRun* textRun = GetTextRun(aTextRunType); |
| 10288 | if (!textRun) { |
| 10289 | return; |
| 10290 | } |
| 10291 | |
| 10292 | // Pass null for the line container. This will disable tab spacing, but that's |
| 10293 | // OK since we can't really handle tabs for intrinsic sizing anyway. |
| 10294 | |
| 10295 | const nsStyleText* textStyle = StyleText(); |
| 10296 | const auto& characterDataBuffer = CharacterDataBuffer(); |
| 10297 | PropertyProvider provider(textRun, textStyle, characterDataBuffer, this, iter, |
| 10298 | INT32_MAX(2147483647), nullptr, 0, aTextRunType, |
| 10299 | aData->mLineIsEmpty); |
| 10300 | |
| 10301 | // text-combine-upright frame is constantly 1em on inline-axis, or zero if |
| 10302 | // there is a following sibling with the same style. |
| 10303 | if (Style()->IsTextCombined()) { |
| 10304 | if (!GetNextSibling() || GetNextSibling()->Style() != Style()) { |
| 10305 | aData->mCurrentLine += provider.GetFontMetrics()->EmHeight(); |
| 10306 | } |
| 10307 | aData->mTrailingWhitespace = 0; |
| 10308 | aData->mLineIsEmpty = false; |
| 10309 | return; |
| 10310 | } |
| 10311 | |
| 10312 | bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant(); |
| 10313 | bool preformatNewlines = textStyle->NewlineIsSignificant(this); |
| 10314 | bool preformatTabs = textStyle->TabIsSignificant(); |
| 10315 | gfxFloat tabWidth = -1; |
| 10316 | uint32_t start = FindStartAfterSkippingWhitespace(&provider, aData, textStyle, |
| 10317 | &iter, flowEndInTextRun); |
| 10318 | if (aData->mLineIsEmpty) { |
| 10319 | provider.SetStartOfLine(iter); |
| 10320 | } |
| 10321 | |
| 10322 | // XXX Should we consider hyphenation here? |
| 10323 | // If newlines and tabs aren't preformatted, nothing to do inside |
| 10324 | // the loop so skip directly to the end |
| 10325 | const uint32_t loopStart = |
| 10326 | (preformatNewlines || preformatTabs) ? start : flowEndInTextRun; |
| 10327 | const auto* glyphs = textRun->GetCharacterGlyphs(); |
| 10328 | const auto* limit = glyphs + flowEndInTextRun; |
| 10329 | const bool canUseSimpleAdvance = loopStart == start && !provider.HasSpacing(); |
| 10330 | uint32_t lineStart = start; |
| 10331 | nscoord runAdvance = 0; |
| 10332 | for (const auto* g = glyphs + loopStart; g <= limit; ++g) { |
| 10333 | // Simple glyphs are never tabs or newlines, so batch-skip them to avoid |
| 10334 | // per-character CharIsTab/CharIsNewline calls. |
| 10335 | while (g < limit && g->IsSimpleGlyph()) { |
| 10336 | runAdvance += g->GetSimpleAdvance(); |
| 10337 | ++g; |
| 10338 | } |
| 10339 | |
| 10340 | // Now /g/ points to a non-simple glyph, or has reached /limit/. |
| 10341 | bool preformattedNewline = false; |
| 10342 | bool preformattedTab = false; |
| 10343 | if (g < limit) { |
| 10344 | // XXXldb Shouldn't we be including the newline as part of the |
| 10345 | // segment that it ends rather than part of the segment that it |
| 10346 | // starts? |
| 10347 | MOZ_ASSERT(preformatNewlines || preformatTabs,do { static_assert( mozilla::detail::AssertionConditionType< decltype(preformatNewlines || preformatTabs)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(preformatNewlines || preformatTabs ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "preformatNewlines || preformatTabs" " (" "We can't be here unless newlines are " "hard breaks or there are tabs" ")", "./../../../layout/generic/nsTextFrame.cpp" , 10349); AnnotateMozCrashReason("MOZ_ASSERT" "(" "preformatNewlines || preformatTabs" ") (" "We can't be here unless newlines are " "hard breaks or there are tabs" ")"); do { MOZ_CrashSequence(__null, 10349); __attribute__(( nomerge)) ::abort(); } while (false); } } while (false) |
| 10348 | "We can't be here unless newlines are "do { static_assert( mozilla::detail::AssertionConditionType< decltype(preformatNewlines || preformatTabs)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(preformatNewlines || preformatTabs ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "preformatNewlines || preformatTabs" " (" "We can't be here unless newlines are " "hard breaks or there are tabs" ")", "./../../../layout/generic/nsTextFrame.cpp" , 10349); AnnotateMozCrashReason("MOZ_ASSERT" "(" "preformatNewlines || preformatTabs" ") (" "We can't be here unless newlines are " "hard breaks or there are tabs" ")"); do { MOZ_CrashSequence(__null, 10349); __attribute__(( nomerge)) ::abort(); } while (false); } } while (false) |
| 10349 | "hard breaks or there are tabs")do { static_assert( mozilla::detail::AssertionConditionType< decltype(preformatNewlines || preformatTabs)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(preformatNewlines || preformatTabs ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "preformatNewlines || preformatTabs" " (" "We can't be here unless newlines are " "hard breaks or there are tabs" ")", "./../../../layout/generic/nsTextFrame.cpp" , 10349); AnnotateMozCrashReason("MOZ_ASSERT" "(" "preformatNewlines || preformatTabs" ") (" "We can't be here unless newlines are " "hard breaks or there are tabs" ")"); do { MOZ_CrashSequence(__null, 10349); __attribute__(( nomerge)) ::abort(); } while (false); } } while (false); |
| 10350 | MOZ_ASSERT(!g->IsSimpleGlyph(), "should have been skipped")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!g->IsSimpleGlyph())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!g->IsSimpleGlyph()))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("!g->IsSimpleGlyph()" " (" "should have been skipped" ")", "./../../../layout/generic/nsTextFrame.cpp" , 10350); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!g->IsSimpleGlyph()" ") (" "should have been skipped" ")"); do { MOZ_CrashSequence (__null, 10350); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 10351 | preformattedNewline = preformatNewlines && g->CharIsNewline(); |
| 10352 | preformattedTab = preformatTabs && g->CharIsTab(); |
| 10353 | // Skip accumulating advance if there's custom spacing in the |
| 10354 | // PropertyProvider; we'll call textRun->GetAdvanceWidth instead. |
| 10355 | if (canUseSimpleAdvance) { |
| 10356 | if (uint32_t count = g->GetGlyphCount()) { |
| 10357 | // We don't check g->ApplyLetterSpacingBetweenDetailedGlyphs() here |
| 10358 | // because canUseSimpleAdvance depends on there being no spacing to |
| 10359 | // consider. |
| 10360 | const auto* details = textRun->GetDetailedGlyphs(g - glyphs, count); |
| 10361 | while (count--) { |
| 10362 | runAdvance += details->mAdvance; |
| 10363 | ++details; |
| 10364 | } |
| 10365 | } |
| 10366 | } |
| 10367 | if (!preformattedNewline && !preformattedTab) { |
| 10368 | // we needn't break here (and it's not the end of the flow) |
| 10369 | continue; |
| 10370 | } |
| 10371 | } |
| 10372 | |
| 10373 | uint32_t lineEnd = g - glyphs; |
| 10374 | if (lineEnd > lineStart) { |
| 10375 | // If there's no custom spacing, and we didn't immediately skip to |
| 10376 | // the end of the loop, and neither end of the range falls within a |
| 10377 | // ligature, we can use the advance that was accumulated above. |
| 10378 | nscoord width = canUseSimpleAdvance && |
| 10379 | (glyphs + lineStart)->IsLigatureGroupStart() && |
| 10380 | (lineEnd == flowEndInTextRun || |
| 10381 | (glyphs + lineEnd)->IsLigatureGroupStart()) |
| 10382 | ? runAdvance |
| 10383 | : NSToCoordCeilClamped(textRun->GetAdvanceWidth( |
| 10384 | Range(lineStart, lineEnd), &provider)); |
| 10385 | width = std::max(0, width); |
| 10386 | aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width); |
| 10387 | aData->mLineIsEmpty = false; |
| 10388 | |
| 10389 | if (collapseWhitespace) { |
| 10390 | uint32_t trimStart = GetEndOfTrimmedText(characterDataBuffer, textStyle, |
| 10391 | lineStart, lineEnd, &iter); |
| 10392 | if (trimStart == start) { |
| 10393 | // This is *all* trimmable whitespace, so whatever trailingWhitespace |
| 10394 | // we saw previously is still trailing... |
| 10395 | aData->mTrailingWhitespace += width; |
| 10396 | } else { |
| 10397 | // Some non-whitespace so the old trailingWhitespace is no longer |
| 10398 | // trailing |
| 10399 | nscoord wsWidth = NSToCoordCeilClamped( |
| 10400 | textRun->GetAdvanceWidth(Range(trimStart, lineEnd), &provider)); |
| 10401 | aData->mTrailingWhitespace = std::max(0, wsWidth); |
| 10402 | } |
| 10403 | } else { |
| 10404 | aData->mTrailingWhitespace = 0; |
| 10405 | } |
| 10406 | } |
| 10407 | runAdvance = 0; |
| 10408 | |
| 10409 | if (preformattedTab) { |
| 10410 | PropertyProvider::Spacing spacing; |
| 10411 | provider.GetSpacing(Range(lineEnd, lineEnd + 1), &spacing); |
| 10412 | aData->mCurrentLine += nscoord(spacing.mBefore); |
| 10413 | if (tabWidth < 0) { |
| 10414 | tabWidth = ComputeTabWidthAppUnits(this); |
| 10415 | } |
| 10416 | gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth, |
| 10417 | provider.MinTabAdvance()); |
| 10418 | aData->mCurrentLine = nscoord(afterTab + spacing.mAfter); |
| 10419 | aData->mLineIsEmpty = false; |
| 10420 | lineStart = lineEnd + 1; |
| 10421 | } else if (preformattedNewline) { |
| 10422 | aData->ForceBreak(); |
| 10423 | lineStart = lineEnd; |
| 10424 | } |
| 10425 | } |
| 10426 | |
| 10427 | // Check if we have collapsible whitespace at the end |
| 10428 | if (start < flowEndInTextRun) { |
| 10429 | aData->mSkipWhitespace = IsTrimmableSpace( |
| 10430 | provider.GetCharacterDataBuffer(), |
| 10431 | iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle); |
| 10432 | } |
| 10433 | } |
| 10434 | |
| 10435 | // XXX Need to do something here to avoid incremental reflow bugs due to |
| 10436 | // first-line and first-letter changing pref-width |
| 10437 | /* virtual */ |
| 10438 | void nsTextFrame::AddInlinePrefISize(const IntrinsicSizeInput& aInput, |
| 10439 | InlinePrefISizeData* aData) { |
| 10440 | float inflation = nsLayoutUtils::FontSizeInflationFor(this); |
| 10441 | TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated; |
| 10442 | |
| 10443 | if (trtype == eInflated && !IsCurrentFontInflation(inflation)) { |
| 10444 | // FIXME: Ideally, if we already have a text run, we'd move it to be |
| 10445 | // the uninflated text run. |
| 10446 | ClearTextRun(nullptr, nsTextFrame::eInflated); |
| 10447 | mFontMetrics = nullptr; |
| 10448 | } |
| 10449 | |
| 10450 | nsTextFrame* f; |
| 10451 | const gfxTextRun* lastTextRun = nullptr; |
| 10452 | // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames |
| 10453 | // in the flow are handled right here. |
| 10454 | for (f = this; f; f = f->GetNextContinuation()) { |
| 10455 | // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we |
| 10456 | // haven't set up textruns yet for f. Except in OOM situations, |
| 10457 | // lastTextRun will only be null for the first text frame. |
| 10458 | if (f == this || f->GetTextRun(trtype) != lastTextRun) { |
| 10459 | nsIFrame* lc; |
| 10460 | if (aData->LineContainer() && |
| 10461 | aData->LineContainer() != (lc = f->FindLineContainer())) { |
| 10462 | NS_ASSERTION(f != this,do { if (!(f != this)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "wrong InlinePrefISizeData container" " for first continuation", "f != this", "./../../../layout/generic/nsTextFrame.cpp" , 10464); MOZ_PretendNoReturn(); } } while (0) |
| 10463 | "wrong InlinePrefISizeData container"do { if (!(f != this)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "wrong InlinePrefISizeData container" " for first continuation", "f != this", "./../../../layout/generic/nsTextFrame.cpp" , 10464); MOZ_PretendNoReturn(); } } while (0) |
| 10464 | " for first continuation")do { if (!(f != this)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "wrong InlinePrefISizeData container" " for first continuation", "f != this", "./../../../layout/generic/nsTextFrame.cpp" , 10464); MOZ_PretendNoReturn(); } } while (0); |
| 10465 | aData->mLine = nullptr; |
| 10466 | aData->SetLineContainer(lc); |
| 10467 | } |
| 10468 | |
| 10469 | // This will process all the text frames that share the same textrun as f. |
| 10470 | f->AddInlinePrefISizeForFlow(aInput.mContext, aData, trtype); |
| 10471 | lastTextRun = f->GetTextRun(trtype); |
| 10472 | } |
| 10473 | } |
| 10474 | } |
| 10475 | |
| 10476 | /* virtual */ |
| 10477 | nsIFrame::SizeComputationResult nsTextFrame::ComputeSize( |
| 10478 | const SizeComputationInput& aSizingInput, WritingMode aWM, |
| 10479 | const LogicalSize& aCBSize, nscoord aAvailableISize, |
| 10480 | const LogicalSize& aMargin, const LogicalSize& aBorderPadding, |
| 10481 | const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { |
| 10482 | // Inlines and text don't compute size before reflow. |
| 10483 | return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE), |
| 10484 | AspectRatioUsage::None}; |
| 10485 | } |
| 10486 | |
| 10487 | static nsRect RoundOut(const gfxRect& aRect) { |
| 10488 | nsRect r; |
| 10489 | r.x = NSToCoordFloor(aRect.X()); |
| 10490 | r.y = NSToCoordFloor(aRect.Y()); |
| 10491 | r.width = NSToCoordCeil(aRect.XMost()) - r.x; |
| 10492 | r.height = NSToCoordCeil(aRect.YMost()) - r.y; |
| 10493 | return r; |
| 10494 | } |
| 10495 | |
| 10496 | nsRect nsTextFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const { |
| 10497 | if (Style()->HasTextDecorationLines() || HasAnyStateBits(TEXT_HYPHEN_BREAK)) { |
| 10498 | // This is conservative, but OK. |
| 10499 | return InkOverflowRect(); |
| 10500 | } |
| 10501 | |
| 10502 | gfxSkipCharsIterator iter = |
| 10503 | const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated); |
| 10504 | if (!mTextRun) { |
| 10505 | return nsRect(); |
| 10506 | } |
| 10507 | |
| 10508 | PropertyProvider provider(const_cast<nsTextFrame*>(this), iter, |
| 10509 | nsTextFrame::eInflated, mFontMetrics); |
| 10510 | // Trim trailing whitespace |
| 10511 | provider.InitializeForDisplay(true); |
| 10512 | |
| 10513 | gfxTextRun::Metrics metrics = mTextRun->MeasureText( |
| 10514 | ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, |
| 10515 | aDrawTarget, &provider); |
| 10516 | if (GetWritingMode().IsLineInverted()) { |
| 10517 | metrics.mBoundingBox.y = -metrics.mBoundingBox.YMost(); |
| 10518 | } |
| 10519 | // mAscent should be the same as metrics.mAscent, but it's what we use to |
| 10520 | // paint so that's the one we'll use. |
| 10521 | nsRect boundingBox = RoundOut(metrics.mBoundingBox); |
| 10522 | boundingBox += nsPoint(0, mAscent); |
| 10523 | if (mTextRun->IsVertical()) { |
| 10524 | // Swap line-relative textMetrics dimensions to physical coordinates. |
| 10525 | std::swap(boundingBox.x, boundingBox.y); |
| 10526 | std::swap(boundingBox.width, boundingBox.height); |
| 10527 | } |
| 10528 | return boundingBox; |
| 10529 | } |
| 10530 | |
| 10531 | /* virtual */ |
| 10532 | nsresult nsTextFrame::GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX, |
| 10533 | nscoord* aXMost) { |
| 10534 | gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); |
| 10535 | if (!mTextRun) { |
| 10536 | return NS_ERROR_FAILURE; |
| 10537 | } |
| 10538 | |
| 10539 | PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics); |
| 10540 | provider.InitializeForMeasure(); |
| 10541 | |
| 10542 | gfxTextRun::Metrics metrics = mTextRun->MeasureText( |
| 10543 | ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, |
| 10544 | aContext->GetDrawTarget(), &provider); |
| 10545 | // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency. |
| 10546 | *aX = NSToCoordFloor(metrics.mBoundingBox.x); |
| 10547 | *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost()); |
| 10548 | |
| 10549 | return NS_OK; |
| 10550 | } |
| 10551 | |
| 10552 | static bool HasSoftHyphenBefore(const CharacterDataBuffer& aBuffer, |
| 10553 | const gfxTextRun* aTextRun, |
| 10554 | int32_t aStartOffset, |
| 10555 | const gfxSkipCharsIterator& aIter) { |
| 10556 | if (aIter.GetSkippedOffset() < aTextRun->GetLength() && |
| 10557 | aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) { |
| 10558 | return true; |
| 10559 | } |
| 10560 | if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasShy)) { |
| 10561 | return false; |
| 10562 | } |
| 10563 | gfxSkipCharsIterator iter = aIter; |
| 10564 | while (iter.GetOriginalOffset() > aStartOffset) { |
| 10565 | iter.AdvanceOriginal(-1); |
| 10566 | if (!iter.IsOriginalCharSkipped()) { |
| 10567 | break; |
| 10568 | } |
| 10569 | if (aBuffer.CharAt(AssertedCast<uint32_t>(iter.GetOriginalOffset())) == |
| 10570 | CH_SHY173) { |
| 10571 | return true; |
| 10572 | } |
| 10573 | } |
| 10574 | return false; |
| 10575 | } |
| 10576 | |
| 10577 | /** |
| 10578 | * Removes all frames from aFrame up to (but not including) aFirstToNotRemove, |
| 10579 | * because their text has all been taken and reflowed by earlier frames. |
| 10580 | */ |
| 10581 | static void RemoveEmptyInFlows(nsTextFrame* aFrame, |
| 10582 | nsTextFrame* aFirstToNotRemove) { |
| 10583 | MOZ_ASSERT(aFrame != aFirstToNotRemove, "This will go very badly")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aFrame != aFirstToNotRemove)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aFrame != aFirstToNotRemove) )), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aFrame != aFirstToNotRemove" " (" "This will go very badly" ")", "./../../../layout/generic/nsTextFrame.cpp" , 10583); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aFrame != aFirstToNotRemove" ") (" "This will go very badly" ")"); do { MOZ_CrashSequence (__null, 10583); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 10584 | // We have to be careful here, because some RemoveFrame implementations |
| 10585 | // remove and destroy not only the passed-in frame but also all its following |
| 10586 | // in-flows (and sometimes all its following continuations in general). So |
| 10587 | // we remove |f| and everything up to but not including firstToNotRemove from |
| 10588 | // the flow first, to make sure that only the things we want destroyed are |
| 10589 | // destroyed. |
| 10590 | |
| 10591 | // This sadly duplicates some of the logic from |
| 10592 | // nsSplittableFrame::RemoveFromFlow. We can get away with not duplicating |
| 10593 | // all of it, because we know that the prev-continuation links of |
| 10594 | // firstToNotRemove and f are fluid, and non-null. |
| 10595 | NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() ==do { if (!(aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove ->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow () != nullptr)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aFirstToNotRemove should have a fluid prev continuation" , "aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow() != nullptr" , "./../../../layout/generic/nsTextFrame.cpp", 10598); MOZ_PretendNoReturn (); } } while (0) |
| 10596 | aFirstToNotRemove->GetPrevInFlow() &&do { if (!(aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove ->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow () != nullptr)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aFirstToNotRemove should have a fluid prev continuation" , "aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow() != nullptr" , "./../../../layout/generic/nsTextFrame.cpp", 10598); MOZ_PretendNoReturn (); } } while (0) |
| 10597 | aFirstToNotRemove->GetPrevInFlow() != nullptr,do { if (!(aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove ->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow () != nullptr)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aFirstToNotRemove should have a fluid prev continuation" , "aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow() != nullptr" , "./../../../layout/generic/nsTextFrame.cpp", 10598); MOZ_PretendNoReturn (); } } while (0) |
| 10598 | "aFirstToNotRemove should have a fluid prev continuation")do { if (!(aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove ->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow () != nullptr)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "aFirstToNotRemove should have a fluid prev continuation" , "aFirstToNotRemove->GetPrevContinuation() == aFirstToNotRemove->GetPrevInFlow() && aFirstToNotRemove->GetPrevInFlow() != nullptr" , "./../../../layout/generic/nsTextFrame.cpp", 10598); MOZ_PretendNoReturn (); } } while (0); |
| 10599 | NS_ASSERTION(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() &&do { if (!(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow () && aFrame->GetPrevInFlow() != nullptr)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "aFrame should have a fluid prev continuation" , "aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() && aFrame->GetPrevInFlow() != nullptr" , "./../../../layout/generic/nsTextFrame.cpp", 10601); MOZ_PretendNoReturn (); } } while (0) |
| 10600 | aFrame->GetPrevInFlow() != nullptr,do { if (!(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow () && aFrame->GetPrevInFlow() != nullptr)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "aFrame should have a fluid prev continuation" , "aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() && aFrame->GetPrevInFlow() != nullptr" , "./../../../layout/generic/nsTextFrame.cpp", 10601); MOZ_PretendNoReturn (); } } while (0) |
| 10601 | "aFrame should have a fluid prev continuation")do { if (!(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow () && aFrame->GetPrevInFlow() != nullptr)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "aFrame should have a fluid prev continuation" , "aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() && aFrame->GetPrevInFlow() != nullptr" , "./../../../layout/generic/nsTextFrame.cpp", 10601); MOZ_PretendNoReturn (); } } while (0); |
| 10602 | |
| 10603 | nsTextFrame* prevContinuation = aFrame->GetPrevContinuation(); |
| 10604 | nsTextFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation(); |
| 10605 | |
| 10606 | for (nsTextFrame* f = aFrame; f != aFirstToNotRemove; |
| 10607 | f = f->GetNextContinuation()) { |
| 10608 | // f is going to be destroyed soon, after it is unlinked from the |
| 10609 | // continuation chain. If its textrun is going to be destroyed we need to |
| 10610 | // do it now, before we unlink the frames to remove from the flow, |
| 10611 | // because Destroy calls ClearTextRuns() and that will start at the |
| 10612 | // first frame with the text run and walk the continuations. |
| 10613 | if (f->IsInTextRunUserData()) { |
| 10614 | f->ClearTextRuns(); |
| 10615 | } else { |
| 10616 | f->DisconnectTextRuns(); |
| 10617 | } |
| 10618 | } |
| 10619 | |
| 10620 | prevContinuation->SetNextInFlow(aFirstToNotRemove); |
| 10621 | aFirstToNotRemove->SetPrevInFlow(prevContinuation); |
| 10622 | |
| 10623 | // **Note: it is important here that we clear the Next link from lastRemoved |
| 10624 | // BEFORE clearing the Prev link from aFrame, because SetPrevInFlow() will |
| 10625 | // follow the Next pointers, wiping out the cached mFirstContinuation field |
| 10626 | // from each following frame in the list. We need this to stop when it |
| 10627 | // reaches lastRemoved! |
| 10628 | lastRemoved->SetNextInFlow(nullptr); |
| 10629 | aFrame->SetPrevInFlow(nullptr); |
| 10630 | |
| 10631 | nsContainerFrame* parent = aFrame->GetParent(); |
| 10632 | nsIFrame::DestroyContext context(aFrame->PresShell()); |
| 10633 | nsBlockFrame* parentBlock = do_QueryFrame(parent); |
| 10634 | if (parentBlock) { |
| 10635 | // Manually call DoRemoveFrame so we can tell it that we're |
| 10636 | // removing empty frames; this will keep it from blowing away |
| 10637 | // text runs. |
| 10638 | parentBlock->DoRemoveFrame(context, aFrame, nsBlockFrame::FRAMES_ARE_EMPTY); |
| 10639 | } else { |
| 10640 | // Just remove it normally; use FrameChildListID::NoReflowPrincipal to avoid |
| 10641 | // posting new reflows. |
| 10642 | parent->RemoveFrame(context, FrameChildListID::NoReflowPrincipal, aFrame); |
| 10643 | } |
| 10644 | } |
| 10645 | |
| 10646 | void nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout, |
| 10647 | uint32_t aSetLengthFlags) { |
| 10648 | mContentLengthHint = aLength; |
| 10649 | int32_t end = GetContentOffset() + aLength; |
| 10650 | nsTextFrame* f = GetNextInFlow(); |
| 10651 | if (!f) { |
| 10652 | return; |
| 10653 | } |
| 10654 | |
| 10655 | // If our end offset is moving, then even if frames are not being pushed or |
| 10656 | // pulled, content is moving to or from the next line and the next line |
| 10657 | // must be reflowed. |
| 10658 | // If the next-continuation is dirty, then we should dirty the next line now |
| 10659 | // because we may have skipped doing it if we dirtied it in |
| 10660 | // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow |
| 10661 | // and ChildIsDirty to handle a range of frames would be worse. |
| 10662 | if (aLineLayout && |
| 10663 | (end != f->mContentOffset || f->HasAnyStateBits(NS_FRAME_IS_DIRTY))) { |
| 10664 | aLineLayout->SetDirtyNextLine(); |
| 10665 | } |
| 10666 | |
| 10667 | if (end < f->mContentOffset) { |
| 10668 | // Our frame is shrinking. Give the text to our next in flow. |
| 10669 | if (aLineLayout && HasSignificantTerminalNewline() && |
| 10670 | !GetParent()->IsLetterFrame() && |
| 10671 | (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) { |
| 10672 | // Whatever text we hand to our next-in-flow will end up in a frame all of |
| 10673 | // its own, since it ends in a forced linebreak. Might as well just put |
| 10674 | // it in a separate frame now. This is important to prevent text run |
| 10675 | // churn; if we did not do that, then we'd likely end up rebuilding |
| 10676 | // textruns for all our following continuations. |
| 10677 | // We skip this optimization when the parent is a first-letter frame |
| 10678 | // because it doesn't deal well with more than one child frame. |
| 10679 | // We also skip this optimization if we were called during bidi |
| 10680 | // resolution, so as not to create a new frame which doesn't appear in |
| 10681 | // the bidi resolver's list of frames |
| 10682 | nsIFrame* newFrame = |
| 10683 | PresShell()->FrameConstructor()->CreateContinuingFrame(this, |
| 10684 | GetParent()); |
| 10685 | nsTextFrame* next = static_cast<nsTextFrame*>(newFrame); |
| 10686 | GetParent()->InsertFrames(FrameChildListID::NoReflowPrincipal, this, |
| 10687 | aLineLayout->GetLine(), |
| 10688 | nsFrameList(next, next)); |
| 10689 | f = next; |
| 10690 | } |
| 10691 | |
| 10692 | f->mContentOffset = end; |
| 10693 | if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) { |
| 10694 | ClearTextRuns(); |
| 10695 | f->ClearTextRuns(); |
| 10696 | } |
| 10697 | return; |
| 10698 | } |
| 10699 | // Our frame is growing. Take text from our in-flow(s). |
| 10700 | // We can take text from frames in lines beyond just the next line. |
| 10701 | // We don't dirty those lines. That's OK, because when we reflow |
| 10702 | // our empty next-in-flow, it will take text from its next-in-flow and |
| 10703 | // dirty that line. |
| 10704 | |
| 10705 | // Note that in the process we may end up removing some frames from |
| 10706 | // the flow if they end up empty. |
| 10707 | nsTextFrame* framesToRemove = nullptr; |
| 10708 | while (f && f->mContentOffset < end) { |
| 10709 | f->mContentOffset = end; |
| 10710 | if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) { |
| 10711 | ClearTextRuns(); |
| 10712 | f->ClearTextRuns(); |
| 10713 | } |
| 10714 | nsTextFrame* next = f->GetNextInFlow(); |
| 10715 | // Note: the "f->GetNextSibling() == next" check below is to restrict |
| 10716 | // this optimization to the case where they are on the same child list. |
| 10717 | // Otherwise we might remove the only child of a nsFirstLetterFrame |
| 10718 | // for example and it can't handle that. See bug 597627 for details. |
| 10719 | if (next && next->mContentOffset <= end && f->GetNextSibling() == next && |
| 10720 | (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) { |
| 10721 | // |f| is now empty. We may as well remove it, instead of copying all |
| 10722 | // the text from |next| into it instead; the latter leads to use |
| 10723 | // rebuilding textruns for all following continuations. |
| 10724 | // We skip this optimization if we were called during bidi resolution, |
| 10725 | // since the bidi resolver may try to handle the destroyed frame later |
| 10726 | // and crash |
| 10727 | if (!framesToRemove) { |
| 10728 | // Remember that we have to remove this frame. |
| 10729 | framesToRemove = f; |
| 10730 | } |
| 10731 | } else if (framesToRemove) { |
| 10732 | RemoveEmptyInFlows(framesToRemove, f); |
| 10733 | framesToRemove = nullptr; |
| 10734 | } |
| 10735 | f = next; |
| 10736 | } |
| 10737 | |
| 10738 | MOZ_ASSERT(!framesToRemove || (f && f->mContentOffset == end),do { static_assert( mozilla::detail::AssertionConditionType< decltype(!framesToRemove || (f && f->mContentOffset == end))>::isValid, "invalid assertion condition"); if (( __builtin_expect(!!(!(!!(!framesToRemove || (f && f-> mContentOffset == end)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("!framesToRemove || (f && f->mContentOffset == end)" " (" "How did we exit the loop if we null out framesToRemove if " "!next || next->mContentOffset > end ?" ")", "./../../../layout/generic/nsTextFrame.cpp" , 10740); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!framesToRemove || (f && f->mContentOffset == end)" ") (" "How did we exit the loop if we null out framesToRemove if " "!next || next->mContentOffset > end ?" ")"); do { MOZ_CrashSequence (__null, 10740); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 10739 | "How did we exit the loop if we null out framesToRemove if "do { static_assert( mozilla::detail::AssertionConditionType< decltype(!framesToRemove || (f && f->mContentOffset == end))>::isValid, "invalid assertion condition"); if (( __builtin_expect(!!(!(!!(!framesToRemove || (f && f-> mContentOffset == end)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("!framesToRemove || (f && f->mContentOffset == end)" " (" "How did we exit the loop if we null out framesToRemove if " "!next || next->mContentOffset > end ?" ")", "./../../../layout/generic/nsTextFrame.cpp" , 10740); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!framesToRemove || (f && f->mContentOffset == end)" ") (" "How did we exit the loop if we null out framesToRemove if " "!next || next->mContentOffset > end ?" ")"); do { MOZ_CrashSequence (__null, 10740); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 10740 | "!next || next->mContentOffset > end ?")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!framesToRemove || (f && f->mContentOffset == end))>::isValid, "invalid assertion condition"); if (( __builtin_expect(!!(!(!!(!framesToRemove || (f && f-> mContentOffset == end)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("!framesToRemove || (f && f->mContentOffset == end)" " (" "How did we exit the loop if we null out framesToRemove if " "!next || next->mContentOffset > end ?" ")", "./../../../layout/generic/nsTextFrame.cpp" , 10740); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!framesToRemove || (f && f->mContentOffset == end)" ") (" "How did we exit the loop if we null out framesToRemove if " "!next || next->mContentOffset > end ?" ")"); do { MOZ_CrashSequence (__null, 10740); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 10741 | |
| 10742 | if (framesToRemove) { |
| 10743 | // We are guaranteed that we exited the loop with f not null, per the |
| 10744 | // postcondition above |
| 10745 | RemoveEmptyInFlows(framesToRemove, f); |
| 10746 | } |
| 10747 | |
| 10748 | #ifdef DEBUG1 |
| 10749 | f = this; |
| 10750 | int32_t iterations = 0; |
| 10751 | while (f && iterations < 10) { |
| 10752 | f->GetContentLength(); // Assert if negative length |
| 10753 | f = f->GetNextContinuation(); |
| 10754 | ++iterations; |
| 10755 | } |
| 10756 | f = this; |
| 10757 | iterations = 0; |
| 10758 | while (f && iterations < 10) { |
| 10759 | f->GetContentLength(); // Assert if negative length |
| 10760 | f = f->GetPrevContinuation(); |
| 10761 | ++iterations; |
| 10762 | } |
| 10763 | #endif |
| 10764 | } |
| 10765 | |
| 10766 | void nsTextFrame::SetFirstLetterLength(int32_t aLength) { |
| 10767 | if (aLength == GetContentLength()) { |
| 10768 | return; |
| 10769 | } |
| 10770 | |
| 10771 | mContentLengthHint = aLength; |
| 10772 | nsTextFrame* next = static_cast<nsTextFrame*>(GetNextInFlow()); |
| 10773 | if (!aLength && !next) { |
| 10774 | return; |
| 10775 | } |
| 10776 | |
| 10777 | if (aLength > GetContentLength()) { |
| 10778 | // Stealing some text from our next-in-flow; this happens with floating |
| 10779 | // first-letter, which is initially given a zero-length range, with all |
| 10780 | // the text being in its continuation. |
| 10781 | if (!next) { |
| 10782 | MOZ_ASSERT_UNREACHABLE("Expected a next-in-flow; first-letter broken?")do { static_assert( mozilla::detail::AssertionConditionType< decltype(false)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: " "Expected a next-in-flow; first-letter broken?" ")", "./../../../layout/generic/nsTextFrame.cpp" , 10782); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "Expected a next-in-flow; first-letter broken?" ")"); do { MOZ_CrashSequence(__null, 10782); __attribute__(( nomerge)) ::abort(); } while (false); } } while (false); |
| 10783 | return; |
| 10784 | } |
| 10785 | } else if (!next) { |
| 10786 | // We need to create a continuation for the parent first-letter frame, |
| 10787 | // and move any kids after this frame to the new one; if there are none, |
| 10788 | // a new continuing text frame will be created there. |
| 10789 | MOZ_ASSERT(GetParent()->IsLetterFrame())do { static_assert( mozilla::detail::AssertionConditionType< decltype(GetParent()->IsLetterFrame())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(GetParent()->IsLetterFrame ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("GetParent()->IsLetterFrame()", "./../../../layout/generic/nsTextFrame.cpp" , 10789); AnnotateMozCrashReason("MOZ_ASSERT" "(" "GetParent()->IsLetterFrame()" ")"); do { MOZ_CrashSequence(__null, 10789); __attribute__(( nomerge)) ::abort(); } while (false); } } while (false); |
| 10790 | auto* letterFrame = static_cast<nsFirstLetterFrame*>(GetParent()); |
| 10791 | next = letterFrame->CreateContinuationForFramesAfter(this); |
Value stored to 'next' is never read | |
| 10792 | } |
| 10793 | |
| 10794 | SetLength(aLength, nullptr, 0); |
| 10795 | |
| 10796 | ClearTextRuns(); |
| 10797 | } |
| 10798 | |
| 10799 | bool nsTextFrame::IsFloatingFirstLetterChild() const { |
| 10800 | nsIFrame* frame = GetParent(); |
| 10801 | return frame && frame->IsFloating() && frame->IsLetterFrame(); |
| 10802 | } |
| 10803 | |
| 10804 | bool nsTextFrame::IsInitialLetterChild() const { |
| 10805 | nsIFrame* frame = GetParent(); |
| 10806 | return frame && frame->StyleTextReset()->mInitialLetter.size != 0.0f && |
| 10807 | frame->IsLetterFrame(); |
| 10808 | } |
| 10809 | |
| 10810 | struct nsTextFrame::NewlineProperty { |
| 10811 | int32_t mStartOffset; |
| 10812 | // The offset of the first \n after mStartOffset, or -1 if there is none |
| 10813 | int32_t mNewlineOffset; |
| 10814 | }; |
| 10815 | |
| 10816 | int32_t nsTextFrame::GetContentNewLineOffset( |
| 10817 | int32_t aOffset, NewlineProperty*& aCachedNewlineOffset) { |
| 10818 | int32_t contentNewLineOffset = -1; // this will be -1 or a content offset |
| 10819 | if (StyleText()->NewlineIsSignificant(this)) { |
| 10820 | // Pointer to the nsGkAtoms::newline set on this frame's element |
| 10821 | aCachedNewlineOffset = mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY) |
| 10822 | ? static_cast<NewlineProperty*>( |
| 10823 | mContent->GetProperty(nsGkAtoms::newline)) |
| 10824 | : nullptr; |
| 10825 | if (aCachedNewlineOffset && aCachedNewlineOffset->mStartOffset <= aOffset && |
| 10826 | (aCachedNewlineOffset->mNewlineOffset == -1 || |
| 10827 | aCachedNewlineOffset->mNewlineOffset >= aOffset)) { |
| 10828 | contentNewLineOffset = aCachedNewlineOffset->mNewlineOffset; |
| 10829 | } else { |
| 10830 | contentNewLineOffset = |
| 10831 | FindChar(CharacterDataBuffer(), aOffset, |
| 10832 | GetContent()->TextLength() - aOffset, '\n'); |
| 10833 | } |
| 10834 | } |
| 10835 | |
| 10836 | return contentNewLineOffset; |
| 10837 | } |
| 10838 | |
| 10839 | void nsTextFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, |
| 10840 | const ReflowInput& aReflowInput, |
| 10841 | nsReflowStatus& aStatus) { |
| 10842 | MarkInReflow(); |
| 10843 | DO_GLOBAL_REFLOW_COUNT("nsTextFrame")aPresContext->CountReflows(("nsTextFrame"), (nsIFrame*)this );; |
| 10844 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aStatus.IsEmpty())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aStatus.IsEmpty()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aStatus.IsEmpty()" " (" "Caller should pass a fresh reflow status!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 10844); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aStatus.IsEmpty()" ") (" "Caller should pass a fresh reflow status!" ")"); do { MOZ_CrashSequence(__null, 10844); __attribute__((nomerge)) :: abort(); } while (false); } } while (false); |
| 10845 | |
| 10846 | InvalidateSelectionState(); |
| 10847 | |
| 10848 | // XXX If there's no line layout, we shouldn't even have created this |
| 10849 | // frame. This may happen if, for example, this is text inside a table |
| 10850 | // but not inside a cell. For now, just don't reflow. |
| 10851 | if (!aReflowInput.mLineLayout) { |
| 10852 | ClearMetrics(aMetrics); |
| 10853 | return; |
| 10854 | } |
| 10855 | |
| 10856 | ReflowText(*aReflowInput.mLineLayout, aReflowInput.AvailableWidth(), |
| 10857 | aReflowInput.mRenderingContext->GetDrawTarget(), aMetrics, |
| 10858 | aStatus); |
| 10859 | } |
| 10860 | |
| 10861 | #ifdef ACCESSIBILITY1 |
| 10862 | /** |
| 10863 | * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText. |
| 10864 | */ |
| 10865 | class MOZ_STACK_CLASS ReflowTextA11yNotifier { |
| 10866 | public: |
| 10867 | ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent) |
| 10868 | : mContent(aContent), mPresContext(aPresContext) {} |
| 10869 | ~ReflowTextA11yNotifier() { |
| 10870 | if (nsAccessibilityService* accService = GetAccService()) { |
| 10871 | accService->UpdateText(mPresContext->PresShell(), mContent); |
| 10872 | } |
| 10873 | } |
| 10874 | |
| 10875 | ReflowTextA11yNotifier() = delete; |
| 10876 | ReflowTextA11yNotifier(const ReflowTextA11yNotifier&) = delete; |
| 10877 | ReflowTextA11yNotifier& operator=(const ReflowTextA11yNotifier&) = delete; |
| 10878 | |
| 10879 | private: |
| 10880 | nsIContent* mContent; |
| 10881 | nsPresContext* mPresContext; |
| 10882 | }; |
| 10883 | #endif |
| 10884 | |
| 10885 | void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, |
| 10886 | DrawTarget* aDrawTarget, ReflowOutput& aMetrics, |
| 10887 | nsReflowStatus& aStatus) { |
| 10888 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aStatus.IsEmpty())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aStatus.IsEmpty()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aStatus.IsEmpty()" " (" "Caller should pass a fresh reflow status!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 10888); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aStatus.IsEmpty()" ") (" "Caller should pass a fresh reflow status!" ")"); do { MOZ_CrashSequence(__null, 10888); __attribute__((nomerge)) :: abort(); } while (false); } } while (false); |
| 10889 | |
| 10890 | #ifdef NOISY_REFLOW |
| 10891 | ListTag(stdoutstdout); |
| 10892 | printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth); |
| 10893 | #endif |
| 10894 | |
| 10895 | nsPresContext* presContext = PresContext(); |
| 10896 | |
| 10897 | #ifdef ACCESSIBILITY1 |
| 10898 | // Schedule the update of accessible tree since rendered text might be |
| 10899 | // changed. |
| 10900 | if (StyleVisibility()->IsVisible()) { |
| 10901 | ReflowTextA11yNotifier(presContext, mContent); |
| 10902 | } |
| 10903 | #endif |
| 10904 | |
| 10905 | ///////////////////////////////////////////////////////////////////// |
| 10906 | // Set up flags and clear out state |
| 10907 | ///////////////////////////////////////////////////////////////////// |
| 10908 | |
| 10909 | // Clear out the reflow input flags in mState. We also clear the whitespace |
| 10910 | // flags because this can change whether the frame maps whitespace-only text |
| 10911 | // or not. We also clear the flag that tracks whether we had a pending |
| 10912 | // reflow request from CharacterDataChanged (since we're reflowing now). |
| 10913 | RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS); |
| 10914 | mReflowRequestedForCharDataChange = false; |
| 10915 | RemoveProperty(WebRenderTextBounds()); |
| 10916 | |
| 10917 | // Discard cached continuations array that will be invalidated by the reflow. |
| 10918 | if (nsTextFrame* first = FirstContinuation()) { |
| 10919 | first->ClearCachedContinuations(); |
| 10920 | } |
| 10921 | |
| 10922 | // Temporarily map all possible content while we construct our new textrun. |
| 10923 | // so that when doing reflow our styles prevail over any part of the |
| 10924 | // textrun we look at. Note that next-in-flows may be mapping the same |
| 10925 | // content; gfxTextRun construction logic will ensure that we take priority. |
| 10926 | int32_t maxContentLength = GetInFlowContentLength(); |
| 10927 | |
| 10928 | InvalidateSelectionState(); |
| 10929 | |
| 10930 | // We don't need to reflow if there is no content. |
| 10931 | if (!maxContentLength) { |
| 10932 | ClearMetrics(aMetrics); |
| 10933 | return; |
| 10934 | } |
| 10935 | |
| 10936 | #ifdef NOISY_BIDI |
| 10937 | printf("Reflowed textframe\n"); |
| 10938 | #endif |
| 10939 | |
| 10940 | const nsStyleText* textStyle = StyleText(); |
| 10941 | |
| 10942 | bool atStartOfLine = aLineLayout.LineAtStart(); |
| 10943 | if (atStartOfLine) { |
| 10944 | AddStateBits(TEXT_START_OF_LINE); |
| 10945 | } |
| 10946 | |
| 10947 | uint32_t flowEndInTextRun; |
| 10948 | nsIFrame* lineContainer = aLineLayout.LineContainerFrame(); |
| 10949 | const auto& characterDataBuffer = CharacterDataBuffer(); |
| 10950 | |
| 10951 | // DOM offsets of the text range we need to measure, after trimming |
| 10952 | // whitespace, restricting to first-letter, and restricting preformatted text |
| 10953 | // to nearest newline |
| 10954 | int32_t length = maxContentLength; |
| 10955 | int32_t offset = GetContentOffset(); |
| 10956 | |
| 10957 | // Restrict preformatted text to the nearest newline |
| 10958 | NewlineProperty* cachedNewlineOffset = nullptr; |
| 10959 | int32_t newLineOffset = -1; // this will be -1 or a content offset |
| 10960 | // This will just return -1 if newlines are not significant. |
| 10961 | int32_t contentNewLineOffset = |
| 10962 | GetContentNewLineOffset(offset, cachedNewlineOffset); |
| 10963 | if (contentNewLineOffset < offset + length) { |
| 10964 | // The new line offset could be outside this frame if the frame has been |
| 10965 | // split by bidi resolution. In that case we won't use it in this reflow |
| 10966 | // (newLineOffset will remain -1), but we will still cache it in mContent |
| 10967 | newLineOffset = contentNewLineOffset; |
| 10968 | } |
| 10969 | if (newLineOffset >= 0) { |
| 10970 | length = newLineOffset + 1 - offset; |
| 10971 | } |
| 10972 | |
| 10973 | if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) || |
| 10974 | HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) { |
| 10975 | // Skip leading whitespace. Make sure we don't skip a 'pre-line' |
| 10976 | // newline if there is one. |
| 10977 | int32_t skipLength = newLineOffset >= 0 ? length - 1 : length; |
| 10978 | int32_t whitespaceCount = |
| 10979 | GetTrimmableWhitespaceCount(characterDataBuffer, offset, skipLength, 1); |
| 10980 | if (whitespaceCount) { |
| 10981 | offset += whitespaceCount; |
| 10982 | length -= whitespaceCount; |
| 10983 | // Make sure this frame maps the trimmable whitespace. |
| 10984 | if (MOZ_UNLIKELY(offset > GetContentEnd())(__builtin_expect(!!(offset > GetContentEnd()), 0))) { |
| 10985 | SetLength(offset - GetContentOffset(), &aLineLayout, |
| 10986 | ALLOW_FRAME_CREATION_AND_DESTRUCTION); |
| 10987 | } |
| 10988 | } |
| 10989 | } |
| 10990 | |
| 10991 | // If trimming whitespace left us with nothing to do, return early. |
| 10992 | if (length == 0) { |
| 10993 | ClearMetrics(aMetrics); |
| 10994 | return; |
| 10995 | } |
| 10996 | |
| 10997 | bool completedFirstLetter = false; |
| 10998 | // Layout dependent styles are a problem because we need to reconstruct |
| 10999 | // the gfxTextRun based on our layout. |
| 11000 | if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) { |
| 11001 | SetLength(maxContentLength, &aLineLayout, |
| 11002 | ALLOW_FRAME_CREATION_AND_DESTRUCTION); |
| 11003 | |
| 11004 | if (aLineLayout.GetInFirstLetter()) { |
| 11005 | // floating first-letter boundaries are significant in textrun |
| 11006 | // construction, so clear the textrun out every time we hit a first-letter |
| 11007 | // and have changed our length (which controls the first-letter boundary) |
| 11008 | ClearTextRuns(); |
| 11009 | // Find the length of the first-letter. We need a textrun for this. |
| 11010 | // REVIEW: maybe-bogus inflation should be ok (fixed below) |
| 11011 | gfxSkipCharsIterator iter = |
| 11012 | EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer, |
| 11013 | aLineLayout.GetLine(), &flowEndInTextRun); |
| 11014 | |
| 11015 | if (mTextRun) { |
| 11016 | int32_t firstLetterLength = length; |
| 11017 | if (aLineLayout.GetFirstLetterStyleOK()) { |
| 11018 | // We only pass a language code to FindFirstLetterRange if it was |
| 11019 | // explicit in the content. |
| 11020 | const nsStyleFont* styleFont = StyleFont(); |
| 11021 | const nsAtom* lang = |
| 11022 | styleFont->mExplicitLanguage ? styleFont->GetLangAtom() : nullptr; |
| 11023 | completedFirstLetter = |
| 11024 | FindFirstLetterRange(characterDataBuffer, lang, mTextRun, offset, |
| 11025 | iter, &firstLetterLength); |
| 11026 | if (newLineOffset >= 0) { |
| 11027 | // Don't allow a preformatted newline to be part of a first-letter. |
| 11028 | firstLetterLength = std::min(firstLetterLength, length - 1); |
| 11029 | if (length == 1) { |
| 11030 | // There is no text to be consumed by the first-letter before the |
| 11031 | // preformatted newline. Note that the first letter is therefore |
| 11032 | // complete (FindFirstLetterRange will have returned false). |
| 11033 | completedFirstLetter = true; |
| 11034 | } |
| 11035 | } |
| 11036 | } else { |
| 11037 | // We're in a first-letter frame's first in flow, so if there |
| 11038 | // was a first-letter, we'd be it. However, for one reason |
| 11039 | // or another (e.g., preformatted line break before this text), |
| 11040 | // we're not actually supposed to have first-letter style. So |
| 11041 | // just make a zero-length first-letter. |
| 11042 | firstLetterLength = 0; |
| 11043 | completedFirstLetter = true; |
| 11044 | } |
| 11045 | length = firstLetterLength; |
| 11046 | if (length) { |
| 11047 | AddStateBits(TEXT_FIRST_LETTER); |
| 11048 | } |
| 11049 | // Change this frame's length to the first-letter length right now |
| 11050 | // so that when we rebuild the textrun it will be built with the |
| 11051 | // right first-letter boundary |
| 11052 | SetLength(offset + length - GetContentOffset(), &aLineLayout, |
| 11053 | ALLOW_FRAME_CREATION_AND_DESTRUCTION); |
| 11054 | // Ensure that the textrun will be rebuilt |
| 11055 | ClearTextRuns(); |
| 11056 | } |
| 11057 | } |
| 11058 | } |
| 11059 | |
| 11060 | float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this); |
| 11061 | |
| 11062 | if (!IsCurrentFontInflation(fontSizeInflation)) { |
| 11063 | // FIXME: Ideally, if we already have a text run, we'd move it to be |
| 11064 | // the uninflated text run. |
| 11065 | ClearTextRun(nullptr, nsTextFrame::eInflated); |
| 11066 | mFontMetrics = nullptr; |
| 11067 | } |
| 11068 | |
| 11069 | gfxSkipCharsIterator iter = |
| 11070 | EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer, |
| 11071 | aLineLayout.GetLine(), &flowEndInTextRun); |
| 11072 | |
| 11073 | NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation),do { if (!(IsCurrentFontInflation(fontSizeInflation))) { NS_DebugBreak (NS_DEBUG_ASSERTION, "EnsureTextRun should have set font size inflation" , "IsCurrentFontInflation(fontSizeInflation)", "./../../../layout/generic/nsTextFrame.cpp" , 11074); MOZ_PretendNoReturn(); } } while (0) |
| 11074 | "EnsureTextRun should have set font size inflation")do { if (!(IsCurrentFontInflation(fontSizeInflation))) { NS_DebugBreak (NS_DEBUG_ASSERTION, "EnsureTextRun should have set font size inflation" , "IsCurrentFontInflation(fontSizeInflation)", "./../../../layout/generic/nsTextFrame.cpp" , 11074); MOZ_PretendNoReturn(); } } while (0); |
| 11075 | |
| 11076 | if (mTextRun && iter.GetOriginalEnd() < offset + length) { |
| 11077 | // The textrun does not map enough text for this frame. This can happen |
| 11078 | // when the textrun was ended in the middle of a text node because a |
| 11079 | // preformatted newline was encountered, and prev-in-flow frames have |
| 11080 | // consumed all the text of the textrun. We need a new textrun. |
| 11081 | ClearTextRuns(); |
| 11082 | iter = EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer, |
| 11083 | aLineLayout.GetLine(), &flowEndInTextRun); |
| 11084 | } |
| 11085 | |
| 11086 | if (!mTextRun) { |
| 11087 | ClearMetrics(aMetrics); |
| 11088 | return; |
| 11089 | } |
| 11090 | |
| 11091 | NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(do { if (!(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped ( offset + length) <= mTextRun->GetLength())) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Text run does not map enough text for our reflow" , "gfxSkipCharsIterator(iter).ConvertOriginalToSkipped( offset + length) <= mTextRun->GetLength()" , "./../../../layout/generic/nsTextFrame.cpp", 11093); MOZ_PretendNoReturn (); } } while (0) |
| 11092 | offset + length) <= mTextRun->GetLength(),do { if (!(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped ( offset + length) <= mTextRun->GetLength())) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Text run does not map enough text for our reflow" , "gfxSkipCharsIterator(iter).ConvertOriginalToSkipped( offset + length) <= mTextRun->GetLength()" , "./../../../layout/generic/nsTextFrame.cpp", 11093); MOZ_PretendNoReturn (); } } while (0) |
| 11093 | "Text run does not map enough text for our reflow")do { if (!(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped ( offset + length) <= mTextRun->GetLength())) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Text run does not map enough text for our reflow" , "gfxSkipCharsIterator(iter).ConvertOriginalToSkipped( offset + length) <= mTextRun->GetLength()" , "./../../../layout/generic/nsTextFrame.cpp", 11093); MOZ_PretendNoReturn (); } } while (0); |
| 11094 | |
| 11095 | ///////////////////////////////////////////////////////////////////// |
| 11096 | // See how much text should belong to this text frame, and measure it |
| 11097 | ///////////////////////////////////////////////////////////////////// |
| 11098 | |
| 11099 | iter.SetOriginalOffset(offset); |
| 11100 | nscoord xOffsetForTabs = |
| 11101 | (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab) |
| 11102 | ? (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() - |
| 11103 | lineContainer->GetUsedBorderAndPadding().left) |
| 11104 | : -1; |
| 11105 | PropertyProvider provider(mTextRun, textStyle, characterDataBuffer, this, |
| 11106 | iter, length, lineContainer, xOffsetForTabs, |
| 11107 | nsTextFrame::eInflated, |
| 11108 | HasAnyStateBits(TEXT_START_OF_LINE)); |
| 11109 | |
| 11110 | uint32_t transformedOffset = provider.GetStart().GetSkippedOffset(); |
| 11111 | |
| 11112 | gfxFont::BoundingBoxType boundingBoxType = gfxFont::LOOSE_INK_EXTENTS; |
| 11113 | if (IsFloatingFirstLetterChild() || IsInitialLetterChild()) { |
| 11114 | if (nsFirstLetterFrame* firstLetter = do_QueryFrame(GetParent())) { |
| 11115 | if (firstLetter->UseTightBounds()) { |
| 11116 | boundingBoxType = gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS; |
| 11117 | } |
| 11118 | } |
| 11119 | } |
| 11120 | |
| 11121 | int32_t limitLength = length; |
| 11122 | int32_t forceBreak = aLineLayout.GetForcedBreakPosition(this); |
| 11123 | bool forceBreakAfter = false; |
| 11124 | if (forceBreak >= length) { |
| 11125 | forceBreakAfter = forceBreak == length; |
| 11126 | // The break is not within the text considered for this textframe. |
| 11127 | forceBreak = -1; |
| 11128 | } |
| 11129 | if (forceBreak >= 0) { |
| 11130 | limitLength = forceBreak; |
| 11131 | } |
| 11132 | // This is the heart of text reflow right here! We don't know where |
| 11133 | // to break, so we need to see how much text fits in the available width. |
| 11134 | uint32_t transformedLength; |
| 11135 | if (offset + limitLength >= int32_t(characterDataBuffer.GetLength())) { |
| 11136 | NS_ASSERTION(do { if (!(offset + limitLength == int32_t(characterDataBuffer .GetLength()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Content offset/length out of bounds" , "offset + limitLength == int32_t(characterDataBuffer.GetLength())" , "./../../../layout/generic/nsTextFrame.cpp", 11138); MOZ_PretendNoReturn (); } } while (0) |
| 11137 | offset + limitLength == int32_t(characterDataBuffer.GetLength()),do { if (!(offset + limitLength == int32_t(characterDataBuffer .GetLength()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Content offset/length out of bounds" , "offset + limitLength == int32_t(characterDataBuffer.GetLength())" , "./../../../layout/generic/nsTextFrame.cpp", 11138); MOZ_PretendNoReturn (); } } while (0) |
| 11138 | "Content offset/length out of bounds")do { if (!(offset + limitLength == int32_t(characterDataBuffer .GetLength()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Content offset/length out of bounds" , "offset + limitLength == int32_t(characterDataBuffer.GetLength())" , "./../../../layout/generic/nsTextFrame.cpp", 11138); MOZ_PretendNoReturn (); } } while (0); |
| 11139 | NS_ASSERTION(flowEndInTextRun >= transformedOffset,do { if (!(flowEndInTextRun >= transformedOffset)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Negative flow length?", "flowEndInTextRun >= transformedOffset" , "./../../../layout/generic/nsTextFrame.cpp", 11140); MOZ_PretendNoReturn (); } } while (0) |
| 11140 | "Negative flow length?")do { if (!(flowEndInTextRun >= transformedOffset)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Negative flow length?", "flowEndInTextRun >= transformedOffset" , "./../../../layout/generic/nsTextFrame.cpp", 11140); MOZ_PretendNoReturn (); } } while (0); |
| 11141 | transformedLength = flowEndInTextRun - transformedOffset; |
| 11142 | } else { |
| 11143 | // we're not looking at all the content, so we need to compute the |
| 11144 | // length of the transformed substring we're looking at |
| 11145 | gfxSkipCharsIterator iter(provider.GetStart()); |
| 11146 | iter.SetOriginalOffset(offset + limitLength); |
| 11147 | transformedLength = iter.GetSkippedOffset() - transformedOffset; |
| 11148 | } |
| 11149 | gfxTextRun::Metrics textMetrics; |
| 11150 | uint32_t transformedLastBreak = 0; |
| 11151 | bool usedHyphenation = false; |
| 11152 | gfxTextRun::TrimmableWS trimmableWS; |
| 11153 | gfxFloat availWidth = aAvailableWidth; |
| 11154 | if (Style()->IsTextCombined()) { |
| 11155 | // If text-combine-upright is 'all', we would compress whatever long |
| 11156 | // text into ~1em width, so there is no limited on the avail width. |
| 11157 | availWidth = std::numeric_limits<gfxFloat>::infinity(); |
| 11158 | } |
| 11159 | bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() || |
| 11160 | HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML); |
| 11161 | bool isBreakSpaces = |
| 11162 | textStyle->mWhiteSpaceCollapse == StyleWhiteSpaceCollapse::BreakSpaces; |
| 11163 | // allow whitespace to overflow the container |
| 11164 | bool whitespaceCanHang = textStyle->WhiteSpaceCanHangOrVisuallyCollapse(); |
| 11165 | gfxBreakPriority breakPriority = aLineLayout.LastOptionalBreakPriority(); |
| 11166 | gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak; |
| 11167 | bool shouldSuppressLineBreak = ShouldSuppressLineBreak(); |
| 11168 | if (shouldSuppressLineBreak) { |
| 11169 | suppressBreak = gfxTextRun::eSuppressAllBreaks; |
| 11170 | } else if (!aLineLayout.LineIsBreakable()) { |
| 11171 | suppressBreak = gfxTextRun::eSuppressInitialBreak; |
| 11172 | } |
| 11173 | uint32_t transformedCharsFit = mTextRun->BreakAndMeasureText( |
| 11174 | transformedOffset, transformedLength, HasAnyStateBits(TEXT_START_OF_LINE), |
| 11175 | availWidth, provider, suppressBreak, boundingBoxType, aDrawTarget, |
| 11176 | textStyle->WordCanWrap(this), textStyle->WhiteSpaceCanWrap(this), |
| 11177 | isBreakSpaces, |
| 11178 | // The following are output parameters: |
| 11179 | canTrimTrailingWhitespace || whitespaceCanHang ? &trimmableWS : nullptr, |
| 11180 | textMetrics, usedHyphenation, transformedLastBreak, |
| 11181 | // In/out |
| 11182 | breakPriority); |
| 11183 | if (!length && !textMetrics.mAscent && !textMetrics.mDescent) { |
| 11184 | // If we're measuring a zero-length piece of text, update |
| 11185 | // the height manually. |
| 11186 | nsFontMetrics* fm = provider.GetFontMetrics(); |
| 11187 | if (fm) { |
| 11188 | textMetrics.mAscent = gfxFloat(fm->MaxAscent()); |
| 11189 | textMetrics.mDescent = gfxFloat(fm->MaxDescent()); |
| 11190 | } |
| 11191 | } |
| 11192 | if (GetWritingMode().IsLineInverted()) { |
| 11193 | std::swap(textMetrics.mAscent, textMetrics.mDescent); |
| 11194 | textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost(); |
| 11195 | } |
| 11196 | // The "end" iterator points to the first character after the string mapped |
| 11197 | // by this frame. Basically, its original-string offset is offset+charsFit |
| 11198 | // after we've computed charsFit. |
| 11199 | gfxSkipCharsIterator end(provider.GetEndHint()); |
| 11200 | end.SetSkippedOffset(transformedOffset + transformedCharsFit); |
| 11201 | int32_t charsFit = end.GetOriginalOffset() - offset; |
| 11202 | if (offset + charsFit == newLineOffset) { |
| 11203 | // We broke before a trailing preformatted '\n'. The newline should |
| 11204 | // be assigned to this frame. Note that newLineOffset will be -1 if |
| 11205 | // there was no preformatted newline, so we wouldn't get here in that |
| 11206 | // case. |
| 11207 | ++charsFit; |
| 11208 | } |
| 11209 | // That might have taken us beyond our assigned content range (because |
| 11210 | // we might have advanced over some skipped chars that extend outside |
| 11211 | // this frame), so get back in. |
| 11212 | int32_t lastBreak = -1; |
| 11213 | if (charsFit >= limitLength) { |
| 11214 | charsFit = limitLength; |
| 11215 | if (transformedLastBreak != UINT32_MAX(4294967295U)) { |
| 11216 | // lastBreak is needed. |
| 11217 | // This may set lastBreak greater than 'length', but that's OK |
| 11218 | lastBreak = end.ConvertSkippedToOriginal(transformedOffset + |
| 11219 | transformedLastBreak); |
| 11220 | } |
| 11221 | end.SetOriginalOffset(offset + charsFit); |
| 11222 | // If we were forced to fit, and the break position is after a soft hyphen, |
| 11223 | // note that this is a hyphenation break. |
| 11224 | if ((forceBreak >= 0 || forceBreakAfter) && |
| 11225 | HasSoftHyphenBefore(characterDataBuffer, mTextRun, offset, end)) { |
| 11226 | usedHyphenation = true; |
| 11227 | } |
| 11228 | } |
| 11229 | if (usedHyphenation) { |
| 11230 | // Fix up metrics to include hyphen |
| 11231 | AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &textMetrics, |
| 11232 | boundingBoxType, aDrawTarget); |
| 11233 | AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS); |
| 11234 | } |
| 11235 | if (textMetrics.mBoundingBox.IsEmpty()) { |
| 11236 | AddStateBits(TEXT_NO_RENDERED_GLYPHS); |
| 11237 | } |
| 11238 | |
| 11239 | bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength; |
| 11240 | if (trimmableWS.mAdvance > 0.0) { |
| 11241 | if (canTrimTrailingWhitespace) { |
| 11242 | // Optimization: if we we can be sure this frame will be at end of line, |
| 11243 | // then trim the whitespace now. |
| 11244 | if (brokeText || HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) { |
| 11245 | // We're definitely going to break so our trailing whitespace should |
| 11246 | // definitely be trimmed. Record that we've already done it. |
| 11247 | AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE); |
| 11248 | textMetrics.mAdvanceWidth -= trimmableWS.mAdvance; |
| 11249 | trimmableWS.mAdvance = 0.0; |
| 11250 | } |
| 11251 | ClearHangableISize(); |
| 11252 | ClearTrimmableWS(); |
| 11253 | } else if (whitespaceCanHang) { |
| 11254 | // Figure out how much whitespace will hang if at end-of-line. |
| 11255 | gfxFloat hang = |
| 11256 | std::min(std::max(0.0, textMetrics.mAdvanceWidth - availWidth), |
| 11257 | gfxFloat(trimmableWS.mAdvance)); |
| 11258 | SetHangableISize(NSToCoordRound(trimmableWS.mAdvance - hang)); |
| 11259 | // nsLineLayout only needs the TrimmableWS property if justifying, so |
| 11260 | // check whether this is relevant. |
| 11261 | if (textStyle->mTextAlign == StyleTextAlign::Justify || |
| 11262 | textStyle->mTextAlignLast == StyleTextAlignLast::Justify) { |
| 11263 | SetTrimmableWS(trimmableWS); |
| 11264 | } |
| 11265 | textMetrics.mAdvanceWidth -= hang; |
| 11266 | trimmableWS.mAdvance = 0.0; |
| 11267 | } else { |
| 11268 | MOZ_ASSERT_UNREACHABLE("How did trimmableWS get set?!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(false)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: " "How did trimmableWS get set?!" ")", "./../../../layout/generic/nsTextFrame.cpp" , 11268); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "How did trimmableWS get set?!" ")" ); do { MOZ_CrashSequence(__null, 11268); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
| 11269 | ClearHangableISize(); |
| 11270 | ClearTrimmableWS(); |
| 11271 | trimmableWS.mAdvance = 0.0; |
| 11272 | } |
| 11273 | } else { |
| 11274 | // Remove any stale frame properties. |
| 11275 | ClearHangableISize(); |
| 11276 | ClearTrimmableWS(); |
| 11277 | } |
| 11278 | |
| 11279 | if (!brokeText && lastBreak >= 0) { |
| 11280 | // Since everything fit and no break was forced, |
| 11281 | // record the last break opportunity |
| 11282 | NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth,do { if (!(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "If the text doesn't fit, and we have a break opportunity, " "why didn't MeasureText use it?", "textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth" , "./../../../layout/generic/nsTextFrame.cpp", 11284); MOZ_PretendNoReturn (); } } while (0) |
| 11283 | "If the text doesn't fit, and we have a break opportunity, "do { if (!(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "If the text doesn't fit, and we have a break opportunity, " "why didn't MeasureText use it?", "textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth" , "./../../../layout/generic/nsTextFrame.cpp", 11284); MOZ_PretendNoReturn (); } } while (0) |
| 11284 | "why didn't MeasureText use it?")do { if (!(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "If the text doesn't fit, and we have a break opportunity, " "why didn't MeasureText use it?", "textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth" , "./../../../layout/generic/nsTextFrame.cpp", 11284); MOZ_PretendNoReturn (); } } while (0); |
| 11285 | MOZ_ASSERT(lastBreak >= offset, "Strange break position")do { static_assert( mozilla::detail::AssertionConditionType< decltype(lastBreak >= offset)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(lastBreak >= offset))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("lastBreak >= offset" " (" "Strange break position" ")", "./../../../layout/generic/nsTextFrame.cpp" , 11285); AnnotateMozCrashReason("MOZ_ASSERT" "(" "lastBreak >= offset" ") (" "Strange break position" ")"); do { MOZ_CrashSequence( __null, 11285); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 11286 | aLineLayout.NotifyOptionalBreakPosition(this, lastBreak - offset, true, |
| 11287 | breakPriority); |
| 11288 | } |
| 11289 | |
| 11290 | int32_t contentLength = offset + charsFit - GetContentOffset(); |
| 11291 | |
| 11292 | ///////////////////////////////////////////////////////////////////// |
| 11293 | // Compute output metrics |
| 11294 | ///////////////////////////////////////////////////////////////////// |
| 11295 | |
| 11296 | // first-letter frames should use the tight bounding box metrics for |
| 11297 | // ascent/descent for good drop-cap effects |
| 11298 | if (HasAnyStateBits(TEXT_FIRST_LETTER)) { |
| 11299 | textMetrics.mAscent = |
| 11300 | std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y()); |
| 11301 | textMetrics.mDescent = |
| 11302 | std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost()); |
| 11303 | } |
| 11304 | |
| 11305 | // Setup metrics for caller |
| 11306 | // Disallow negative widths |
| 11307 | WritingMode wm = GetWritingMode(); |
| 11308 | LogicalSize finalSize(wm); |
| 11309 | finalSize.ISize(wm) = |
| 11310 | NSToCoordCeilClamped(std::max(gfxFloat(0.0), textMetrics.mAdvanceWidth)); |
| 11311 | |
| 11312 | nscoord fontBaseline; |
| 11313 | // Note(dshin): Baseline should tecnhically be halfway through the em box for |
| 11314 | // a central baseline. It is simply half of the text run block size so that it |
| 11315 | // can be easily calculated in `GetNaturalBaselineBOffset`. |
| 11316 | if (transformedCharsFit == 0 && !usedHyphenation) { |
| 11317 | aMetrics.SetBlockStartAscent(0); |
| 11318 | finalSize.BSize(wm) = 0; |
| 11319 | fontBaseline = 0; |
| 11320 | } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) { |
| 11321 | fontBaseline = NSToCoordCeil(textMetrics.mAscent); |
| 11322 | const auto size = fontBaseline + NSToCoordCeil(textMetrics.mDescent); |
| 11323 | // Use actual text metrics for floating first letter frame. |
| 11324 | aMetrics.SetBlockStartAscent(wm.IsAlphabeticalBaseline() ? fontBaseline |
| 11325 | : size / 2); |
| 11326 | finalSize.BSize(wm) = size; |
| 11327 | } else { |
| 11328 | // Otherwise, ascent should contain the overline drawable area. |
| 11329 | // And also descent should contain the underline drawable area. |
| 11330 | // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them. |
| 11331 | nsFontMetrics* fm = provider.GetFontMetrics(); |
| 11332 | nscoord fontAscent = |
| 11333 | wm.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent(); |
| 11334 | nscoord fontDescent = |
| 11335 | wm.IsLineInverted() ? fm->MaxAscent() : fm->MaxDescent(); |
| 11336 | fontBaseline = std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent); |
| 11337 | const auto size = |
| 11338 | fontBaseline + |
| 11339 | std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent); |
| 11340 | aMetrics.SetBlockStartAscent(wm.IsAlphabeticalBaseline() ? fontBaseline |
| 11341 | : size / 2); |
| 11342 | finalSize.BSize(wm) = size; |
| 11343 | } |
| 11344 | if (Style()->IsTextCombined()) { |
| 11345 | nsFontMetrics* fm = provider.GetFontMetrics(); |
| 11346 | nscoord width = finalSize.ISize(wm); |
| 11347 | nscoord em = fm->EmHeight(); |
| 11348 | // Compress the characters in horizontal axis if necessary. |
| 11349 | auto* data = GetOrCreateDeletableProperty(TextCombineDataProperty()); |
| 11350 | data->mNaturalWidth = width; |
| 11351 | finalSize.ISize(wm) = em; |
| 11352 | // If we're going to have to adjust the block-size to make it 1em, make an |
| 11353 | // appropriate adjustment to the font baseline (determined by the ascent |
| 11354 | // returned in aMetrics) |
| 11355 | if (finalSize.BSize(wm) != em) { |
| 11356 | fontBaseline = |
| 11357 | aMetrics.BlockStartAscent() + (em - finalSize.BSize(wm)) / 2; |
| 11358 | aMetrics.SetBlockStartAscent(fontBaseline); |
| 11359 | } |
| 11360 | // If we have a next-in-flow with the same style, remove our block-size |
| 11361 | // so that they end up beside each other; only the last in the series |
| 11362 | // gets to keep its block-axis size. |
| 11363 | if (GetNextSibling() && GetNextSibling()->Style() == Style()) { |
| 11364 | finalSize.BSize(wm) = 0; |
| 11365 | } else { |
| 11366 | // Make the characters be in an 1em square. |
| 11367 | finalSize.BSize(wm) = em; |
| 11368 | } |
| 11369 | // If there are earlier sibling frames with the same style, and we have |
| 11370 | // reached the end of the run of same-style siblings, recompute the scale |
| 11371 | // as necessary to make them all fit. (This is a bit inefficient, but is |
| 11372 | // such a rare case that we don't much care.) |
| 11373 | nsIFrame* f = GetPrevSibling(); |
| 11374 | if (f && f->Style() == Style() && |
| 11375 | (!GetNextSibling() || GetNextSibling()->Style() != Style())) { |
| 11376 | // Collect the total "natural" width of the text. |
| 11377 | while (f->Style() == Style()) { |
| 11378 | if (auto* fData = f->GetProperty(TextCombineDataProperty())) { |
| 11379 | width += fData->mNaturalWidth; |
| 11380 | } |
| 11381 | if (!f->GetPrevSibling()) { |
| 11382 | break; |
| 11383 | } |
| 11384 | f = f->GetPrevSibling(); |
| 11385 | } |
| 11386 | } else { |
| 11387 | // Not at the end of a run of frames; we're just going to handle `this`. |
| 11388 | f = this; |
| 11389 | } |
| 11390 | // Figure out scaling to apply to this frame (or to the run of same-style |
| 11391 | // sibling frames), and the starting offset within the em square. |
| 11392 | float scale; |
| 11393 | nscoord offset; |
| 11394 | if (width > em) { |
| 11395 | scale = static_cast<float>(em) / width; |
| 11396 | offset = 0; |
| 11397 | } else { |
| 11398 | scale = 1.0f; |
| 11399 | offset = (em - width) / 2; |
| 11400 | } |
| 11401 | while (true) { |
| 11402 | if (auto* fData = f->GetProperty(TextCombineDataProperty())) { |
| 11403 | fData->mScale = scale; |
| 11404 | fData->mOffset = offset; |
| 11405 | offset += fData->mNaturalWidth * scale; |
| 11406 | } |
| 11407 | if (f == this) { |
| 11408 | break; |
| 11409 | } |
| 11410 | f = f->GetNextSibling(); |
| 11411 | } |
| 11412 | } |
| 11413 | aMetrics.SetSize(wm, finalSize); |
| 11414 | |
| 11415 | NS_ASSERTION(aMetrics.BlockStartAscent() >= 0, "Negative ascent???")do { if (!(aMetrics.BlockStartAscent() >= 0)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Negative ascent???", "aMetrics.BlockStartAscent() >= 0" , "./../../../layout/generic/nsTextFrame.cpp", 11415); MOZ_PretendNoReturn (); } } while (0); |
| 11416 | // The effective "descent" here will be negative in the case of a frame that |
| 11417 | // is participating in text-combine-upright, but is not the last frame within |
| 11418 | // the combined upright cell, because we zero out its block-size (see above). |
| 11419 | // So this creates an exception to the non-negative assertion here. |
| 11420 | DebugOnly<nscoord> descent = |
| 11421 | (Style()->IsTextCombined() ? aMetrics.ISize(aMetrics.GetWritingMode()) |
| 11422 | : aMetrics.BSize(aMetrics.GetWritingMode())) - |
| 11423 | aMetrics.BlockStartAscent(); |
| 11424 | NS_ASSERTION(descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() &&do { if (!(descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() && GetNextSibling()->Style() == Style ()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Unexpected negative descent???" , "descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() && GetNextSibling()->Style() == Style())" , "./../../../layout/generic/nsTextFrame.cpp", 11426); MOZ_PretendNoReturn (); } } while (0) |
| 11425 | GetNextSibling()->Style() == Style()),do { if (!(descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() && GetNextSibling()->Style() == Style ()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Unexpected negative descent???" , "descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() && GetNextSibling()->Style() == Style())" , "./../../../layout/generic/nsTextFrame.cpp", 11426); MOZ_PretendNoReturn (); } } while (0) |
| 11426 | "Unexpected negative descent???")do { if (!(descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() && GetNextSibling()->Style() == Style ()))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Unexpected negative descent???" , "descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() && GetNextSibling()->Style() == Style())" , "./../../../layout/generic/nsTextFrame.cpp", 11426); MOZ_PretendNoReturn (); } } while (0); |
| 11427 | |
| 11428 | mAscent = fontBaseline; |
| 11429 | |
| 11430 | // Handle text that runs outside its normal bounds. |
| 11431 | nsRect boundingBox = RoundOut(textMetrics.mBoundingBox); |
| 11432 | if (mTextRun->IsVertical()) { |
| 11433 | // Swap line-relative textMetrics dimensions to physical coordinates. |
| 11434 | std::swap(boundingBox.x, boundingBox.y); |
| 11435 | std::swap(boundingBox.width, boundingBox.height); |
| 11436 | if (GetWritingMode().IsVerticalRL()) { |
| 11437 | boundingBox.x = -boundingBox.XMost(); |
| 11438 | boundingBox.x += aMetrics.Width() - mAscent; |
| 11439 | } else { |
| 11440 | boundingBox.x += mAscent; |
| 11441 | } |
| 11442 | } else { |
| 11443 | boundingBox.y += mAscent; |
| 11444 | } |
| 11445 | aMetrics.SetOverflowAreasToDesiredBounds(); |
| 11446 | aMetrics.InkOverflow().UnionRect(aMetrics.InkOverflow(), boundingBox); |
| 11447 | |
| 11448 | // When we have text decorations, we don't need to compute their overflow now |
| 11449 | // because we're guaranteed to do it later |
| 11450 | // (see nsLineLayout::RelativePositionFrames) |
| 11451 | UnionAdditionalOverflow(presContext, aLineLayout.LineContainerFrame(), |
| 11452 | provider, &aMetrics.InkOverflow(), false, true); |
| 11453 | |
| 11454 | ///////////////////////////////////////////////////////////////////// |
| 11455 | // Clean up, update state |
| 11456 | ///////////////////////////////////////////////////////////////////// |
| 11457 | |
| 11458 | // If all our characters are discarded or collapsed, then trimmable width |
| 11459 | // from the last textframe should be preserved. Otherwise the trimmable width |
| 11460 | // from this textframe overrides. (Currently in CSS trimmable width can be |
| 11461 | // at most one space so there's no way for trimmable width from a previous |
| 11462 | // frame to accumulate with trimmable width from this frame.) |
| 11463 | if (transformedCharsFit > 0) { |
| 11464 | aLineLayout.SetTrimmableISize(NSToCoordFloor(trimmableWS.mAdvance)); |
| 11465 | AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS); |
| 11466 | } |
| 11467 | bool breakAfter = forceBreakAfter; |
| 11468 | if (!shouldSuppressLineBreak) { |
| 11469 | if (charsFit > 0 && charsFit == length && |
| 11470 | textStyle->mHyphens != StyleHyphens::None && |
| 11471 | HasSoftHyphenBefore(characterDataBuffer, mTextRun, offset, end)) { |
| 11472 | bool fits = |
| 11473 | textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth; |
| 11474 | // Record a potential break after final soft hyphen |
| 11475 | aLineLayout.NotifyOptionalBreakPosition(this, length, fits, |
| 11476 | gfxBreakPriority::eNormalBreak); |
| 11477 | } |
| 11478 | // length == 0 means either the text is empty or it's all collapsed away |
| 11479 | bool emptyTextAtStartOfLine = atStartOfLine && length == 0; |
| 11480 | if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine && |
| 11481 | transformedOffset + transformedLength == mTextRun->GetLength() && |
| 11482 | (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTrailingBreak)) { |
| 11483 | // We placed all the text in the textrun and we have a break opportunity |
| 11484 | // at the end of the textrun. We need to record it because the following |
| 11485 | // content may not care about nsLineBreaker. |
| 11486 | |
| 11487 | // Note that because we didn't break, we can be sure that (thanks to the |
| 11488 | // code up above) textMetrics.mAdvanceWidth includes the width of any |
| 11489 | // trailing whitespace. So we need to subtract trimmableWidth here |
| 11490 | // because if we did break at this point, that much width would be |
| 11491 | // trimmed. |
| 11492 | if (textMetrics.mAdvanceWidth - trimmableWS.mAdvance > availWidth) { |
| 11493 | breakAfter = true; |
| 11494 | } else { |
| 11495 | aLineLayout.NotifyOptionalBreakPosition(this, length, true, |
| 11496 | gfxBreakPriority::eNormalBreak); |
| 11497 | } |
| 11498 | } |
| 11499 | } |
| 11500 | |
| 11501 | // Compute reflow status |
| 11502 | if (contentLength != maxContentLength) { |
| 11503 | aStatus.SetIncomplete(); |
| 11504 | } |
| 11505 | |
| 11506 | if (charsFit == 0 && length > 0 && !usedHyphenation) { |
| 11507 | // Couldn't place any text |
| 11508 | aStatus.SetInlineLineBreakBeforeAndReset(); |
| 11509 | } else if (contentLength > 0 && |
| 11510 | mContentOffset + contentLength - 1 == newLineOffset) { |
| 11511 | // Ends in \n |
| 11512 | aStatus.SetInlineLineBreakAfter(); |
| 11513 | aLineLayout.SetLineEndsInBR(true); |
| 11514 | } else if (breakAfter) { |
| 11515 | aStatus.SetInlineLineBreakAfter(); |
| 11516 | } |
| 11517 | if (completedFirstLetter) { |
| 11518 | aLineLayout.SetFirstLetterStyleOK(false); |
| 11519 | aStatus.SetFirstLetterComplete(); |
| 11520 | } |
| 11521 | if (brokeText && breakPriority == gfxBreakPriority::eWordWrapBreak) { |
| 11522 | aLineLayout.SetUsedOverflowWrap(); |
| 11523 | } |
| 11524 | |
| 11525 | // Updated the cached NewlineProperty, or delete it. |
| 11526 | if (contentLength < maxContentLength && |
| 11527 | textStyle->NewlineIsSignificant(this) && |
| 11528 | (contentNewLineOffset < 0 || |
| 11529 | mContentOffset + contentLength <= contentNewLineOffset)) { |
| 11530 | if (!cachedNewlineOffset) { |
| 11531 | cachedNewlineOffset = new NewlineProperty; |
| 11532 | if (NS_FAILED(mContent->SetProperty(((bool)(__builtin_expect(!!(NS_FAILED_impl(mContent->SetProperty ( nsGkAtoms::newline, cachedNewlineOffset, nsINode::DeleteProperty <NewlineProperty>))), 0))) |
| 11533 | nsGkAtoms::newline, cachedNewlineOffset,((bool)(__builtin_expect(!!(NS_FAILED_impl(mContent->SetProperty ( nsGkAtoms::newline, cachedNewlineOffset, nsINode::DeleteProperty <NewlineProperty>))), 0))) |
| 11534 | nsINode::DeleteProperty<NewlineProperty>))((bool)(__builtin_expect(!!(NS_FAILED_impl(mContent->SetProperty ( nsGkAtoms::newline, cachedNewlineOffset, nsINode::DeleteProperty <NewlineProperty>))), 0)))) { |
| 11535 | delete cachedNewlineOffset; |
| 11536 | cachedNewlineOffset = nullptr; |
| 11537 | } |
| 11538 | mContent->SetFlags(NS_HAS_NEWLINE_PROPERTY); |
| 11539 | } |
| 11540 | if (cachedNewlineOffset) { |
| 11541 | cachedNewlineOffset->mStartOffset = offset; |
| 11542 | cachedNewlineOffset->mNewlineOffset = contentNewLineOffset; |
| 11543 | } |
| 11544 | } else if (cachedNewlineOffset) { |
| 11545 | mContent->RemoveProperty(nsGkAtoms::newline); |
| 11546 | mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY); |
| 11547 | } |
| 11548 | |
| 11549 | // Compute space and letter counts for justification, if required |
| 11550 | if ((lineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify || |
| 11551 | lineContainer->StyleText()->mTextAlignLast == |
| 11552 | StyleTextAlignLast::Justify || |
| 11553 | shouldSuppressLineBreak) && |
| 11554 | !lineContainer->IsInSVGTextSubtree()) { |
| 11555 | AddStateBits(TEXT_JUSTIFICATION_ENABLED); |
| 11556 | Range range(uint32_t(offset), uint32_t(offset + charsFit)); |
| 11557 | aLineLayout.SetJustificationInfo(provider.ComputeJustification(range)); |
| 11558 | } |
| 11559 | |
| 11560 | SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION); |
| 11561 | |
| 11562 | InvalidateFrame(); |
| 11563 | |
| 11564 | #ifdef NOISY_REFLOW |
| 11565 | ListTag(stdoutstdout); |
| 11566 | printf(": desiredSize=%d,%d(b=%d) status=%x\n", aMetrics.Width(), |
| 11567 | aMetrics.Height(), aMetrics.BlockStartAscent(), aStatus); |
| 11568 | #endif |
| 11569 | } |
| 11570 | |
| 11571 | /* virtual */ |
| 11572 | bool nsTextFrame::CanContinueTextRun() const { |
| 11573 | // We can continue a text run through a text frame |
| 11574 | return true; |
| 11575 | } |
| 11576 | |
| 11577 | nsTextFrame::TrimOutput nsTextFrame::TrimTrailingWhiteSpace( |
| 11578 | DrawTarget* aDrawTarget) { |
| 11579 | MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW),do { static_assert( mozilla::detail::AssertionConditionType< decltype(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW ))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)" " (" "frame should have been reflowed" ")", "./../../../layout/generic/nsTextFrame.cpp" , 11580); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)" ") (" "frame should have been reflowed" ")"); do { MOZ_CrashSequence (__null, 11580); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 11580 | "frame should have been reflowed")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW ))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)" " (" "frame should have been reflowed" ")", "./../../../layout/generic/nsTextFrame.cpp" , 11580); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)" ") (" "frame should have been reflowed" ")"); do { MOZ_CrashSequence (__null, 11580); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 11581 | |
| 11582 | TrimOutput result; |
| 11583 | result.mChanged = false; |
| 11584 | result.mDeltaWidth = 0; |
| 11585 | |
| 11586 | AddStateBits(TEXT_END_OF_LINE); |
| 11587 | |
| 11588 | if (!GetTextRun(nsTextFrame::eInflated)) { |
| 11589 | // If reflow didn't create a textrun, there must have been no content once |
| 11590 | // leading whitespace was trimmed, so nothing more to do here. |
| 11591 | return result; |
| 11592 | } |
| 11593 | |
| 11594 | int32_t contentLength = GetContentLength(); |
| 11595 | if (!contentLength) { |
| 11596 | return result; |
| 11597 | } |
| 11598 | |
| 11599 | gfxSkipCharsIterator start = |
| 11600 | EnsureTextRun(nsTextFrame::eInflated, aDrawTarget); |
| 11601 | NS_ENSURE_TRUE(mTextRun, result)do { if ((__builtin_expect(!!(!(mTextRun)), 0))) { NS_DebugBreak (NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "mTextRun" ") failed", nullptr , "./../../../layout/generic/nsTextFrame.cpp", 11601); return result; } } while (false); |
| 11602 | |
| 11603 | uint32_t trimmedStart = start.GetSkippedOffset(); |
| 11604 | |
| 11605 | const auto& characterDataBuffer = CharacterDataBuffer(); |
| 11606 | TrimmedOffsets trimmed = GetTrimmedOffsets(characterDataBuffer); |
| 11607 | gfxSkipCharsIterator trimmedEndIter = start; |
| 11608 | const nsStyleText* textStyle = StyleText(); |
| 11609 | gfxFloat delta = 0; |
| 11610 | uint32_t trimmedEnd = |
| 11611 | trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd()); |
| 11612 | |
| 11613 | if (!HasAnyStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE) && |
| 11614 | trimmed.GetEnd() < GetContentEnd()) { |
| 11615 | gfxSkipCharsIterator end = trimmedEndIter; |
| 11616 | uint32_t endOffset = |
| 11617 | end.ConvertOriginalToSkipped(GetContentOffset() + contentLength); |
| 11618 | if (trimmedEnd < endOffset) { |
| 11619 | // We can't be dealing with tabs here ... they wouldn't be trimmed. So |
| 11620 | // it's OK to pass null for the line container. |
| 11621 | PropertyProvider provider(mTextRun, textStyle, characterDataBuffer, this, |
| 11622 | start, contentLength, nullptr, 0, |
| 11623 | nsTextFrame::eInflated, |
| 11624 | HasAnyStateBits(TEXT_START_OF_LINE)); |
| 11625 | delta = |
| 11626 | mTextRun->GetAdvanceWidth(Range(trimmedEnd, endOffset), &provider); |
| 11627 | result.mChanged = true; |
| 11628 | } |
| 11629 | } |
| 11630 | |
| 11631 | gfxFloat advanceDelta; |
| 11632 | mTextRun->SetLineBreaks(Range(trimmedStart, trimmedEnd), |
| 11633 | HasAnyStateBits(TEXT_START_OF_LINE), true, |
| 11634 | &advanceDelta); |
| 11635 | if (advanceDelta != 0) { |
| 11636 | result.mChanged = true; |
| 11637 | } |
| 11638 | |
| 11639 | // aDeltaWidth is *subtracted* from our width. |
| 11640 | // If advanceDelta is positive then setting the line break made us longer, |
| 11641 | // so aDeltaWidth could go negative. |
| 11642 | result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta); |
| 11643 | // If aDeltaWidth goes negative, that means this frame might not actually fit |
| 11644 | // anymore!!! We need higher level line layout to recover somehow. |
| 11645 | // If it's because the frame has a soft hyphen that is now being displayed, |
| 11646 | // this should actually be OK, because our reflow recorded the break |
| 11647 | // opportunity that allowed the soft hyphen to be used, and we wouldn't |
| 11648 | // have recorded the opportunity unless the hyphen fit (or was the first |
| 11649 | // opportunity on the line). |
| 11650 | // Otherwise this can/ really only happen when we have glyphs with special |
| 11651 | // shapes at the end of lines, I think. Breaking inside a kerning pair won't |
| 11652 | // do it because that would mean we broke inside this textrun, and |
| 11653 | // BreakAndMeasureText should make sure the resulting shaped substring fits. |
| 11654 | // Maybe if we passed a maxTextLength? But that only happens at direction |
| 11655 | // changes (so we wouldn't kern across the boundary) or for first-letter |
| 11656 | // (which always fits because it starts the line!). |
| 11657 | NS_WARNING_ASSERTION(result.mDeltaWidth >= 0,do { if (!(result.mDeltaWidth >= 0)) { NS_DebugBreak(NS_DEBUG_WARNING , "Negative deltawidth, something odd is happening", "result.mDeltaWidth >= 0" , "./../../../layout/generic/nsTextFrame.cpp", 11658); } } while (false) |
| 11658 | "Negative deltawidth, something odd is happening")do { if (!(result.mDeltaWidth >= 0)) { NS_DebugBreak(NS_DEBUG_WARNING , "Negative deltawidth, something odd is happening", "result.mDeltaWidth >= 0" , "./../../../layout/generic/nsTextFrame.cpp", 11658); } } while (false); |
| 11659 | |
| 11660 | #ifdef NOISY_TRIM |
| 11661 | ListTag(stdoutstdout); |
| 11662 | printf(": trim => %d\n", result.mDeltaWidth); |
| 11663 | #endif |
| 11664 | return result; |
| 11665 | } |
| 11666 | |
| 11667 | OverflowAreas nsTextFrame::RecomputeOverflow(nsIFrame* aBlockFrame, |
| 11668 | bool aIncludeShadows) { |
| 11669 | RemoveProperty(WebRenderTextBounds()); |
| 11670 | |
| 11671 | nsRect bounds(nsPoint(0, 0), GetSize()); |
| 11672 | OverflowAreas result(bounds, bounds); |
| 11673 | |
| 11674 | gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); |
| 11675 | if (!mTextRun) { |
| 11676 | return result; |
| 11677 | } |
| 11678 | |
| 11679 | PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics); |
| 11680 | // Don't trim trailing space, in case we need to paint it as selected. |
| 11681 | provider.InitializeForDisplay(false); |
| 11682 | |
| 11683 | gfxTextRun::Metrics textMetrics = |
| 11684 | mTextRun->MeasureText(ComputeTransformedRange(provider), |
| 11685 | gfxFont::LOOSE_INK_EXTENTS, nullptr, &provider); |
| 11686 | if (GetWritingMode().IsLineInverted()) { |
| 11687 | textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost(); |
| 11688 | } |
| 11689 | nsRect boundingBox = RoundOut(textMetrics.mBoundingBox); |
| 11690 | boundingBox += nsPoint(0, mAscent); |
| 11691 | if (mTextRun->IsVertical()) { |
| 11692 | // Swap line-relative textMetrics dimensions to physical coordinates. |
| 11693 | std::swap(boundingBox.x, boundingBox.y); |
| 11694 | std::swap(boundingBox.width, boundingBox.height); |
| 11695 | } |
| 11696 | nsRect& vis = result.InkOverflow(); |
| 11697 | vis.UnionRect(vis, boundingBox); |
| 11698 | UnionAdditionalOverflow(PresContext(), aBlockFrame, provider, &vis, true, |
| 11699 | aIncludeShadows); |
| 11700 | return result; |
| 11701 | } |
| 11702 | |
| 11703 | static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle, |
| 11704 | const gfxTextRun* aTextRun, uint32_t aSkippedOffset, |
| 11705 | const CharacterDataBuffer& aBuffer, |
| 11706 | int32_t aBufferOffset, int32_t aBufferLen, |
| 11707 | nsAString& aOut) { |
| 11708 | nsAutoString fragString; |
| 11709 | char16_t* out; |
| 11710 | bool needsToMaskPassword = NeedsToMaskPassword(aFrame); |
| 11711 | if (aStyle->mTextTransform.IsNone() && !needsToMaskPassword && |
| 11712 | aStyle->mWebkitTextSecurity == StyleTextSecurity::None) { |
| 11713 | // No text-transform, so we can copy directly to the output string. |
| 11714 | aOut.SetLength(aOut.Length() + aBufferLen); |
| 11715 | out = aOut.EndWriting() - aBufferLen; |
| 11716 | } else { |
| 11717 | // Use a temporary string as source for the transform. |
| 11718 | fragString.SetLength(aBufferLen); |
| 11719 | out = fragString.BeginWriting(); |
| 11720 | } |
| 11721 | |
| 11722 | // Copy the text, with \n and \t replaced by <space> if appropriate. |
| 11723 | MOZ_ASSERT(aBufferOffset >= 0)do { static_assert( mozilla::detail::AssertionConditionType< decltype(aBufferOffset >= 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aBufferOffset >= 0))), 0) )) { do { } while (false); MOZ_ReportAssertionFailure("aBufferOffset >= 0" , "./../../../layout/generic/nsTextFrame.cpp", 11723); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aBufferOffset >= 0" ")"); do { MOZ_CrashSequence (__null, 11723); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 11724 | for (uint32_t i = 0; i < static_cast<uint32_t>(aBufferLen); ++i) { |
| 11725 | char16_t ch = aBuffer.CharAt(static_cast<uint32_t>(aBufferOffset) + i); |
| 11726 | if ((ch == '\n' && !aStyle->NewlineIsSignificant(aFrame)) || |
| 11727 | (ch == '\t' && !aStyle->TabIsSignificant())) { |
| 11728 | ch = ' '; |
| 11729 | } |
| 11730 | out[i] = ch; |
| 11731 | } |
| 11732 | |
| 11733 | if (!aStyle->mTextTransform.IsNone() || needsToMaskPassword || |
| 11734 | aStyle->mWebkitTextSecurity != StyleTextSecurity::None) { |
| 11735 | MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed)do { static_assert( mozilla::detail::AssertionConditionType< decltype(aTextRun->GetFlags2() & nsTextFrameUtils::Flags ::IsTransformed)>::isValid, "invalid assertion condition") ; if ((__builtin_expect(!!(!(!!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed" , "./../../../layout/generic/nsTextFrame.cpp", 11735); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed" ")"); do { MOZ_CrashSequence(__null, 11735); __attribute__(( nomerge)) ::abort(); } while (false); } } while (false); |
| 11736 | if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) { |
| 11737 | // Apply text-transform according to style in the transformed run. |
| 11738 | char16_t maskChar = |
| 11739 | needsToMaskPassword ? 0 : aStyle->TextSecurityMaskChar(); |
| 11740 | auto transformedTextRun = |
| 11741 | static_cast<const nsTransformedTextRun*>(aTextRun); |
| 11742 | nsAutoString convertedString; |
| 11743 | AutoTArray<bool, 50> charsToMergeArray; |
| 11744 | AutoTArray<bool, 50> deletedCharsArray; |
| 11745 | nsCaseTransformTextRunFactory::TransformString( |
| 11746 | fragString, convertedString, /* aGlobalTransform = */ Nothing(), |
| 11747 | maskChar, /* aCaseTransformsOnly = */ true, |
| 11748 | StaticPrefs::layout_css_text_transform_uppercase_eszett_enabled(), |
| 11749 | nullptr, charsToMergeArray, deletedCharsArray, transformedTextRun, |
| 11750 | aSkippedOffset); |
| 11751 | aOut.Append(convertedString); |
| 11752 | } else { |
| 11753 | // Should not happen (see assertion above), but as a fallback... |
| 11754 | aOut.Append(fragString); |
| 11755 | } |
| 11756 | } |
| 11757 | } |
| 11758 | |
| 11759 | static void LineStartsOrEndsAtHardLineBreak(nsTextFrame* aFrame, |
| 11760 | nsBlockFrame* aLineContainer, |
| 11761 | bool* aStartsAtHardBreak, |
| 11762 | bool* aEndsAtHardBreak) { |
| 11763 | bool foundValidLine; |
| 11764 | nsBlockInFlowLineIterator iter(aLineContainer, aFrame, &foundValidLine); |
| 11765 | if (!foundValidLine) { |
| 11766 | NS_ERROR("Invalid line!")do { NS_DebugBreak(NS_DEBUG_ASSERTION, "Invalid line!", "Error" , "./../../../layout/generic/nsTextFrame.cpp", 11766); MOZ_PretendNoReturn (); } while (0); |
| 11767 | *aStartsAtHardBreak = *aEndsAtHardBreak = true; |
| 11768 | return; |
| 11769 | } |
| 11770 | |
| 11771 | *aEndsAtHardBreak = !iter.GetLine()->IsLineWrapped(); |
| 11772 | if (iter.Prev()) { |
| 11773 | *aStartsAtHardBreak = !iter.GetLine()->IsLineWrapped(); |
| 11774 | } else { |
| 11775 | // Hit block boundary |
| 11776 | *aStartsAtHardBreak = true; |
| 11777 | } |
| 11778 | } |
| 11779 | |
| 11780 | bool nsTextFrame::AppendRenderedText(AppendRenderedTextState& aState, |
| 11781 | RenderedText& aResult) { |
| 11782 | if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { |
| 11783 | // We don't trust dirty frames, especially when computing rendered text. |
| 11784 | return false; |
| 11785 | } |
| 11786 | |
| 11787 | // Ensure the text run and grab the gfxSkipCharsIterator for it |
| 11788 | gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); |
| 11789 | if (!mTextRun) { |
| 11790 | return false; |
| 11791 | } |
| 11792 | gfxSkipCharsIterator tmpIter = iter; |
| 11793 | |
| 11794 | // Check if the frame starts/ends at a hard line break, to determine |
| 11795 | // whether whitespace should be trimmed. |
| 11796 | bool startsAtHardBreak, endsAtHardBreak; |
| 11797 | if (!HasAnyStateBits(TEXT_START_OF_LINE | TEXT_END_OF_LINE)) { |
| 11798 | startsAtHardBreak = endsAtHardBreak = false; |
| 11799 | } else if (nsBlockFrame* thisLc = do_QueryFrame(FindLineContainer())) { |
| 11800 | if (thisLc != aState.mLineContainer) { |
| 11801 | // Setup line cursor when needed. |
| 11802 | aState.mLineContainer = thisLc; |
| 11803 | aState.mLineContainer->SetupLineCursorForQuery(); |
| 11804 | } |
| 11805 | LineStartsOrEndsAtHardLineBreak(this, aState.mLineContainer, |
| 11806 | &startsAtHardBreak, &endsAtHardBreak); |
| 11807 | } else { |
| 11808 | // Weird situation where we have a line layout without a block. |
| 11809 | // No soft breaks occur in this situation. |
| 11810 | startsAtHardBreak = endsAtHardBreak = true; |
| 11811 | } |
| 11812 | |
| 11813 | // Whether we need to trim whitespaces after the text frame. |
| 11814 | // TrimmedOffsetFlags::Default will allow trimming; we set NoTrim* flags |
| 11815 | // in the cases where this should not occur. |
| 11816 | TrimmedOffsetFlags trimFlags = TrimmedOffsetFlags::Default; |
| 11817 | if (!IsAtEndOfLine() || |
| 11818 | aState.mTrimTrailingWhitespace != TrailingWhitespace::Trim || |
| 11819 | !endsAtHardBreak) { |
| 11820 | trimFlags |= TrimmedOffsetFlags::NoTrimAfter; |
| 11821 | } |
| 11822 | |
| 11823 | // Whether to trim whitespaces before the text frame. |
| 11824 | if (!startsAtHardBreak) { |
| 11825 | trimFlags |= TrimmedOffsetFlags::NoTrimBefore; |
| 11826 | } |
| 11827 | |
| 11828 | TrimmedOffsets trimmedOffsets = |
| 11829 | GetTrimmedOffsets(aState.mCharacterDataBuffer, trimFlags); |
| 11830 | bool trimmedSignificantNewline = |
| 11831 | (trimmedOffsets.GetEnd() < GetContentEnd() || |
| 11832 | (aState.mTrimTrailingWhitespace == TrailingWhitespace::Trim && |
| 11833 | StyleText()->mWhiteSpaceCollapse == |
| 11834 | StyleWhiteSpaceCollapse::PreserveBreaks)) && |
| 11835 | HasSignificantTerminalNewline(); |
| 11836 | uint32_t skippedToRenderedStringOffset = |
| 11837 | aState.mOffsetInRenderedString - |
| 11838 | tmpIter.ConvertOriginalToSkipped(trimmedOffsets.mStart); |
| 11839 | uint32_t nextOffsetInRenderedString = |
| 11840 | tmpIter.ConvertOriginalToSkipped(trimmedOffsets.GetEnd()) + |
| 11841 | (trimmedSignificantNewline ? 1 : 0) + skippedToRenderedStringOffset; |
| 11842 | |
| 11843 | if (aState.mOffsetType == TextOffsetType::OffsetsInRenderedText) { |
| 11844 | if (nextOffsetInRenderedString <= aState.mStartOffset) { |
| 11845 | aState.mOffsetInRenderedString = nextOffsetInRenderedString; |
| 11846 | return true; |
| 11847 | } |
| 11848 | if (!aState.mHaveOffsets) { |
| 11849 | aResult.mOffsetWithinNodeText = tmpIter.ConvertSkippedToOriginal( |
| 11850 | aState.mStartOffset - skippedToRenderedStringOffset); |
| 11851 | aResult.mOffsetWithinNodeRenderedText = aState.mStartOffset; |
| 11852 | aState.mHaveOffsets = true; |
| 11853 | } |
| 11854 | if (aState.mOffsetInRenderedString >= aState.mEndOffset) { |
| 11855 | return false; |
| 11856 | } |
| 11857 | } else { |
| 11858 | if (uint32_t(GetContentEnd()) <= aState.mStartOffset) { |
| 11859 | aState.mOffsetInRenderedString = nextOffsetInRenderedString; |
| 11860 | return true; |
| 11861 | } |
| 11862 | if (!aState.mHaveOffsets) { |
| 11863 | aResult.mOffsetWithinNodeText = aState.mStartOffset; |
| 11864 | // Skip trimmed space when computed the rendered text offset. |
| 11865 | int32_t clamped = |
| 11866 | std::max<int32_t>(aState.mStartOffset, trimmedOffsets.mStart); |
| 11867 | aResult.mOffsetWithinNodeRenderedText = |
| 11868 | tmpIter.ConvertOriginalToSkipped(clamped) + |
| 11869 | skippedToRenderedStringOffset; |
| 11870 | MOZ_ASSERT(aResult.mOffsetWithinNodeRenderedText >=do { static_assert( mozilla::detail::AssertionConditionType< decltype(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647 ))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647 )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)" " (" "Bad offset within rendered text" ")", "./../../../layout/generic/nsTextFrame.cpp" , 11873); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)" ") (" "Bad offset within rendered text" ")"); do { MOZ_CrashSequence (__null, 11873); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 11871 | aState.mOffsetInRenderedString &&do { static_assert( mozilla::detail::AssertionConditionType< decltype(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647 ))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647 )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)" " (" "Bad offset within rendered text" ")", "./../../../layout/generic/nsTextFrame.cpp" , 11873); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)" ") (" "Bad offset within rendered text" ")"); do { MOZ_CrashSequence (__null, 11873); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 11872 | aResult.mOffsetWithinNodeRenderedText <= INT32_MAX,do { static_assert( mozilla::detail::AssertionConditionType< decltype(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647 ))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647 )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)" " (" "Bad offset within rendered text" ")", "./../../../layout/generic/nsTextFrame.cpp" , 11873); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)" ") (" "Bad offset within rendered text" ")"); do { MOZ_CrashSequence (__null, 11873); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 11873 | "Bad offset within rendered text")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647 ))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647 )))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)" " (" "Bad offset within rendered text" ")", "./../../../layout/generic/nsTextFrame.cpp" , 11873); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aResult.mOffsetWithinNodeRenderedText >= aState.mOffsetInRenderedString && aResult.mOffsetWithinNodeRenderedText <= (2147483647)" ") (" "Bad offset within rendered text" ")"); do { MOZ_CrashSequence (__null, 11873); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 11874 | aState.mHaveOffsets = true; |
| 11875 | } |
| 11876 | if (uint32_t(mContentOffset) >= aState.mEndOffset) { |
| 11877 | return false; |
| 11878 | } |
| 11879 | } |
| 11880 | |
| 11881 | int32_t startOffset; |
| 11882 | int32_t endOffset; |
| 11883 | if (aState.mOffsetType == TextOffsetType::OffsetsInRenderedText) { |
| 11884 | startOffset = tmpIter.ConvertSkippedToOriginal( |
| 11885 | aState.mStartOffset - skippedToRenderedStringOffset); |
| 11886 | endOffset = tmpIter.ConvertSkippedToOriginal(aState.mEndOffset - |
| 11887 | skippedToRenderedStringOffset); |
| 11888 | } else { |
| 11889 | startOffset = aState.mStartOffset; |
| 11890 | endOffset = std::min<uint32_t>(INT32_MAX(2147483647), aState.mEndOffset); |
| 11891 | } |
| 11892 | |
| 11893 | // If startOffset and/or endOffset are inside of trimmedOffsets' range, |
| 11894 | // then clamp the edges of trimmedOffsets accordingly. |
| 11895 | int32_t origTrimmedOffsetsEnd = trimmedOffsets.GetEnd(); |
| 11896 | trimmedOffsets.mStart = |
| 11897 | std::max<uint32_t>(trimmedOffsets.mStart, startOffset); |
| 11898 | trimmedOffsets.mLength = |
| 11899 | std::min<uint32_t>(origTrimmedOffsetsEnd, endOffset) - |
| 11900 | trimmedOffsets.mStart; |
| 11901 | |
| 11902 | if (trimmedOffsets.mLength > 0) { |
| 11903 | const nsStyleText* textStyle = StyleText(); |
| 11904 | iter.SetOriginalOffset(trimmedOffsets.mStart); |
| 11905 | while (iter.GetOriginalOffset() < trimmedOffsets.GetEnd()) { |
| 11906 | int32_t runLength; |
| 11907 | bool isSkipped = iter.IsOriginalCharSkipped(&runLength); |
| 11908 | runLength = std::min(runLength, |
| 11909 | trimmedOffsets.GetEnd() - iter.GetOriginalOffset()); |
| 11910 | if (isSkipped) { |
| 11911 | MOZ_ASSERT(runLength >= 0)do { static_assert( mozilla::detail::AssertionConditionType< decltype(runLength >= 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(runLength >= 0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("runLength >= 0" , "./../../../layout/generic/nsTextFrame.cpp", 11911); AnnotateMozCrashReason ("MOZ_ASSERT" "(" "runLength >= 0" ")"); do { MOZ_CrashSequence (__null, 11911); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 11912 | for (uint32_t i = 0; i < static_cast<uint32_t>(runLength); ++i) { |
| 11913 | const char16_t ch = aState.mCharacterDataBuffer.CharAt( |
| 11914 | AssertedCast<uint32_t>(iter.GetOriginalOffset() + i)); |
| 11915 | if (ch == CH_SHY173) { |
| 11916 | // We should preserve soft hyphens. They can't be transformed. |
| 11917 | aResult.mString.Append(ch); |
| 11918 | } |
| 11919 | } |
| 11920 | } else { |
| 11921 | TransformChars(this, textStyle, mTextRun, iter.GetSkippedOffset(), |
| 11922 | aState.mCharacterDataBuffer, iter.GetOriginalOffset(), |
| 11923 | runLength, aResult.mString); |
| 11924 | } |
| 11925 | iter.AdvanceOriginal(runLength); |
| 11926 | } |
| 11927 | } |
| 11928 | |
| 11929 | if (trimmedSignificantNewline && GetContentEnd() <= endOffset) { |
| 11930 | // A significant newline was trimmed off (we must be |
| 11931 | // white-space:pre-line). Put it back. |
| 11932 | aResult.mString.Append('\n'); |
| 11933 | } |
| 11934 | aState.mOffsetInRenderedString = nextOffsetInRenderedString; |
| 11935 | |
| 11936 | return true; |
| 11937 | } |
| 11938 | |
| 11939 | nsIFrame::RenderedText nsTextFrame::GetRenderedText( |
| 11940 | uint32_t aStartOffset, uint32_t aEndOffset, TextOffsetType aOffsetType, |
| 11941 | TrailingWhitespace aTrimTrailingWhitespace) { |
| 11942 | MOZ_ASSERT(aStartOffset <= aEndOffset, "bogus offsets")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aStartOffset <= aEndOffset)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aStartOffset <= aEndOffset ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "aStartOffset <= aEndOffset" " (" "bogus offsets" ")", "./../../../layout/generic/nsTextFrame.cpp" , 11942); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aStartOffset <= aEndOffset" ") (" "bogus offsets" ")"); do { MOZ_CrashSequence(__null, 11942 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
| 11943 | MOZ_ASSERT(!GetPrevContinuation() ||do { static_assert( mozilla::detail::AssertionConditionType< decltype(!GetPrevContinuation() || (aOffsetType == TextOffsetType ::OffsetsInContentText && aStartOffset >= (uint32_t )GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd ()))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!GetPrevContinuation() || (aOffsetType == TextOffsetType ::OffsetsInContentText && aStartOffset >= (uint32_t )GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd ())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())" " (" "Must be called on first-in-flow, or content offsets must be " "given and be within this frame." ")", "./../../../layout/generic/nsTextFrame.cpp" , 11948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())" ") (" "Must be called on first-in-flow, or content offsets must be " "given and be within this frame." ")"); do { MOZ_CrashSequence (__null, 11948); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 11944 | (aOffsetType == TextOffsetType::OffsetsInContentText &&do { static_assert( mozilla::detail::AssertionConditionType< decltype(!GetPrevContinuation() || (aOffsetType == TextOffsetType ::OffsetsInContentText && aStartOffset >= (uint32_t )GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd ()))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!GetPrevContinuation() || (aOffsetType == TextOffsetType ::OffsetsInContentText && aStartOffset >= (uint32_t )GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd ())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())" " (" "Must be called on first-in-flow, or content offsets must be " "given and be within this frame." ")", "./../../../layout/generic/nsTextFrame.cpp" , 11948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())" ") (" "Must be called on first-in-flow, or content offsets must be " "given and be within this frame." ")"); do { MOZ_CrashSequence (__null, 11948); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 11945 | aStartOffset >= (uint32_t)GetContentOffset() &&do { static_assert( mozilla::detail::AssertionConditionType< decltype(!GetPrevContinuation() || (aOffsetType == TextOffsetType ::OffsetsInContentText && aStartOffset >= (uint32_t )GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd ()))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!GetPrevContinuation() || (aOffsetType == TextOffsetType ::OffsetsInContentText && aStartOffset >= (uint32_t )GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd ())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())" " (" "Must be called on first-in-flow, or content offsets must be " "given and be within this frame." ")", "./../../../layout/generic/nsTextFrame.cpp" , 11948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())" ") (" "Must be called on first-in-flow, or content offsets must be " "given and be within this frame." ")"); do { MOZ_CrashSequence (__null, 11948); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 11946 | aEndOffset <= (uint32_t)GetContentEnd()),do { static_assert( mozilla::detail::AssertionConditionType< decltype(!GetPrevContinuation() || (aOffsetType == TextOffsetType ::OffsetsInContentText && aStartOffset >= (uint32_t )GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd ()))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!GetPrevContinuation() || (aOffsetType == TextOffsetType ::OffsetsInContentText && aStartOffset >= (uint32_t )GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd ())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())" " (" "Must be called on first-in-flow, or content offsets must be " "given and be within this frame." ")", "./../../../layout/generic/nsTextFrame.cpp" , 11948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())" ") (" "Must be called on first-in-flow, or content offsets must be " "given and be within this frame." ")"); do { MOZ_CrashSequence (__null, 11948); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 11947 | "Must be called on first-in-flow, or content offsets must be "do { static_assert( mozilla::detail::AssertionConditionType< decltype(!GetPrevContinuation() || (aOffsetType == TextOffsetType ::OffsetsInContentText && aStartOffset >= (uint32_t )GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd ()))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!GetPrevContinuation() || (aOffsetType == TextOffsetType ::OffsetsInContentText && aStartOffset >= (uint32_t )GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd ())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())" " (" "Must be called on first-in-flow, or content offsets must be " "given and be within this frame." ")", "./../../../layout/generic/nsTextFrame.cpp" , 11948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())" ") (" "Must be called on first-in-flow, or content offsets must be " "given and be within this frame." ")"); do { MOZ_CrashSequence (__null, 11948); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
| 11948 | "given and be within this frame.")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!GetPrevContinuation() || (aOffsetType == TextOffsetType ::OffsetsInContentText && aStartOffset >= (uint32_t )GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd ()))>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(!GetPrevContinuation() || (aOffsetType == TextOffsetType ::OffsetsInContentText && aStartOffset >= (uint32_t )GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd ())))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())" " (" "Must be called on first-in-flow, or content offsets must be " "given and be within this frame." ")", "./../../../layout/generic/nsTextFrame.cpp" , 11948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!GetPrevContinuation() || (aOffsetType == TextOffsetType::OffsetsInContentText && aStartOffset >= (uint32_t)GetContentOffset() && aEndOffset <= (uint32_t)GetContentEnd())" ") (" "Must be called on first-in-flow, or content offsets must be " "given and be within this frame." ")"); do { MOZ_CrashSequence (__null, 11948); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
| 11949 | |
| 11950 | // The handling of offsets could be more efficient... |
| 11951 | RenderedText result; |
| 11952 | AppendRenderedTextState state{aStartOffset, aEndOffset, aOffsetType, |
| 11953 | aTrimTrailingWhitespace, CharacterDataBuffer()}; |
| 11954 | |
| 11955 | for (nsTextFrame* textFrame = this; textFrame; |
| 11956 | textFrame = textFrame->GetNextContinuation()) { |
| 11957 | if (!textFrame->AppendRenderedText(state, result)) { |
| 11958 | break; |
| 11959 | } |
| 11960 | } |
| 11961 | |
| 11962 | if (!state.mHaveOffsets) { |
| 11963 | result.mOffsetWithinNodeText = state.mCharacterDataBuffer.GetLength(); |
| 11964 | result.mOffsetWithinNodeRenderedText = state.mOffsetInRenderedString; |
| 11965 | } |
| 11966 | |
| 11967 | return result; |
| 11968 | } |
| 11969 | |
| 11970 | /* virtual */ |
| 11971 | bool nsTextFrame::IsEmpty() { |
| 11972 | NS_ASSERTION(do { if (!(!HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE ))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Invalid state", "!HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE)" , "./../../../layout/generic/nsTextFrame.cpp", 11974); MOZ_PretendNoReturn (); } } while (0) |
| 11973 | !HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE),do { if (!(!HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE ))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Invalid state", "!HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE)" , "./../../../layout/generic/nsTextFrame.cpp", 11974); MOZ_PretendNoReturn (); } } while (0) |
| 11974 | "Invalid state")do { if (!(!HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE ))) { NS_DebugBreak(NS_DEBUG_ASSERTION, "Invalid state", "!HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE)" , "./../../../layout/generic/nsTextFrame.cpp", 11974); MOZ_PretendNoReturn (); } } while (0); |
| 11975 | |
| 11976 | // XXXldb Should this check compatibility mode as well??? |
| 11977 | const nsStyleText* textStyle = StyleText(); |
| 11978 | if (textStyle->WhiteSpaceIsSignificant()) { |
| 11979 | // When WhiteSpaceIsSignificant styles are in effect, we only treat the |
| 11980 | // frame as empty if its content really is entirely *empty* (not just |
| 11981 | // whitespace). |
| 11982 | return !GetContentLength(); |
| 11983 | } |
| 11984 | |
| 11985 | if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE)) { |
| 11986 | return false; |
| 11987 | } |
| 11988 | |
| 11989 | if (HasAnyStateBits(TEXT_IS_ONLY_WHITESPACE)) { |
| 11990 | return true; |
| 11991 | } |
| 11992 | |
| 11993 | bool isEmpty = IsAllWhitespace(CharacterDataBuffer(), |
| 11994 | textStyle->mWhiteSpaceCollapse != |
| 11995 | StyleWhiteSpaceCollapse::PreserveBreaks); |
| 11996 | AddStateBits(isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE); |
| 11997 | return isEmpty; |
| 11998 | } |
| 11999 | |
| 12000 | #ifdef DEBUG_FRAME_DUMP1 |
| 12001 | // Translate the mapped content into a string that's printable |
| 12002 | void nsTextFrame::ToCString(nsCString& aBuf) const { |
| 12003 | // Get the frame's text content |
| 12004 | const auto& characterDataBuffer = CharacterDataBuffer(); |
| 12005 | |
| 12006 | const int32_t length = GetContentEnd() - mContentOffset; |
| 12007 | if (length <= 0) { |
| 12008 | // Negative lengths are possible during invalidation. |
| 12009 | return; |
| 12010 | } |
| 12011 | |
| 12012 | const uint32_t bufferLength = AssertedCast<uint32_t>(GetContentEnd()); |
| 12013 | uint32_t bufferOffset = AssertedCast<uint32_t>(GetContentOffset()); |
| 12014 | |
| 12015 | while (bufferOffset < bufferLength) { |
| 12016 | char16_t ch = characterDataBuffer.CharAt(bufferOffset++); |
| 12017 | if (ch == '\r') { |
| 12018 | aBuf.AppendLiteral("\\r"); |
| 12019 | } else if (ch == '\n') { |
| 12020 | aBuf.AppendLiteral("\\n"); |
| 12021 | } else if (ch == '\t') { |
| 12022 | aBuf.AppendLiteral("\\t"); |
| 12023 | } else if ((ch < ' ') || (ch >= 127)) { |
| 12024 | aBuf.Append(nsPrintfCString("\\u%04x", ch)); |
| 12025 | } else { |
| 12026 | aBuf.Append(ch); |
| 12027 | } |
| 12028 | } |
| 12029 | } |
| 12030 | |
| 12031 | nsresult nsTextFrame::GetFrameName(nsAString& aResult) const { |
| 12032 | MakeFrameName(u"Text"_ns, aResult); |
| 12033 | nsAutoCString tmp; |
| 12034 | ToCString(tmp); |
| 12035 | tmp.SetLength(std::min<size_t>(tmp.Length(), 50u)); |
| 12036 | aResult += u"\""_ns + NS_ConvertASCIItoUTF16(tmp) + u"\""_ns; |
| 12037 | return NS_OK; |
| 12038 | } |
| 12039 | |
| 12040 | void nsTextFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const { |
| 12041 | nsCString str; |
| 12042 | ListGeneric(str, aPrefix, aFlags); |
| 12043 | |
| 12044 | if (!aFlags.contains(ListFlag::OnlyListDeterministicInfo)) { |
| 12045 | str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun)); |
| 12046 | } |
| 12047 | |
| 12048 | // Output the first/last content offset and prev/next in flow info |
| 12049 | bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength(); |
| 12050 | str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(), |
| 12051 | isComplete ? 'T' : 'F'); |
| 12052 | |
| 12053 | if (IsSelected()) { |
| 12054 | str += " SELECTED"; |
| 12055 | } |
| 12056 | fprintf_stderr(out, "%s\n", str.get()); |
| 12057 | } |
| 12058 | |
| 12059 | void nsTextFrame::ListTextRuns(FILE* out, |
| 12060 | nsTHashSet<const void*>& aSeen) const { |
| 12061 | if (!mTextRun || aSeen.Contains(mTextRun)) { |
| 12062 | return; |
| 12063 | } |
| 12064 | aSeen.Insert(mTextRun); |
| 12065 | mTextRun->Dump(out); |
| 12066 | } |
| 12067 | #endif |
| 12068 | |
| 12069 | void nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd) { |
| 12070 | AddStateBits(NS_FRAME_IS_BIDI); |
| 12071 | if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) { |
| 12072 | mContent->RemoveProperty(nsGkAtoms::flowlength); |
| 12073 | mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY); |
| 12074 | } |
| 12075 | |
| 12076 | /* |
| 12077 | * After Bidi resolution we may need to reassign text runs. |
| 12078 | * This is called during bidi resolution from the block container, so we |
| 12079 | * shouldn't be holding a local reference to a textrun anywhere. |
| 12080 | */ |
| 12081 | ClearTextRuns(); |
| 12082 | |
| 12083 | nsTextFrame* prev = GetPrevContinuation(); |
| 12084 | if (prev) { |
| 12085 | // the bidi resolver can be very evil when columns/pages are involved. Don't |
| 12086 | // let it violate our invariants. |
| 12087 | int32_t prevOffset = prev->GetContentOffset(); |
| 12088 | aStart = std::max(aStart, prevOffset); |
| 12089 | aEnd = std::max(aEnd, prevOffset); |
| 12090 | prev->ClearTextRuns(); |
| 12091 | } |
| 12092 | |
| 12093 | mContentOffset = aStart; |
| 12094 | SetLength(aEnd - aStart, nullptr, 0); |
| 12095 | } |
| 12096 | |
| 12097 | /** |
| 12098 | * @return true if this text frame ends with a newline character. It should |
| 12099 | * return false if it is not a text frame. |
| 12100 | */ |
| 12101 | bool nsTextFrame::HasSignificantTerminalNewline() const { |
| 12102 | return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant(this); |
| 12103 | } |
| 12104 | |
| 12105 | bool nsTextFrame::IsAtEndOfLine() const { |
| 12106 | return HasAnyStateBits(TEXT_END_OF_LINE); |
| 12107 | } |
| 12108 | |
| 12109 | Maybe<nscoord> nsTextFrame::GetNaturalBaselineBOffset( |
| 12110 | WritingMode aWM, BaselineSharingGroup aBaselineGroup, |
| 12111 | BaselineExportContext) const { |
| 12112 | if (aBaselineGroup == BaselineSharingGroup::Last) { |
| 12113 | return Nothing{}; |
| 12114 | } |
| 12115 | |
| 12116 | if (!aWM.IsOrthogonalTo(GetWritingMode())) { |
| 12117 | if (aWM.IsCentralBaseline()) { |
| 12118 | return Some(GetLogicalUsedBorderAndPadding(aWM).BStart(aWM) + |
| 12119 | ContentBSize(aWM) / 2); |
| 12120 | } |
| 12121 | return Some(mAscent); |
| 12122 | } |
| 12123 | |
| 12124 | // When the text frame has a writing mode orthogonal to the desired |
| 12125 | // writing mode, return a baseline coincides its parent frame. |
| 12126 | nsIFrame* parent = GetParent(); |
| 12127 | nsPoint position = GetNormalPosition(); |
| 12128 | nscoord parentAscent = parent->GetLogicalBaseline(aWM); |
| 12129 | if (aWM.IsVerticalRL()) { |
| 12130 | nscoord parentDescent = parent->GetSize().width - parentAscent; |
| 12131 | nscoord descent = parentDescent - position.x; |
| 12132 | return Some(GetSize().width - descent); |
| 12133 | } |
| 12134 | return Some(parentAscent - (aWM.IsVertical() ? position.x : position.y)); |
| 12135 | } |
| 12136 | |
| 12137 | nscoord nsTextFrame::GetCaretBaseline() const { |
| 12138 | if (mAscent == 0 && HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS)) { |
| 12139 | nsBlockFrame* container = do_QueryFrame(FindLineContainer()); |
| 12140 | // TODO(emilio): Ideally we'd want to find out if only our line is empty, |
| 12141 | // but that's non-trivial to do, and realistically empty inlines and text |
| 12142 | // will get placed into a non-empty line unless all lines are empty, I |
| 12143 | // believe... |
| 12144 | if (container && container->LinesAreEmpty()) { |
| 12145 | return GetFontMetricsDerivedCaretBaseline(); |
| 12146 | } |
| 12147 | } |
| 12148 | return nsIFrame::GetCaretBaseline(); |
| 12149 | } |
| 12150 | |
| 12151 | bool nsTextFrame::HasAnyNoncollapsedCharacters() { |
| 12152 | gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); |
| 12153 | int32_t offset = GetContentOffset(), offsetEnd = GetContentEnd(); |
| 12154 | int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset); |
| 12155 | int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd); |
| 12156 | return skippedOffset != skippedOffsetEnd; |
| 12157 | } |
| 12158 | |
| 12159 | bool nsTextFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) { |
| 12160 | return ComputeCustomOverflowInternal(aOverflowAreas, true); |
| 12161 | } |
| 12162 | |
| 12163 | bool nsTextFrame::ComputeCustomOverflowInternal(OverflowAreas& aOverflowAreas, |
| 12164 | bool aIncludeShadows) { |
| 12165 | if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { |
| 12166 | return true; |
| 12167 | } |
| 12168 | |
| 12169 | nsIFrame* decorationsBlock; |
| 12170 | if (IsFloatingFirstLetterChild()) { |
| 12171 | decorationsBlock = GetParent(); |
| 12172 | } else { |
| 12173 | nsIFrame* f = this; |
| 12174 | for (;;) { |
| 12175 | nsBlockFrame* fBlock = do_QueryFrame(f); |
| 12176 | if (fBlock) { |
| 12177 | decorationsBlock = fBlock; |
| 12178 | break; |
| 12179 | } |
| 12180 | |
| 12181 | f = f->GetParent(); |
| 12182 | if (!f) { |
| 12183 | NS_ERROR("Couldn't find any block ancestor (for text decorations)")do { NS_DebugBreak(NS_DEBUG_ASSERTION, "Couldn't find any block ancestor (for text decorations)" , "Error", "./../../../layout/generic/nsTextFrame.cpp", 12183 ); MOZ_PretendNoReturn(); } while (0); |
| 12184 | return nsIFrame::ComputeCustomOverflow(aOverflowAreas); |
| 12185 | } |
| 12186 | } |
| 12187 | } |
| 12188 | |
| 12189 | aOverflowAreas = RecomputeOverflow(decorationsBlock, aIncludeShadows); |
| 12190 | return nsIFrame::ComputeCustomOverflow(aOverflowAreas); |
| 12191 | } |
| 12192 | |
| 12193 | NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(JustificationAssignmentProperty, int32_t)static const mozilla::FramePropertyDescriptor<mozilla::SmallValueHolder <int32_t> >* JustificationAssignmentProperty() { static const auto descriptor = mozilla::FramePropertyDescriptor< mozilla::SmallValueHolder<int32_t> >::NewWithoutDestructor (); return &descriptor; } |
| 12194 | |
| 12195 | void nsTextFrame::AssignJustificationGaps( |
| 12196 | const mozilla::JustificationAssignment& aAssign) { |
| 12197 | int32_t encoded = (aAssign.mGapsAtStart << 8) | aAssign.mGapsAtEnd; |
| 12198 | static_assert(sizeof(aAssign) == 1, |
| 12199 | "The encoding might be broken if JustificationAssignment " |
| 12200 | "is larger than 1 byte"); |
| 12201 | SetProperty(JustificationAssignmentProperty(), encoded); |
| 12202 | } |
| 12203 | |
| 12204 | mozilla::JustificationAssignment nsTextFrame::GetJustificationAssignment() |
| 12205 | const { |
| 12206 | int32_t encoded = GetProperty(JustificationAssignmentProperty()); |
| 12207 | mozilla::JustificationAssignment result; |
| 12208 | result.mGapsAtStart = encoded >> 8; |
| 12209 | result.mGapsAtEnd = encoded & 0xFF; |
| 12210 | return result; |
| 12211 | } |
| 12212 | |
| 12213 | uint32_t nsTextFrame::CountGraphemeClusters() const { |
| 12214 | const auto& characterDataBuffer = CharacterDataBuffer(); |
| 12215 | nsAutoString content; |
| 12216 | characterDataBuffer.AppendTo(content, |
| 12217 | AssertedCast<uint32_t>(GetContentOffset()), |
| 12218 | AssertedCast<uint32_t>(GetContentLength())); |
| 12219 | return unicode::CountGraphemeClusters(content); |
| 12220 | } |
| 12221 | |
| 12222 | bool nsTextFrame::HasNonSuppressedText() const { |
| 12223 | if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE | |
| 12224 | // If we haven't reflowed yet, or are currently doing so, |
| 12225 | // just return true because we can't be sure. |
| 12226 | NS_FRAME_FIRST_REFLOW | NS_FRAME_IN_REFLOW)) { |
| 12227 | return true; |
| 12228 | } |
| 12229 | |
| 12230 | if (!GetTextRun(nsTextFrame::eInflated)) { |
| 12231 | return false; |
| 12232 | } |
| 12233 | |
| 12234 | TrimmedOffsets offsets = |
| 12235 | GetTrimmedOffsets(CharacterDataBuffer(), TrimmedOffsetFlags::NoTrimAfter); |
| 12236 | return offsets.mLength != 0; |
| 12237 | } |