SVGTextFrame.cpp (190632B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 // Main header first: 8 #include "SVGTextFrame.h" 9 10 // Keep others in (case-insensitive) order: 11 #include <algorithm> 12 #include <cmath> 13 #include <limits> 14 15 #include "DOMSVGPoint.h" 16 #include "SVGAnimatedNumberList.h" 17 #include "SVGContentUtils.h" 18 #include "SVGContextPaint.h" 19 #include "SVGLengthList.h" 20 #include "SVGNumberList.h" 21 #include "SVGPaintServerFrame.h" 22 #include "gfx2DGlue.h" 23 #include "gfxContext.h" 24 #include "gfxFont.h" 25 #include "gfxSkipChars.h" 26 #include "gfxTypes.h" 27 #include "gfxUtils.h" 28 #include "mozilla/CaretAssociationHint.h" 29 #include "mozilla/DisplaySVGItem.h" 30 #include "mozilla/Likely.h" 31 #include "mozilla/PresShell.h" 32 #include "mozilla/SVGObserverUtils.h" 33 #include "mozilla/SVGOuterSVGFrame.h" 34 #include "mozilla/SVGUtils.h" 35 #include "mozilla/dom/DOMPointBinding.h" 36 #include "mozilla/dom/SVGGeometryElement.h" 37 #include "mozilla/dom/SVGRect.h" 38 #include "mozilla/dom/SVGTextContentElementBinding.h" 39 #include "mozilla/dom/SVGTextPathElement.h" 40 #include "mozilla/dom/Selection.h" 41 #include "mozilla/dom/Text.h" 42 #include "mozilla/gfx/2D.h" 43 #include "mozilla/gfx/PatternHelpers.h" 44 #include "nsBidiPresUtils.h" 45 #include "nsBlockFrame.h" 46 #include "nsCaret.h" 47 #include "nsContentUtils.h" 48 #include "nsFrameSelection.h" 49 #include "nsGkAtoms.h" 50 #include "nsLayoutUtils.h" 51 #include "nsStyleStructInlines.h" 52 #include "nsTArray.h" 53 #include "nsTextFrame.h" 54 55 using namespace mozilla::dom; 56 using namespace mozilla::dom::SVGTextContentElement_Binding; 57 using namespace mozilla::gfx; 58 using namespace mozilla::image; 59 60 namespace mozilla { 61 62 // ============================================================================ 63 // Utility functions 64 65 /** 66 * Using the specified gfxSkipCharsIterator, converts an offset and length 67 * in original char indexes to skipped char indexes. 68 * 69 * @param aIterator The gfxSkipCharsIterator to use for the conversion. 70 * @param aOriginalOffset The original offset. 71 * @param aOriginalLength The original length. 72 */ 73 static gfxTextRun::Range ConvertOriginalToSkipped( 74 gfxSkipCharsIterator& aIterator, uint32_t aOriginalOffset, 75 uint32_t aOriginalLength) { 76 uint32_t start = aIterator.ConvertOriginalToSkipped(aOriginalOffset); 77 aIterator.AdvanceOriginal(aOriginalLength); 78 return gfxTextRun::Range(start, aIterator.GetSkippedOffset()); 79 } 80 81 /** 82 * Converts an nsPoint from app units to user space units using the specified 83 * nsPresContext and returns it as a gfxPoint. 84 */ 85 static gfxPoint AppUnitsToGfxUnits(const nsPoint& aPoint, 86 const nsPresContext* aContext) { 87 return gfxPoint(aContext->AppUnitsToGfxUnits(aPoint.x), 88 aContext->AppUnitsToGfxUnits(aPoint.y)); 89 } 90 91 /** 92 * Converts a nsRect that is in app units to CSS pixels and returns it 93 * as a gfxRect. 94 */ 95 static gfxRect AppUnitsToFloatCSSPixels(const nsRect& aRect) { 96 return gfxRect(nsPresContext::AppUnitsToFloatCSSPixels(aRect.x), 97 nsPresContext::AppUnitsToFloatCSSPixels(aRect.y), 98 nsPresContext::AppUnitsToFloatCSSPixels(aRect.width), 99 nsPresContext::AppUnitsToFloatCSSPixels(aRect.height)); 100 } 101 102 /** 103 * Gets the measured ascent and descent of the text in the given nsTextFrame 104 * in app units. 105 * 106 * @param aFrame The text frame. 107 * @param aAscent The ascent in app units (output). 108 * @param aDescent The descent in app units (output). 109 */ 110 static void GetAscentAndDescentInAppUnits(nsTextFrame* aFrame, 111 gfxFloat& aAscent, 112 gfxFloat& aDescent) { 113 gfxSkipCharsIterator it = aFrame->EnsureTextRun(nsTextFrame::eInflated); 114 gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated); 115 116 gfxTextRun::Range range = ConvertOriginalToSkipped( 117 it, aFrame->GetContentOffset(), aFrame->GetContentLength()); 118 119 textRun->GetLineHeightMetrics(range, aAscent, aDescent); 120 } 121 122 /** 123 * Updates an interval by intersecting it with another interval. 124 * The intervals are specified using a start index and a length. 125 */ 126 static void IntersectInterval(uint32_t& aStart, uint32_t& aLength, 127 uint32_t aStartOther, uint32_t aLengthOther) { 128 uint32_t aEnd = aStart + aLength; 129 uint32_t aEndOther = aStartOther + aLengthOther; 130 131 if (aStartOther >= aEnd || aStart >= aEndOther) { 132 aLength = 0; 133 } else { 134 if (aStartOther >= aStart) { 135 aStart = aStartOther; 136 } 137 aLength = std::min(aEnd, aEndOther) - aStart; 138 } 139 } 140 141 /** 142 * Intersects an interval as IntersectInterval does but by taking 143 * the offset and length of the other interval from a 144 * nsTextFrame::TrimmedOffsets object. 145 */ 146 static void TrimOffsets(uint32_t& aStart, uint32_t& aLength, 147 const nsTextFrame::TrimmedOffsets& aTrimmedOffsets) { 148 IntersectInterval(aStart, aLength, aTrimmedOffsets.mStart, 149 aTrimmedOffsets.mLength); 150 } 151 152 /** 153 * Returns the closest ancestor-or-self node that is not an SVG <a> 154 * element. 155 */ 156 static nsIContent* GetFirstNonAAncestor(nsIContent* aContent) { 157 while (aContent && aContent->IsSVGElement(nsGkAtoms::a)) { 158 aContent = aContent->GetParent(); 159 } 160 return aContent; 161 } 162 163 /** 164 * Returns whether the given node is a text content element[1], taking into 165 * account whether it has a valid parent. 166 * 167 * For example, in: 168 * 169 * <svg xmlns="http://www.w3.org/2000/svg"> 170 * <text><a/><text/></text> 171 * <tspan/> 172 * </svg> 173 * 174 * true would be returned for the outer <text> element and the <a> element, 175 * and false for the inner <text> element (since a <text> is not allowed 176 * to be a child of another <text>) and the <tspan> element (because it 177 * must be inside a <text> subtree). 178 * 179 * [1] https://svgwg.org/svg2-draft/intro.html#TermTextContentElement 180 */ 181 static bool IsTextContentElement(nsIContent* aContent) { 182 if (aContent->IsSVGElement(nsGkAtoms::text)) { 183 nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent()); 184 return !parent || !IsTextContentElement(parent); 185 } 186 187 if (aContent->IsSVGElement(nsGkAtoms::textPath)) { 188 nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent()); 189 return parent && parent->IsSVGElement(nsGkAtoms::text); 190 } 191 192 return aContent->IsAnyOfSVGElements(nsGkAtoms::a, nsGkAtoms::tspan); 193 } 194 195 /** 196 * Returns whether the specified frame is an nsTextFrame that has some text 197 * content. 198 */ 199 static bool IsNonEmptyTextFrame(nsIFrame* aFrame) { 200 nsTextFrame* textFrame = do_QueryFrame(aFrame); 201 if (!textFrame) { 202 return false; 203 } 204 205 return textFrame->GetContentLength() != 0; 206 } 207 208 /** 209 * Takes an nsIFrame and if it is a text frame that has some text content, 210 * returns it as an nsTextFrame and its corresponding Text. 211 * 212 * @param aFrame The frame to look at. 213 * @param aTextFrame aFrame as an nsTextFrame (output). 214 * @param aTextNode The Text content of aFrame (output). 215 * @return true if aFrame is a non-empty text frame, false otherwise. 216 */ 217 static bool GetNonEmptyTextFrameAndNode(nsIFrame* aFrame, 218 nsTextFrame*& aTextFrame, 219 Text*& aTextNode) { 220 nsTextFrame* text = do_QueryFrame(aFrame); 221 bool isNonEmptyTextFrame = text && text->GetContentLength() != 0; 222 223 if (isNonEmptyTextFrame) { 224 nsIContent* content = text->GetContent(); 225 NS_ASSERTION(content && content->IsText(), 226 "unexpected content type for nsTextFrame"); 227 228 Text* node = content->AsText(); 229 MOZ_ASSERT(node->TextLength() != 0, 230 "frame's GetContentLength() should be 0 if the text node " 231 "has no content"); 232 233 aTextFrame = text; 234 aTextNode = node; 235 } 236 237 MOZ_ASSERT(IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame, 238 "our logic should agree with IsNonEmptyTextFrame"); 239 return isNonEmptyTextFrame; 240 } 241 242 /** 243 * Returns whether the specified atom is for one of the five 244 * glyph positioning attributes that can appear on SVG text 245 * elements -- x, y, dx, dy or rotate. 246 */ 247 static bool IsGlyphPositioningAttribute(nsAtom* aAttribute) { 248 return aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y || 249 aAttribute == nsGkAtoms::dx || aAttribute == nsGkAtoms::dy || 250 aAttribute == nsGkAtoms::rotate; 251 } 252 253 /** 254 * Returns the position in app units of a given baseline (using an 255 * SVG dominant-baseline property value) for a given nsTextFrame. 256 * 257 * @param aFrame The text frame to inspect. 258 * @param aTextRun The text run of aFrame. 259 * @param aDominantBaseline The dominant-baseline value to use. 260 */ 261 static nscoord GetBaselinePosition(nsTextFrame* aFrame, 262 const gfxTextRun* aTextRun, 263 StyleDominantBaseline aDominantBaseline, 264 float aFontSizeScaleFactor) { 265 WritingMode writingMode = aFrame->GetWritingMode(); 266 gfxFloat ascent, descent; 267 aTextRun->GetLineHeightMetrics(ascent, descent); 268 269 auto convertIfVerticalRL = [&](gfxFloat dominantBaseline) { 270 return writingMode.IsVerticalRL() ? ascent + descent - dominantBaseline 271 : dominantBaseline; 272 }; 273 274 switch (aDominantBaseline) { 275 case StyleDominantBaseline::Hanging: 276 return convertIfVerticalRL(ascent * 0.2); 277 case StyleDominantBaseline::TextBeforeEdge: 278 return convertIfVerticalRL(0); 279 280 case StyleDominantBaseline::Alphabetic: 281 return writingMode.IsVerticalRL() 282 ? ascent * 0.5 283 : aFrame->GetLogicalBaseline(writingMode); 284 285 case StyleDominantBaseline::Auto: 286 return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode)); 287 288 case StyleDominantBaseline::Middle: 289 return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode) - 290 SVGContentUtils::GetFontXHeight(aFrame) / 2.0 * 291 AppUnitsPerCSSPixel() * 292 aFontSizeScaleFactor); 293 294 case StyleDominantBaseline::TextAfterEdge: 295 case StyleDominantBaseline::Ideographic: 296 return writingMode.IsVerticalLR() ? 0 : ascent + descent; 297 298 case StyleDominantBaseline::Central: 299 return (ascent + descent) / 2.0; 300 case StyleDominantBaseline::Mathematical: 301 return convertIfVerticalRL(ascent / 2.0); 302 } 303 304 MOZ_ASSERT_UNREACHABLE("unexpected dominant-baseline value"); 305 return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode)); 306 } 307 308 /** 309 * Truncates an array to be at most the length of another array. 310 * 311 * @param aArrayToTruncate The array to truncate. 312 * @param aReferenceArray The array whose length will be used to truncate 313 * aArrayToTruncate to. 314 */ 315 template <typename T, typename U> 316 static void TruncateTo(nsTArray<T>& aArrayToTruncate, 317 const nsTArray<U>& aReferenceArray) { 318 uint32_t length = aReferenceArray.Length(); 319 if (aArrayToTruncate.Length() > length) { 320 aArrayToTruncate.TruncateLength(length); 321 } 322 } 323 324 /** 325 * Asserts that the anonymous block child of the SVGTextFrame has been 326 * reflowed (or does not exist). Returns null if the child has not been 327 * reflowed, and the frame otherwise. 328 * 329 * We check whether the kid has been reflowed and not the frame itself 330 * since we sometimes need to call this function during reflow, after the 331 * kid has been reflowed but before we have cleared the dirty bits on the 332 * frame itself. 333 */ 334 static SVGTextFrame* FrameIfAnonymousChildReflowed(SVGTextFrame* aFrame) { 335 MOZ_ASSERT(aFrame, "aFrame must not be null"); 336 nsIFrame* kid = aFrame->PrincipalChildList().FirstChild(); 337 if (kid->IsSubtreeDirty()) { 338 MOZ_ASSERT(false, "should have already reflowed the anonymous block child"); 339 return nullptr; 340 } 341 return aFrame; 342 } 343 344 // FIXME(emilio): SVG is a special-case where transforms affect layout. We don't 345 // want that to go outside the SVG stuff (and really we should aim to remove 346 // that). 347 static float GetContextScale(SVGTextFrame* aFrame) { 348 if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 349 // When we are non-display, we could be painted in different coordinate 350 // spaces, and we don't want to have to reflow for each of these. We just 351 // assume that the context scale is 1.0 for them all, so we don't get stuck 352 // with a font size scale factor based on whichever referencing frame 353 // happens to reflow first. 354 return 1.0f; 355 } 356 auto matrix = nsLayoutUtils::GetTransformToAncestor( 357 RelativeTo{aFrame}, RelativeTo{SVGUtils::GetOuterSVGFrame(aFrame)}); 358 Matrix transform2D; 359 if (!matrix.CanDraw2D(&transform2D)) { 360 return 1.0f; 361 } 362 auto scales = transform2D.ScaleFactors(); 363 return std::max(0.0f, std::max(scales.xScale, scales.yScale)); 364 } 365 366 // ============================================================================ 367 // Utility classes 368 369 // ---------------------------------------------------------------------------- 370 // TextRenderedRun 371 372 /** 373 * A run of text within a single nsTextFrame whose glyphs can all be painted 374 * with a single call to nsTextFrame::PaintText. A text rendered run can 375 * be created for a sequence of two or more consecutive glyphs as long as: 376 * 377 * - Only the first glyph has (or none of the glyphs have) been positioned 378 * with SVG text positioning attributes 379 * - All of the glyphs have zero rotation 380 * - The glyphs are not on a text path 381 * - The glyphs correspond to content within the one nsTextFrame 382 * 383 * A TextRenderedRunIterator produces TextRenderedRuns required for painting a 384 * whole SVGTextFrame. 385 */ 386 struct TextRenderedRun { 387 using Range = gfxTextRun::Range; 388 389 /** 390 * Constructs a TextRenderedRun that is uninitialized except for mFrame 391 * being null. 392 */ 393 TextRenderedRun() : mFrame(nullptr) {} 394 395 /** 396 * Constructs a TextRenderedRun with all of the information required to 397 * paint it. See the comments documenting the member variables below 398 * for descriptions of the arguments. 399 */ 400 TextRenderedRun(nsTextFrame* aFrame, SVGTextFrame* aSVGTextFrame, 401 const gfxPoint& aPosition, double aRotate, 402 float aFontSizeScaleFactor, nscoord aBaseline, 403 uint32_t aTextFrameContentOffset, 404 uint32_t aTextFrameContentLength, 405 uint32_t aTextElementCharIndex) 406 : mFrame(aFrame), 407 mRoot(aSVGTextFrame), 408 mPosition(aPosition), 409 mLengthAdjustScaleFactor(mRoot->mLengthAdjustScaleFactor), 410 mRotate(static_cast<float>(aRotate)), 411 mFontSizeScaleFactor(aFontSizeScaleFactor), 412 mBaseline(aBaseline), 413 mTextFrameContentOffset(aTextFrameContentOffset), 414 mTextFrameContentLength(aTextFrameContentLength), 415 mTextElementCharIndex(aTextElementCharIndex) {} 416 417 /** 418 * Returns the text run for the text frame that this rendered run is part of. 419 */ 420 gfxTextRun* GetTextRun() const { 421 mFrame->EnsureTextRun(nsTextFrame::eInflated); 422 return mFrame->GetTextRun(nsTextFrame::eInflated); 423 } 424 425 /** 426 * Return true if the logical inline direction is reversed compared to 427 * normal physical coordinates (i.e. if it is leftwards or upwards). 428 */ 429 bool IsInlineReversed() const { return GetTextRun()->IsInlineReversed(); } 430 431 /** 432 * Returns whether this rendered run is vertical. 433 */ 434 bool IsVertical() const { return GetTextRun()->IsVertical(); } 435 436 /** 437 * Returns the transform that converts from a <text> element's user space into 438 * the coordinate space that rendered runs can be painted directly in. 439 * 440 * The difference between this method and 441 * GetTransformFromRunUserSpaceToUserSpace is that when calling in to 442 * nsTextFrame::PaintText, it will already take into account any left clip 443 * edge (that is, it doesn't just apply a visual clip to the rendered text, it 444 * shifts the glyphs over so that they are painted with their left edge at the 445 * x coordinate passed in to it). Thus we need to account for this in our 446 * transform. 447 * 448 * 449 * Assume that we have: 450 * 451 * <text x="100" y="100" rotate="0 0 1 0 0 * 1">abcdef</text>. 452 * 453 * This would result in four text rendered runs: 454 * 455 * - one for "ab" 456 * - one for "c" 457 * - one for "de" 458 * - one for "f" 459 * 460 * Assume now that we are painting the third TextRenderedRun. It will have 461 * a left clip edge that is the sum of the advances of "abc", and it will 462 * have a right clip edge that is the advance of "f". In 463 * SVGTextFrame::PaintSVG(), we pass in nsPoint() (i.e., the origin) 464 * as the point at which to paint the text frame, and we pass in the 465 * clip edge values. The nsTextFrame will paint the substring of its 466 * text such that the top-left corner of the "d"'s glyph cell will be at 467 * (0, 0) in the current coordinate system. 468 * 469 * Thus, GetTransformFromUserSpaceForPainting must return a transform from 470 * whatever user space the <text> element is in to a coordinate space in 471 * device pixels (as that's what nsTextFrame works in) where the origin is at 472 * the same position as our user space mPositions[i].mPosition value for 473 * the "d" glyph, which will be (100 + userSpaceAdvance("abc"), 100). 474 * The translation required to do this (ignoring the scale to get from 475 * user space to device pixels, and ignoring the 476 * (100 + userSpaceAdvance("abc"), 100) translation) is: 477 * 478 * (-leftEdge, -baseline) 479 * 480 * where baseline is the distance between the baseline of the text and the top 481 * edge of the nsTextFrame. We translate by -leftEdge horizontally because 482 * the nsTextFrame will already shift the glyphs over by that amount and start 483 * painting glyphs at x = 0. We translate by -baseline vertically so that 484 * painting the top edges of the glyphs at y = 0 will result in their 485 * baselines being at our desired y position. 486 * 487 * 488 * Now for an example with RTL text. Assume our content is now 489 * <text x="100" y="100" rotate="0 0 1 0 0 1">WERBEH</text>. We'd have 490 * the following text rendered runs: 491 * 492 * - one for "EH" 493 * - one for "B" 494 * - one for "ER" 495 * - one for "W" 496 * 497 * Again, we are painting the third TextRenderedRun. The left clip edge 498 * is the advance of the "W" and the right clip edge is the sum of the 499 * advances of "BEH". Our translation to get the rendered "ER" glyphs 500 * in the right place this time is: 501 * 502 * (-frameWidth + rightEdge, -baseline) 503 * 504 * which is equivalent to: 505 * 506 * (-(leftEdge + advance("ER")), -baseline) 507 * 508 * The reason we have to shift left additionally by the width of the run 509 * of glyphs we are painting is that although the nsTextFrame is RTL, 510 * we still supply the top-left corner to paint the frame at when calling 511 * nsTextFrame::PaintText, even though our user space positions for each 512 * glyph in mPositions specifies the origin of each glyph, which for RTL 513 * glyphs is at the right edge of the glyph cell. 514 * 515 * 516 * For any other use of an nsTextFrame in the context of a particular run 517 * (such as hit testing, or getting its rectangle), 518 * GetTransformFromRunUserSpaceToUserSpace should be used. 519 * 520 * @param aContext The context to use for unit conversions. 521 */ 522 gfxMatrix GetTransformFromUserSpaceForPainting( 523 nsPresContext* aContext, const nscoord aVisIStartEdge, 524 const nscoord aVisIEndEdge) const; 525 526 /** 527 * Returns the transform that converts from "run user space" to a <text> 528 * element's user space. Run user space is a coordinate system that has the 529 * same size as the <text>'s user space but rotated and translated such that 530 * (0,0) is the top-left of the rectangle that bounds the text. 531 * 532 * @param aContext The context to use for unit conversions. 533 */ 534 gfxMatrix GetTransformFromRunUserSpaceToUserSpace( 535 nsPresContext* aContext) const; 536 537 /** 538 * Returns the transform that converts from "run user space" to float pixels 539 * relative to the nsTextFrame that this rendered run is a part of. 540 * 541 * @param aContext The context to use for unit conversions. 542 */ 543 gfxMatrix GetTransformFromRunUserSpaceToFrameUserSpace( 544 nsPresContext* aContext) const; 545 546 /** 547 * Flag values used for the aFlags arguments of GetRunUserSpaceRect, 548 * GetFrameUserSpaceRect and GetUserSpaceRect. 549 */ 550 enum { 551 // Includes the fill geometry of the text in the returned rectangle. 552 eIncludeFill = 1, 553 // Includes the stroke geometry of the text in the returned rectangle. 554 eIncludeStroke = 2, 555 // Don't include any horizontal glyph overflow in the returned rectangle. 556 eNoHorizontalOverflow = 4 557 }; 558 559 /** 560 * Returns a rectangle that bounds the fill and/or stroke of the rendered run 561 * in run user space. 562 * 563 * @param aFlags A combination of the flags above (eIncludeFill and 564 * eIncludeStroke) indicating what parts of the text to include in 565 * the rectangle. 566 */ 567 SVGBBox GetRunUserSpaceRect(uint32_t aFlags) const; 568 569 /** 570 * Returns a rectangle that covers the fill and/or stroke of the rendered run 571 * in "frame user space". 572 * 573 * Frame user space is a coordinate space of the same scale as the <text> 574 * element's user space, but with its rotation set to the rotation of 575 * the glyphs within this rendered run and its origin set to the position 576 * such that placing the nsTextFrame there would result in the glyphs in 577 * this rendered run being at their correct positions. 578 * 579 * For example, say we have <text x="100 150" y="100">ab</text>. Assume 580 * the advance of both the "a" and the "b" is 12 user units, and the 581 * ascent of the text is 8 user units and its descent is 6 user units, 582 * and that we are not measuing the stroke of the text, so that we stay 583 * entirely within the glyph cells. 584 * 585 * There will be two text rendered runs, one for "a" and one for "b". 586 * 587 * The frame user space for the "a" run will have its origin at 588 * (100, 100 - 8) in the <text> element's user space and will have its 589 * axes aligned with the user space (since there is no rotate="" or 590 * text path involve) and with its scale the same as the user space. 591 * The rect returned by this method will be (0, 0, 12, 14), since the "a" 592 * glyph is right at the left of the nsTextFrame. 593 * 594 * The frame user space for the "b" run will have its origin at 595 * (150 - 12, 100 - 8), and scale/rotation the same as above. The rect 596 * returned by this method will be (12, 0, 12, 14), since we are 597 * advance("a") horizontally in to the text frame. 598 * 599 * @param aContext The context to use for unit conversions. 600 * @param aFlags A combination of the flags above (eIncludeFill and 601 * eIncludeStroke) indicating what parts of the text to include in 602 * the rectangle. 603 */ 604 SVGBBox GetFrameUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const; 605 606 /** 607 * Returns a rectangle that covers the fill and/or stroke of the rendered run 608 * in the <text> element's user space. 609 * 610 * @param aContext The context to use for unit conversions. 611 * @param aFlags A combination of the flags above indicating what parts of 612 * the text to include in the rectangle. 613 * @param aAdditionalTransform An additional transform to apply to the 614 * frame user space rectangle before its bounds are transformed into 615 * user space. 616 */ 617 SVGBBox GetUserSpaceRect( 618 nsPresContext* aContext, uint32_t aFlags, 619 const gfxMatrix* aAdditionalTransform = nullptr) const; 620 621 /** 622 * Gets the app unit amounts to clip from the left and right edges of 623 * the nsTextFrame in order to paint just this rendered run. 624 * 625 * Note that if clip edge amounts land in the middle of a glyph, the 626 * glyph won't be painted at all. The clip edges are thus more of 627 * a selection mechanism for which glyphs will be painted, rather 628 * than a geometric clip. 629 */ 630 void GetClipEdges(nscoord& aVisIStartEdge, nscoord& aVisIEndEdge) const; 631 632 /** 633 * Returns the advance width of the whole rendered run. 634 */ 635 nscoord GetAdvanceWidth() const; 636 637 /** 638 * Returns the index of the character into this rendered run whose 639 * glyph cell contains the given point, or -1 if there is no such 640 * character. This does not hit test against any overflow. 641 * 642 * @param aContext The context to use for unit conversions. 643 * @param aPoint The point in the user space of the <text> element. 644 */ 645 int32_t GetCharNumAtPosition(nsPresContext* aContext, 646 const gfxPoint& aPoint) const; 647 648 /** 649 * The text frame that this rendered run lies within. 650 */ 651 nsTextFrame* mFrame; 652 653 /** 654 * The SVGTextFrame to which our text frame belongs. 655 */ 656 SVGTextFrame* mRoot; 657 658 /** 659 * The point in user space that the text is positioned at. 660 * 661 * For a horizontal run: 662 * The x coordinate is the left edge of a LTR run of text or the right edge of 663 * an RTL run. The y coordinate is the baseline of the text. 664 * For a vertical run: 665 * The x coordinate is the baseline of the text. 666 * The y coordinate is the top edge of a LTR run, or bottom of RTL. 667 */ 668 gfxPoint mPosition; 669 670 /** 671 * The horizontal scale factor to apply when painting glyphs to take 672 * into account textLength="". 673 */ 674 float mLengthAdjustScaleFactor; 675 676 /** 677 * The rotation in radians in the user coordinate system that the text has. 678 */ 679 float mRotate; 680 681 /** 682 * The scale factor that was used to transform the text run's original font 683 * size into a sane range for painting and measurement. 684 */ 685 double mFontSizeScaleFactor; 686 687 /** 688 * The baseline in app units of this text run. The measurement is from the 689 * top of the text frame. (From the left edge if vertical.) 690 */ 691 nscoord mBaseline; 692 693 /** 694 * The offset and length in mFrame's content Text that corresponds to 695 * this text rendered run. These are original char indexes. 696 */ 697 uint32_t mTextFrameContentOffset; 698 uint32_t mTextFrameContentLength; 699 700 /** 701 * The character index in the whole SVG <text> element that this text rendered 702 * run begins at. 703 */ 704 uint32_t mTextElementCharIndex; 705 }; 706 707 gfxMatrix TextRenderedRun::GetTransformFromUserSpaceForPainting( 708 nsPresContext* aContext, const nscoord aVisIStartEdge, 709 const nscoord aVisIEndEdge) const { 710 // We transform to device pixels positioned such that painting the text frame 711 // at (0,0) with aItem will result in the text being in the right place. 712 713 gfxMatrix m; 714 if (!mFrame) { 715 return m; 716 } 717 718 float cssPxPerDevPx = 719 nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); 720 721 // Glyph position in user space. 722 m.PreTranslate(mPosition / cssPxPerDevPx); 723 724 // Take into account any font size scaling and scaling due to textLength="". 725 m.PreScale(1.0 / mFontSizeScaleFactor, 1.0 / mFontSizeScaleFactor); 726 727 // Rotation due to rotate="" or a <textPath>. 728 m.PreRotate(mRotate); 729 730 // Scale for textLength="" and translate to get the text frame 731 // to the right place. 732 nsPoint t; 733 if (IsVertical()) { 734 m.PreScale(1.0, mLengthAdjustScaleFactor); 735 t = nsPoint(-mBaseline, IsInlineReversed() 736 ? -mFrame->GetRect().height + aVisIEndEdge 737 : -aVisIStartEdge); 738 } else { 739 m.PreScale(mLengthAdjustScaleFactor, 1.0); 740 t = nsPoint(IsInlineReversed() ? -mFrame->GetRect().width + aVisIEndEdge 741 : -aVisIStartEdge, 742 -mBaseline); 743 } 744 m.PreTranslate(AppUnitsToGfxUnits(t, aContext)); 745 746 return m; 747 } 748 749 gfxMatrix TextRenderedRun::GetTransformFromRunUserSpaceToUserSpace( 750 nsPresContext* aContext) const { 751 gfxMatrix m; 752 if (!mFrame) { 753 return m; 754 } 755 756 float cssPxPerDevPx = 757 nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); 758 759 nscoord start, end; 760 GetClipEdges(start, end); 761 762 // Glyph position in user space. 763 m.PreTranslate(mPosition); 764 765 // Rotation due to rotate="" or a <textPath>. 766 m.PreRotate(mRotate); 767 768 // Scale for textLength="" and translate to get the text frame 769 // to the right place. 770 771 nsPoint t; 772 if (IsVertical()) { 773 m.PreScale(1.0, mLengthAdjustScaleFactor); 774 t = nsPoint(-mBaseline, IsInlineReversed() 775 ? -mFrame->GetRect().height + start + end 776 : 0); 777 } else { 778 m.PreScale(mLengthAdjustScaleFactor, 1.0); 779 t = nsPoint(IsInlineReversed() ? -mFrame->GetRect().width + start + end : 0, 780 -mBaseline); 781 } 782 m.PreTranslate(AppUnitsToGfxUnits(t, aContext) * cssPxPerDevPx / 783 mFontSizeScaleFactor); 784 785 return m; 786 } 787 788 gfxMatrix TextRenderedRun::GetTransformFromRunUserSpaceToFrameUserSpace( 789 nsPresContext* aContext) const { 790 gfxMatrix m; 791 if (!mFrame) { 792 return m; 793 } 794 795 nscoord start, end; 796 GetClipEdges(start, end); 797 798 // Translate by the horizontal distance into the text frame this 799 // rendered run is. 800 gfxFloat appPerCssPx = AppUnitsPerCSSPixel(); 801 gfxPoint t = IsVertical() ? gfxPoint(0, start / appPerCssPx) 802 : gfxPoint(start / appPerCssPx, 0); 803 return m.PreTranslate(t); 804 } 805 806 SVGBBox TextRenderedRun::GetRunUserSpaceRect(uint32_t aFlags) const { 807 SVGBBox r; 808 if (!mFrame) { 809 return r; 810 } 811 812 // Determine the amount of overflow around frame's mRect. 813 // 814 // We need to call InkOverflowRectRelativeToSelf because this includes 815 // overflowing decorations, which the MeasureText call below does not. 816 nsRect self = mFrame->InkOverflowRectRelativeToSelf(); 817 nsRect rect = mFrame->GetRect(); 818 bool vertical = IsVertical(); 819 nsMargin inkOverflow( 820 vertical ? -self.x : -self.y, 821 vertical ? self.YMost() - rect.height : self.XMost() - rect.width, 822 vertical ? self.XMost() - rect.width : self.YMost() - rect.height, 823 vertical ? -self.y : -self.x); 824 825 gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); 826 gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); 827 828 // Get the content range for this rendered run. 829 Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, 830 mTextFrameContentLength); 831 if (range.Length() == 0) { 832 return r; 833 } 834 835 auto& provider = mRoot->PropertyProviderFor(mFrame); 836 837 // Measure that range. 838 gfxTextRun::Metrics metrics = textRun->MeasureText( 839 range, gfxFont::LOOSE_INK_EXTENTS, nullptr, &provider); 840 // Make sure it includes the font-box. 841 gfxRect fontBox(0, -metrics.mAscent, metrics.mAdvanceWidth, 842 metrics.mAscent + metrics.mDescent); 843 metrics.mBoundingBox.UnionRect(metrics.mBoundingBox, fontBox); 844 845 // Determine the rectangle that covers the rendered run's fill, 846 // taking into account the measured overflow due to decorations. 847 nscoord baseline = 848 NSToCoordRoundWithClamp(metrics.mBoundingBox.y + metrics.mAscent); 849 gfxFloat x, width; 850 if (aFlags & eNoHorizontalOverflow) { 851 x = 0.0; 852 width = textRun->GetAdvanceWidth(range, &provider); 853 if (width < 0.0) { 854 x = width; 855 width = -width; 856 } 857 } else { 858 x = metrics.mBoundingBox.x; 859 width = metrics.mBoundingBox.width; 860 } 861 nsRect fillInAppUnits(NSToCoordRoundWithClamp(x), baseline, 862 NSToCoordRoundWithClamp(width), 863 NSToCoordRoundWithClamp(metrics.mBoundingBox.height)); 864 fillInAppUnits.Inflate(inkOverflow); 865 if (textRun->IsVertical()) { 866 // Swap line-relative textMetrics dimensions to physical coordinates. 867 std::swap(fillInAppUnits.x, fillInAppUnits.y); 868 std::swap(fillInAppUnits.width, fillInAppUnits.height); 869 } 870 871 // Convert the app units rectangle to user units. 872 gfxRect fill = AppUnitsToFloatCSSPixels(fillInAppUnits); 873 874 // Scale the rectangle up due to any mFontSizeScaleFactor. 875 fill.Scale(1.0 / mFontSizeScaleFactor); 876 877 // Include the fill if requested. 878 if (aFlags & eIncludeFill) { 879 r = fill; 880 } 881 882 // Include the stroke if requested. 883 if ((aFlags & eIncludeStroke) && !fill.IsEmpty() && 884 SVGUtils::GetStrokeWidth(mFrame) > 0) { 885 r.UnionEdges( 886 SVGUtils::PathExtentsToMaxStrokeExtents(fill, mFrame, gfxMatrix())); 887 } 888 889 return r; 890 } 891 892 SVGBBox TextRenderedRun::GetFrameUserSpaceRect(nsPresContext* aContext, 893 uint32_t aFlags) const { 894 SVGBBox r = GetRunUserSpaceRect(aFlags); 895 if (r.IsEmpty()) { 896 return r; 897 } 898 gfxMatrix m = GetTransformFromRunUserSpaceToFrameUserSpace(aContext); 899 return m.TransformBounds(r.ToThebesRect()); 900 } 901 902 SVGBBox TextRenderedRun::GetUserSpaceRect( 903 nsPresContext* aContext, uint32_t aFlags, 904 const gfxMatrix* aAdditionalTransform) const { 905 SVGBBox r = GetRunUserSpaceRect(aFlags); 906 if (r.IsEmpty()) { 907 return r; 908 } 909 gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext); 910 if (aAdditionalTransform) { 911 m *= *aAdditionalTransform; 912 } 913 return m.TransformBounds(r.ToThebesRect()); 914 } 915 916 void TextRenderedRun::GetClipEdges(nscoord& aVisIStartEdge, 917 nscoord& aVisIEndEdge) const { 918 uint32_t contentLength = mFrame->GetContentLength(); 919 if (mTextFrameContentOffset == 0 && 920 mTextFrameContentLength == contentLength) { 921 // If the rendered run covers the entire content, we know we don't need 922 // to clip without having to measure anything. 923 aVisIStartEdge = 0; 924 aVisIEndEdge = 0; 925 return; 926 } 927 928 gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); 929 gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); 930 auto& provider = mRoot->PropertyProviderFor(mFrame); 931 932 // Get the covered content offset/length for this rendered run in skipped 933 // characters, since that is what GetAdvanceWidth expects. 934 Range runRange = ConvertOriginalToSkipped(it, mTextFrameContentOffset, 935 mTextFrameContentLength); 936 937 // Get the offset/length of the whole nsTextFrame. 938 uint32_t frameOffset = mFrame->GetContentOffset(); 939 uint32_t frameLength = mFrame->GetContentLength(); 940 941 // Trim the whole-nsTextFrame offset/length to remove any leading/trailing 942 // white space, as the nsTextFrame when painting does not include them when 943 // interpreting clip edges. 944 nsTextFrame::TrimmedOffsets trimmedOffsets = 945 mFrame->GetTrimmedOffsets(mFrame->CharacterDataBuffer()); 946 TrimOffsets(frameOffset, frameLength, trimmedOffsets); 947 948 // Convert the trimmed whole-nsTextFrame offset/length into skipped 949 // characters. 950 Range frameRange = ConvertOriginalToSkipped(it, frameOffset, frameLength); 951 952 // Get the advance of aRange, using the aCachedRange if available to 953 // accelerate textrun measurement. 954 auto MeasureUsingCache = [&](SVGTextFrame::CachedMeasuredRange& aCachedRange, 955 const Range& aRange) -> nscoord { 956 if (aRange.Intersects(aCachedRange.mRange)) { 957 // Figure out the deltas between the cached range and the new one at the 958 // start and end edges. 959 Range startDelta, endDelta; 960 int startSign = 0, endSign = 0; 961 if (aRange.start < aCachedRange.mRange.start) { 962 // This range extends the cached range at the start. 963 startSign = 1; 964 startDelta = Range(aRange.start, aCachedRange.mRange.start); 965 } else if (aRange.start > aCachedRange.mRange.start) { 966 // This range trims the cached range at the start. 967 startSign = -1; 968 startDelta = Range(aCachedRange.mRange.start, aRange.start); 969 } 970 if (aRange.end > aCachedRange.mRange.end) { 971 // This range extends the cached range at the end. 972 endSign = 1; 973 endDelta = Range(aCachedRange.mRange.end, aRange.end); 974 } else if (aRange.end < aCachedRange.mRange.end) { 975 // This range trims the cached range at the end. 976 endSign = -1; 977 endDelta = Range(aRange.end, aCachedRange.mRange.end); 978 } 979 // If the total of the deltas is less than the length of aRange, 980 // it will be cheaper to measure them and adjust the cached advance 981 // instead of measuring the whole of aRange. 982 if (startDelta.Length() + endDelta.Length() < aRange.Length()) { 983 if (startSign) { 984 aCachedRange.mAdvance += 985 startSign * textRun->GetAdvanceWidth(startDelta, &provider); 986 } 987 if (endSign) { 988 aCachedRange.mAdvance += 989 endSign * textRun->GetAdvanceWidth(endDelta, &provider); 990 } 991 } else { 992 aCachedRange.mAdvance = textRun->GetAdvanceWidth(aRange, &provider); 993 } 994 } else { 995 // Just measure the range, and cache the result. 996 aCachedRange.mAdvance = textRun->GetAdvanceWidth(aRange, &provider); 997 } 998 aCachedRange.mRange = aRange; 999 return aCachedRange.mAdvance; 1000 }; 1001 1002 mRoot->SetCurrentFrameForCaching(mFrame); 1003 nscoord startEdge = 1004 MeasureUsingCache(mRoot->CachedRange(SVGTextFrame::WhichRange::Before), 1005 Range(frameRange.start, runRange.start)); 1006 nscoord endEdge = 1007 MeasureUsingCache(mRoot->CachedRange(SVGTextFrame::WhichRange::After), 1008 Range(runRange.end, frameRange.end)); 1009 1010 if (textRun->IsInlineReversed()) { 1011 aVisIStartEdge = endEdge; 1012 aVisIEndEdge = startEdge; 1013 } else { 1014 aVisIStartEdge = startEdge; 1015 aVisIEndEdge = endEdge; 1016 } 1017 } 1018 1019 nscoord TextRenderedRun::GetAdvanceWidth() const { 1020 gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); 1021 gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); 1022 auto& provider = mRoot->PropertyProviderFor(mFrame); 1023 1024 Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, 1025 mTextFrameContentLength); 1026 1027 return textRun->GetAdvanceWidth(range, &provider); 1028 } 1029 1030 int32_t TextRenderedRun::GetCharNumAtPosition(nsPresContext* aContext, 1031 const gfxPoint& aPoint) const { 1032 if (mTextFrameContentLength == 0) { 1033 return -1; 1034 } 1035 1036 float cssPxPerDevPx = 1037 nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); 1038 1039 // Convert the point from user space into run user space, and take 1040 // into account any mFontSizeScaleFactor. 1041 gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext); 1042 if (!m.Invert()) { 1043 return -1; 1044 } 1045 gfxPoint p = m.TransformPoint(aPoint) / cssPxPerDevPx * mFontSizeScaleFactor; 1046 1047 // First check that the point lies vertically between the top and bottom 1048 // edges of the text. 1049 gfxFloat ascent, descent; 1050 GetAscentAndDescentInAppUnits(mFrame, ascent, descent); 1051 1052 WritingMode writingMode = mFrame->GetWritingMode(); 1053 if (writingMode.IsVertical()) { 1054 gfxFloat leftEdge = mFrame->GetLogicalBaseline(writingMode) - 1055 (writingMode.IsVerticalRL() ? ascent : descent); 1056 gfxFloat rightEdge = leftEdge + ascent + descent; 1057 if (p.x < aContext->AppUnitsToGfxUnits(leftEdge) || 1058 p.x > aContext->AppUnitsToGfxUnits(rightEdge)) { 1059 return -1; 1060 } 1061 } else { 1062 gfxFloat topEdge = mFrame->GetLogicalBaseline(writingMode) - ascent; 1063 gfxFloat bottomEdge = topEdge + ascent + descent; 1064 if (p.y < aContext->AppUnitsToGfxUnits(topEdge) || 1065 p.y > aContext->AppUnitsToGfxUnits(bottomEdge)) { 1066 return -1; 1067 } 1068 } 1069 1070 gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); 1071 gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); 1072 auto& provider = mRoot->PropertyProviderFor(mFrame); 1073 1074 // Next check that the point lies horizontally within the left and right 1075 // edges of the text. 1076 Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, 1077 mTextFrameContentLength); 1078 gfxFloat runAdvance = 1079 aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(range, &provider)); 1080 1081 gfxFloat pos = writingMode.IsVertical() ? p.y : p.x; 1082 if (pos < 0 || pos >= runAdvance) { 1083 return -1; 1084 } 1085 1086 // Finally, measure progressively smaller portions of the rendered run to 1087 // find which glyph it lies within. This will need to change once we 1088 // support letter-spacing and word-spacing. 1089 bool ir = textRun->IsInlineReversed(); 1090 for (int32_t i = mTextFrameContentLength - 1; i >= 0; i--) { 1091 range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, i); 1092 gfxFloat advance = aContext->AppUnitsToGfxUnits( 1093 textRun->GetAdvanceWidth(range, &provider)); 1094 if ((ir && pos < runAdvance - advance) || (!ir && pos >= advance)) { 1095 return i; 1096 } 1097 } 1098 return -1; 1099 } 1100 1101 // ---------------------------------------------------------------------------- 1102 // TextNodeIterator 1103 1104 enum SubtreePosition { eBeforeSubtree, eWithinSubtree, eAfterSubtree }; 1105 1106 /** 1107 * An iterator class for Text that are descendants of a given node, the 1108 * root. Nodes are iterated in document order. An optional subtree can be 1109 * specified, in which case the iterator will track whether the current state of 1110 * the traversal over the tree is within that subtree or is past that subtree. 1111 */ 1112 class TextNodeIterator { 1113 public: 1114 /** 1115 * Constructs a TextNodeIterator with the specified root node and optional 1116 * subtree. 1117 */ 1118 explicit TextNodeIterator(nsIContent* aRoot, nsIContent* aSubtree = nullptr) 1119 : mRoot(aRoot), 1120 mSubtree(aSubtree == aRoot ? nullptr : aSubtree), 1121 mCurrent(aRoot), 1122 mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) { 1123 NS_ASSERTION(aRoot, "expected non-null root"); 1124 if (!aRoot->IsText()) { 1125 GetNext(); 1126 } 1127 } 1128 1129 /** 1130 * Returns the current Text, or null if the iterator has finished. 1131 */ 1132 Text* GetCurrent() const { return mCurrent ? mCurrent->AsText() : nullptr; } 1133 1134 /** 1135 * Advances to the next Text and returns it, or null if the end of 1136 * iteration has been reached. 1137 */ 1138 Text* GetNext(); 1139 1140 /** 1141 * Returns whether the iterator is currently within the subtree rooted 1142 * at mSubtree. Returns true if we are not tracking a subtree (we consider 1143 * that we're always within the subtree). 1144 */ 1145 bool IsWithinSubtree() const { return mSubtreePosition == eWithinSubtree; } 1146 1147 /** 1148 * Returns whether the iterator is past the subtree rooted at mSubtree. 1149 * Returns false if we are not tracking a subtree. 1150 */ 1151 bool IsAfterSubtree() const { return mSubtreePosition == eAfterSubtree; } 1152 1153 private: 1154 /** 1155 * The root under which all Text will be iterated over. 1156 */ 1157 nsIContent* const mRoot; 1158 1159 /** 1160 * The node rooting the subtree to track. 1161 */ 1162 nsIContent* const mSubtree; 1163 1164 /** 1165 * The current node during iteration. 1166 */ 1167 nsIContent* mCurrent; 1168 1169 /** 1170 * The current iterator position relative to mSubtree. 1171 */ 1172 SubtreePosition mSubtreePosition; 1173 }; 1174 1175 Text* TextNodeIterator::GetNext() { 1176 // Starting from mCurrent, we do a non-recursive traversal to the next 1177 // Text beneath mRoot, updating mSubtreePosition appropriately if we 1178 // encounter mSubtree. 1179 if (mCurrent) { 1180 do { 1181 nsIContent* next = 1182 IsTextContentElement(mCurrent) ? mCurrent->GetFirstChild() : nullptr; 1183 if (next) { 1184 mCurrent = next; 1185 if (mCurrent == mSubtree) { 1186 mSubtreePosition = eWithinSubtree; 1187 } 1188 } else { 1189 for (;;) { 1190 if (mCurrent == mRoot) { 1191 mCurrent = nullptr; 1192 break; 1193 } 1194 if (mCurrent == mSubtree) { 1195 mSubtreePosition = eAfterSubtree; 1196 } 1197 next = mCurrent->GetNextSibling(); 1198 if (next) { 1199 mCurrent = next; 1200 if (mCurrent == mSubtree) { 1201 mSubtreePosition = eWithinSubtree; 1202 } 1203 break; 1204 } 1205 if (mCurrent == mSubtree) { 1206 mSubtreePosition = eAfterSubtree; 1207 } 1208 mCurrent = mCurrent->GetParent(); 1209 } 1210 } 1211 } while (mCurrent && !mCurrent->IsText()); 1212 } 1213 1214 return mCurrent ? mCurrent->AsText() : nullptr; 1215 } 1216 1217 // ---------------------------------------------------------------------------- 1218 // TextNodeCorrespondenceRecorder 1219 1220 /** 1221 * TextNodeCorrespondence is used as the value of a frame property that 1222 * is stored on all its descendant nsTextFrames. It stores the number of DOM 1223 * characters between it and the previous nsTextFrame that did not have an 1224 * nsTextFrame created for them, due to either not being in a correctly 1225 * parented text content element, or because they were display:none. 1226 * These are called "undisplayed characters". 1227 * 1228 * See also TextNodeCorrespondenceRecorder below, which is what sets the 1229 * frame property. 1230 */ 1231 struct TextNodeCorrespondence { 1232 explicit TextNodeCorrespondence(uint32_t aUndisplayedCharacters) 1233 : mUndisplayedCharacters(aUndisplayedCharacters) {} 1234 1235 uint32_t mUndisplayedCharacters; 1236 }; 1237 1238 NS_DECLARE_FRAME_PROPERTY_DELETABLE(TextNodeCorrespondenceProperty, 1239 TextNodeCorrespondence) 1240 1241 /** 1242 * Returns the number of undisplayed characters before the specified 1243 * nsTextFrame. 1244 */ 1245 static uint32_t GetUndisplayedCharactersBeforeFrame(nsTextFrame* aFrame) { 1246 void* value = aFrame->GetProperty(TextNodeCorrespondenceProperty()); 1247 TextNodeCorrespondence* correspondence = 1248 static_cast<TextNodeCorrespondence*>(value); 1249 if (!correspondence) { 1250 // FIXME bug 903785 1251 NS_ERROR( 1252 "expected a TextNodeCorrespondenceProperty on nsTextFrame " 1253 "used for SVG text"); 1254 return 0; 1255 } 1256 return correspondence->mUndisplayedCharacters; 1257 } 1258 1259 /** 1260 * Traverses the nsTextFrames for an SVGTextFrame and records a 1261 * TextNodeCorrespondenceProperty on each for the number of undisplayed DOM 1262 * characters between each frame. This is done by iterating simultaneously 1263 * over the Text and nsTextFrames and noting when Text (or 1264 * parts of them) are skipped when finding the next nsTextFrame. 1265 */ 1266 class TextNodeCorrespondenceRecorder { 1267 public: 1268 /** 1269 * Entry point for the TextNodeCorrespondenceProperty recording. 1270 */ 1271 static void RecordCorrespondence(SVGTextFrame* aRoot); 1272 1273 private: 1274 explicit TextNodeCorrespondenceRecorder(SVGTextFrame* aRoot) 1275 : mNodeIterator(aRoot->GetContent()), 1276 mPreviousNode(nullptr), 1277 mNodeCharIndex(0) {} 1278 1279 void Record(SVGTextFrame* aRoot); 1280 void TraverseAndRecord(nsIFrame* aFrame); 1281 1282 /** 1283 * Returns the next non-empty Text. 1284 */ 1285 Text* NextNode(); 1286 1287 /** 1288 * The iterator over the Text that we use as we simultaneously 1289 * iterate over the nsTextFrames. 1290 */ 1291 TextNodeIterator mNodeIterator; 1292 1293 /** 1294 * The previous Text we iterated over. 1295 */ 1296 Text* mPreviousNode; 1297 1298 /** 1299 * The index into the current Text's character content. 1300 */ 1301 uint32_t mNodeCharIndex; 1302 }; 1303 1304 /* static */ 1305 void TextNodeCorrespondenceRecorder::RecordCorrespondence(SVGTextFrame* aRoot) { 1306 if (aRoot->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY)) { 1307 // Resolve bidi so that continuation frames are created if necessary: 1308 aRoot->MaybeResolveBidiForAnonymousBlockChild(); 1309 TextNodeCorrespondenceRecorder recorder(aRoot); 1310 recorder.Record(aRoot); 1311 aRoot->RemoveStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY); 1312 } 1313 } 1314 1315 void TextNodeCorrespondenceRecorder::Record(SVGTextFrame* aRoot) { 1316 if (!mNodeIterator.GetCurrent()) { 1317 // If there are no Text nodes then there is nothing to do. 1318 return; 1319 } 1320 1321 // Traverse over all the nsTextFrames and record the number of undisplayed 1322 // characters. 1323 TraverseAndRecord(aRoot); 1324 1325 // Find how many undisplayed characters there are after the final nsTextFrame. 1326 uint32_t undisplayed = 0; 1327 if (mNodeIterator.GetCurrent()) { 1328 if (mPreviousNode && mPreviousNode->TextLength() != mNodeCharIndex) { 1329 // The last nsTextFrame ended part way through a Text node. The 1330 // remaining characters count as undisplayed. 1331 NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(), 1332 "incorrect tracking of undisplayed characters in " 1333 "text nodes"); 1334 undisplayed += mPreviousNode->TextLength() - mNodeCharIndex; 1335 } 1336 // All the remaining Text that we iterate must also be undisplayed. 1337 for (Text* textNode = mNodeIterator.GetCurrent(); textNode; 1338 textNode = NextNode()) { 1339 undisplayed += textNode->TextLength(); 1340 } 1341 } 1342 1343 // Record the trailing number of undisplayed characters on the 1344 // SVGTextFrame. 1345 aRoot->mTrailingUndisplayedCharacters = undisplayed; 1346 } 1347 1348 Text* TextNodeCorrespondenceRecorder::NextNode() { 1349 mPreviousNode = mNodeIterator.GetCurrent(); 1350 Text* next; 1351 do { 1352 next = mNodeIterator.GetNext(); 1353 } while (next && next->TextLength() == 0); 1354 return next; 1355 } 1356 1357 void TextNodeCorrespondenceRecorder::TraverseAndRecord(nsIFrame* aFrame) { 1358 // Recursively iterate over the frame tree, for frames that correspond 1359 // to text content elements. 1360 if (IsTextContentElement(aFrame->GetContent())) { 1361 for (nsIFrame* f : aFrame->PrincipalChildList()) { 1362 TraverseAndRecord(f); 1363 } 1364 return; 1365 } 1366 1367 nsTextFrame* frame; // The current text frame. 1368 Text* node; // The text node for the current text frame. 1369 if (!GetNonEmptyTextFrameAndNode(aFrame, frame, node)) { 1370 // If this isn't an nsTextFrame, or is empty, nothing to do. 1371 return; 1372 } 1373 1374 NS_ASSERTION(frame->GetContentOffset() >= 0, 1375 "don't know how to handle negative content indexes"); 1376 1377 uint32_t undisplayed = 0; 1378 if (!mPreviousNode) { 1379 // Must be the very first text frame. 1380 NS_ASSERTION(mNodeCharIndex == 0, 1381 "incorrect tracking of undisplayed " 1382 "characters in text nodes"); 1383 if (!mNodeIterator.GetCurrent()) { 1384 MOZ_ASSERT_UNREACHABLE( 1385 "incorrect tracking of correspondence between " 1386 "text frames and text nodes"); 1387 } else { 1388 // Each whole Text we find before we get to the text node for the 1389 // first text frame must be undisplayed. 1390 while (mNodeIterator.GetCurrent() != node) { 1391 undisplayed += mNodeIterator.GetCurrent()->TextLength(); 1392 NextNode(); 1393 } 1394 // If the first text frame starts at a non-zero content offset, then those 1395 // earlier characters are also undisplayed. 1396 undisplayed += frame->GetContentOffset(); 1397 NextNode(); 1398 } 1399 } else if (mPreviousNode == node) { 1400 // Same text node as last time. 1401 if (static_cast<uint32_t>(frame->GetContentOffset()) != mNodeCharIndex) { 1402 // We have some characters in the middle of the text node 1403 // that are undisplayed. 1404 NS_ASSERTION( 1405 mNodeCharIndex < static_cast<uint32_t>(frame->GetContentOffset()), 1406 "incorrect tracking of undisplayed characters in " 1407 "text nodes"); 1408 undisplayed = frame->GetContentOffset() - mNodeCharIndex; 1409 } 1410 } else { 1411 // Different text node from last time. 1412 if (mPreviousNode->TextLength() != mNodeCharIndex) { 1413 NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(), 1414 "incorrect tracking of undisplayed characters in " 1415 "text nodes"); 1416 // Any trailing characters at the end of the previous Text are 1417 // undisplayed. 1418 undisplayed = mPreviousNode->TextLength() - mNodeCharIndex; 1419 } 1420 // Each whole Text we find before we get to the text node for 1421 // the current text frame must be undisplayed. 1422 while (mNodeIterator.GetCurrent() && mNodeIterator.GetCurrent() != node) { 1423 undisplayed += mNodeIterator.GetCurrent()->TextLength(); 1424 NextNode(); 1425 } 1426 // If the current text frame starts at a non-zero content offset, then those 1427 // earlier characters are also undisplayed. 1428 undisplayed += frame->GetContentOffset(); 1429 NextNode(); 1430 } 1431 1432 // Set the frame property. 1433 frame->SetProperty(TextNodeCorrespondenceProperty(), 1434 new TextNodeCorrespondence(undisplayed)); 1435 1436 // Remember how far into the current Text we are. 1437 mNodeCharIndex = frame->GetContentEnd(); 1438 } 1439 1440 // ---------------------------------------------------------------------------- 1441 // TextFrameIterator 1442 1443 /** 1444 * An iterator class for nsTextFrames that are descendants of an 1445 * SVGTextFrame. The iterator can optionally track whether the 1446 * current nsTextFrame is for a descendant of, or past, a given subtree 1447 * content node or frame. (This functionality is used for example by the SVG 1448 * DOM text methods to get only the nsTextFrames for a particular <tspan>.) 1449 * 1450 * TextFrameIterator also tracks and exposes other information about the 1451 * current nsTextFrame: 1452 * 1453 * * how many undisplayed characters came just before it 1454 * * its position (in app units) relative to the SVGTextFrame's anonymous 1455 * block frame 1456 * * what nsInlineFrame corresponding to a <textPath> element it is a 1457 * descendant of 1458 * * what computed dominant-baseline value applies to it 1459 * 1460 * Note that any text frames that are empty -- whose ContentLength() is 0 -- 1461 * will be skipped over. 1462 */ 1463 class MOZ_STACK_CLASS TextFrameIterator { 1464 public: 1465 /** 1466 * Constructs a TextFrameIterator for the specified SVGTextFrame 1467 * with an optional frame subtree to restrict iterated text frames to. 1468 */ 1469 explicit TextFrameIterator(SVGTextFrame* aRoot, 1470 const nsIFrame* aSubtree = nullptr) 1471 : mRootFrame(aRoot), 1472 mSubtree(aSubtree), 1473 mCurrentFrame(aRoot), 1474 mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) { 1475 Init(); 1476 } 1477 1478 /** 1479 * Constructs a TextFrameIterator for the specified SVGTextFrame 1480 * with an optional frame content subtree to restrict iterated text frames to. 1481 */ 1482 TextFrameIterator(SVGTextFrame* aRoot, nsIContent* aSubtree) 1483 : mRootFrame(aRoot), 1484 mSubtree(aRoot && aSubtree && aSubtree != aRoot->GetContent() 1485 ? aSubtree->GetPrimaryFrame() 1486 : nullptr), 1487 mCurrentFrame(aRoot), 1488 mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) { 1489 Init(); 1490 } 1491 1492 /** 1493 * Returns the root SVGTextFrame this TextFrameIterator is iterating over. 1494 * (May be nullptr, if nullptr was passed to our constructor.) 1495 */ 1496 SVGTextFrame* GetRoot() const { return mRootFrame; } 1497 1498 /** 1499 * Returns the current nsTextFrame, or null if the iterator has finished. 1500 */ 1501 nsTextFrame* GetCurrent() const { return do_QueryFrame(mCurrentFrame); } 1502 1503 /** 1504 * Returns the number of undisplayed characters in the DOM just before the 1505 * current frame. 1506 */ 1507 uint32_t UndisplayedCharacters() const; 1508 1509 /** 1510 * Returns the current frame's position, in app units, relative to the 1511 * root SVGTextFrame's anonymous block frame. 1512 */ 1513 nsPoint Position() const { return mCurrentPosition; } 1514 1515 /** 1516 * Advances to the next nsTextFrame and returns it, or null if the end of 1517 * iteration has been reached. 1518 */ 1519 nsTextFrame* GetNext(); 1520 1521 /** 1522 * Returns whether the iterator is within the subtree. 1523 */ 1524 bool IsWithinSubtree() const { return mSubtreePosition == eWithinSubtree; } 1525 1526 /** 1527 * Returns whether the iterator is past the subtree. 1528 */ 1529 bool IsAfterSubtree() const { return mSubtreePosition == eAfterSubtree; } 1530 1531 /** 1532 * Returns the frame corresponding to the <textPath> element, if we 1533 * are inside one. 1534 */ 1535 nsIFrame* TextPathFrame() const { 1536 return mTextPathFrames.IsEmpty() ? nullptr : mTextPathFrames.LastElement(); 1537 } 1538 1539 /** 1540 * Returns the current frame's computed dominant-baseline value. 1541 */ 1542 StyleDominantBaseline DominantBaseline() const { 1543 return mBaselines.LastElement(); 1544 } 1545 1546 /** 1547 * Finishes the iterator. 1548 */ 1549 void Close() { mCurrentFrame = nullptr; } 1550 1551 private: 1552 /** 1553 * Initializes the iterator and advances to the first item. 1554 */ 1555 void Init() { 1556 if (!mRootFrame) { 1557 return; 1558 } 1559 1560 mBaselines.AppendElement(mRootFrame->StyleSVG()->mDominantBaseline); 1561 GetNext(); 1562 } 1563 1564 /** 1565 * Pushes the specified frame's computed dominant-baseline value. 1566 * If the value of the property is "auto", then the parent frame's 1567 * computed value is used. 1568 */ 1569 void PushBaseline(nsIFrame* aNextFrame); 1570 1571 /** 1572 * Pops the current dominant-baseline off the stack. 1573 */ 1574 void PopBaseline(); 1575 1576 /** 1577 * The root frame we are iterating through. 1578 */ 1579 SVGTextFrame* const mRootFrame; 1580 1581 /** 1582 * The frame for the subtree we are also interested in tracking. 1583 */ 1584 const nsIFrame* const mSubtree; 1585 1586 /** 1587 * The current value of the iterator. 1588 */ 1589 nsIFrame* mCurrentFrame; 1590 1591 /** 1592 * The position, in app units, of the current frame relative to mRootFrame. 1593 */ 1594 nsPoint mCurrentPosition; 1595 1596 /** 1597 * Stack of frames corresponding to <textPath> elements that are in scope 1598 * for the current frame. 1599 */ 1600 AutoTArray<nsIFrame*, 1> mTextPathFrames; 1601 1602 /** 1603 * Stack of dominant-baseline values to record as we traverse through the 1604 * frame tree. 1605 */ 1606 AutoTArray<StyleDominantBaseline, 8> mBaselines; 1607 1608 /** 1609 * The iterator's current position relative to mSubtree. 1610 */ 1611 SubtreePosition mSubtreePosition; 1612 }; 1613 1614 uint32_t TextFrameIterator::UndisplayedCharacters() const { 1615 MOZ_ASSERT( 1616 !mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY), 1617 "Text correspondence must be up to date"); 1618 1619 if (!mCurrentFrame) { 1620 return mRootFrame->mTrailingUndisplayedCharacters; 1621 } 1622 1623 nsTextFrame* frame = do_QueryFrame(mCurrentFrame); 1624 return GetUndisplayedCharactersBeforeFrame(frame); 1625 } 1626 1627 nsTextFrame* TextFrameIterator::GetNext() { 1628 // Starting from mCurrentFrame, we do a non-recursive traversal to the next 1629 // nsTextFrame beneath mRoot, updating mSubtreePosition appropriately if we 1630 // encounter mSubtree. 1631 if (mCurrentFrame) { 1632 do { 1633 nsIFrame* next = IsTextContentElement(mCurrentFrame->GetContent()) 1634 ? mCurrentFrame->PrincipalChildList().FirstChild() 1635 : nullptr; 1636 if (next) { 1637 // Descend into this frame, and accumulate its position. 1638 mCurrentPosition += next->GetPosition(); 1639 if (next->GetContent()->IsSVGElement(nsGkAtoms::textPath)) { 1640 // Record this <textPath> frame. 1641 mTextPathFrames.AppendElement(next); 1642 } 1643 // Record the frame's baseline. 1644 PushBaseline(next); 1645 mCurrentFrame = next; 1646 if (mCurrentFrame == mSubtree) { 1647 // If the current frame is mSubtree, we have now moved into it. 1648 mSubtreePosition = eWithinSubtree; 1649 } 1650 } else { 1651 for (;;) { 1652 // We want to move past the current frame. 1653 if (mCurrentFrame == mRootFrame) { 1654 // If we've reached the root frame, we're finished. 1655 mCurrentFrame = nullptr; 1656 break; 1657 } 1658 // Remove the current frame's position. 1659 mCurrentPosition -= mCurrentFrame->GetPosition(); 1660 if (mCurrentFrame->GetContent()->IsSVGElement(nsGkAtoms::textPath)) { 1661 // Pop off the <textPath> frame if this is a <textPath>. 1662 mTextPathFrames.RemoveLastElement(); 1663 } 1664 // Pop off the current baseline. 1665 PopBaseline(); 1666 if (mCurrentFrame == mSubtree) { 1667 // If this was mSubtree, we have now moved past it. 1668 mSubtreePosition = eAfterSubtree; 1669 } 1670 next = mCurrentFrame->GetNextSibling(); 1671 if (next) { 1672 // Moving to the next sibling. 1673 mCurrentPosition += next->GetPosition(); 1674 if (next->GetContent()->IsSVGElement(nsGkAtoms::textPath)) { 1675 // Record this <textPath> frame. 1676 mTextPathFrames.AppendElement(next); 1677 } 1678 // Record the frame's baseline. 1679 PushBaseline(next); 1680 mCurrentFrame = next; 1681 if (mCurrentFrame == mSubtree) { 1682 // If the current frame is mSubtree, we have now moved into it. 1683 mSubtreePosition = eWithinSubtree; 1684 } 1685 break; 1686 } 1687 if (mCurrentFrame == mSubtree) { 1688 // If there is no next sibling frame, and the current frame is 1689 // mSubtree, we have now moved past it. 1690 mSubtreePosition = eAfterSubtree; 1691 } 1692 // Ascend out of this frame. 1693 mCurrentFrame = mCurrentFrame->GetParent(); 1694 } 1695 } 1696 } while (mCurrentFrame && !IsNonEmptyTextFrame(mCurrentFrame)); 1697 } 1698 1699 return GetCurrent(); 1700 } 1701 1702 void TextFrameIterator::PushBaseline(nsIFrame* aNextFrame) { 1703 StyleDominantBaseline baseline = aNextFrame->StyleSVG()->mDominantBaseline; 1704 mBaselines.AppendElement(baseline); 1705 } 1706 1707 void TextFrameIterator::PopBaseline() { 1708 NS_ASSERTION(!mBaselines.IsEmpty(), "popped too many baselines"); 1709 mBaselines.RemoveLastElement(); 1710 } 1711 1712 // ----------------------------------------------------------------------------- 1713 // TextRenderedRunIterator 1714 1715 /** 1716 * Iterator for TextRenderedRun objects for the SVGTextFrame. 1717 */ 1718 class TextRenderedRunIterator { 1719 public: 1720 /** 1721 * Values for the aFilter argument of the constructor, to indicate which 1722 * frames we should be limited to iterating TextRenderedRun objects for. 1723 */ 1724 enum RenderedRunFilter { 1725 // Iterate TextRenderedRuns for all nsTextFrames. 1726 eAllFrames, 1727 // Iterate only TextRenderedRuns for nsTextFrames that are 1728 // visibility:visible. 1729 eVisibleFrames 1730 }; 1731 1732 /** 1733 * Constructs a TextRenderedRunIterator with an optional frame subtree to 1734 * restrict iterated rendered runs to. 1735 * 1736 * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate 1737 * through. 1738 * @param aFilter Indicates whether to iterate rendered runs for non-visible 1739 * nsTextFrames. 1740 * @param aSubtree An optional frame subtree to restrict iterated rendered 1741 * runs to. 1742 */ 1743 explicit TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame, 1744 RenderedRunFilter aFilter = eAllFrames, 1745 const nsIFrame* aSubtree = nullptr) 1746 : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree), 1747 mFilter(aFilter), 1748 mTextElementCharIndex(0), 1749 mFrameStartTextElementCharIndex(0), 1750 mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor), 1751 mCurrent(First()) {} 1752 1753 /** 1754 * Constructs a TextRenderedRunIterator with a content subtree to restrict 1755 * iterated rendered runs to. 1756 * 1757 * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate 1758 * through. 1759 * @param aFilter Indicates whether to iterate rendered runs for non-visible 1760 * nsTextFrames. 1761 * @param aSubtree A content subtree to restrict iterated rendered runs to. 1762 */ 1763 TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame, 1764 RenderedRunFilter aFilter, nsIContent* aSubtree) 1765 : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree), 1766 mFilter(aFilter), 1767 mTextElementCharIndex(0), 1768 mFrameStartTextElementCharIndex(0), 1769 mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor), 1770 mCurrent(First()) {} 1771 1772 /** 1773 * Ensure any cached PropertyProvider is cleared at the end of the iteration. 1774 */ 1775 ~TextRenderedRunIterator() { 1776 if (auto* root = mFrameIterator.GetRoot()) { 1777 root->ForgetCachedProvider(); 1778 } 1779 } 1780 1781 /** 1782 * Returns the current TextRenderedRun. 1783 */ 1784 TextRenderedRun Current() const { return mCurrent; } 1785 1786 /** 1787 * Advances to the next TextRenderedRun and returns it. 1788 */ 1789 TextRenderedRun Next(); 1790 1791 private: 1792 /** 1793 * Returns the root SVGTextFrame this iterator is for. 1794 */ 1795 SVGTextFrame* GetRoot() const { return mFrameIterator.GetRoot(); } 1796 1797 /** 1798 * Advances to the first TextRenderedRun and returns it. 1799 */ 1800 TextRenderedRun First(); 1801 1802 /** 1803 * The frame iterator to use. 1804 */ 1805 TextFrameIterator mFrameIterator; 1806 1807 /** 1808 * The filter indicating which TextRenderedRuns to return. 1809 */ 1810 RenderedRunFilter mFilter; 1811 1812 /** 1813 * The character index across the entire <text> element we are currently 1814 * up to. 1815 */ 1816 uint32_t mTextElementCharIndex; 1817 1818 /** 1819 * The character index across the entire <text> for the start of the current 1820 * frame. 1821 */ 1822 uint32_t mFrameStartTextElementCharIndex; 1823 1824 /** 1825 * The font-size scale factor we used when constructing the nsTextFrames. 1826 */ 1827 double mFontSizeScaleFactor; 1828 1829 /** 1830 * The current TextRenderedRun. 1831 */ 1832 TextRenderedRun mCurrent; 1833 }; 1834 1835 TextRenderedRun TextRenderedRunIterator::Next() { 1836 if (!mFrameIterator.GetCurrent()) { 1837 // If there are no more frames, then there are no more rendered runs to 1838 // return. 1839 mCurrent = TextRenderedRun(); 1840 return mCurrent; 1841 } 1842 1843 // The values we will use to initialize the TextRenderedRun with. 1844 nsTextFrame* frame; 1845 gfxPoint pt; 1846 double rotate; 1847 nscoord baseline; 1848 uint32_t offset, length; 1849 uint32_t charIndex; 1850 1851 // We loop, because we want to skip over rendered runs that either aren't 1852 // within our subtree of interest, because they don't match the filter, 1853 // or because they are hidden due to having fallen off the end of a 1854 // <textPath>. 1855 for (;;) { 1856 if (mFrameIterator.IsAfterSubtree()) { 1857 mCurrent = TextRenderedRun(); 1858 return mCurrent; 1859 } 1860 1861 frame = mFrameIterator.GetCurrent(); 1862 1863 charIndex = mTextElementCharIndex; 1864 1865 // Find the end of the rendered run, by looking through the 1866 // SVGTextFrame's positions array until we find one that is recorded 1867 // as a run boundary. 1868 uint32_t runStart, 1869 runEnd; // XXX Replace runStart with mTextElementCharIndex. 1870 runStart = mTextElementCharIndex; 1871 runEnd = runStart + 1; 1872 while (runEnd < GetRoot()->mPositions.Length() && 1873 !GetRoot()->mPositions[runEnd].mRunBoundary) { 1874 runEnd++; 1875 } 1876 1877 // Convert the global run start/end indexes into an offset/length into the 1878 // current frame's Text. 1879 offset = 1880 frame->GetContentOffset() + runStart - mFrameStartTextElementCharIndex; 1881 length = runEnd - runStart; 1882 1883 // If the end of the frame's content comes before the run boundary we found 1884 // in SVGTextFrame's position array, we need to shorten the rendered run. 1885 uint32_t contentEnd = frame->GetContentEnd(); 1886 if (offset + length > contentEnd) { 1887 length = contentEnd - offset; 1888 } 1889 1890 NS_ASSERTION(offset >= uint32_t(frame->GetContentOffset()), 1891 "invalid offset"); 1892 NS_ASSERTION(offset + length <= contentEnd, "invalid offset or length"); 1893 1894 // Get the frame's baseline position. 1895 frame->EnsureTextRun(nsTextFrame::eInflated); 1896 baseline = GetBaselinePosition( 1897 frame, frame->GetTextRun(nsTextFrame::eInflated), 1898 mFrameIterator.DominantBaseline(), mFontSizeScaleFactor); 1899 1900 // Trim the offset/length to remove any leading/trailing white space. 1901 uint32_t untrimmedOffset = offset; 1902 uint32_t untrimmedLength = length; 1903 nsTextFrame::TrimmedOffsets trimmedOffsets = 1904 frame->GetTrimmedOffsets(frame->CharacterDataBuffer()); 1905 TrimOffsets(offset, length, trimmedOffsets); 1906 charIndex += offset - untrimmedOffset; 1907 1908 // Get the position and rotation of the character that begins this 1909 // rendered run. 1910 pt = GetRoot()->mPositions[charIndex].mPosition; 1911 rotate = GetRoot()->mPositions[charIndex].mAngle; 1912 1913 // Determine if we should skip this rendered run. 1914 bool skip = !mFrameIterator.IsWithinSubtree() || 1915 GetRoot()->mPositions[mTextElementCharIndex].mHidden; 1916 if (mFilter == eVisibleFrames) { 1917 skip = skip || !frame->StyleVisibility()->IsVisible(); 1918 } 1919 1920 // Update our global character index to move past the characters 1921 // corresponding to this rendered run. 1922 mTextElementCharIndex += untrimmedLength; 1923 1924 // If we have moved past the end of the current frame's content, we need to 1925 // advance to the next frame. 1926 if (offset + untrimmedLength >= contentEnd) { 1927 mFrameIterator.GetNext(); 1928 mTextElementCharIndex += mFrameIterator.UndisplayedCharacters(); 1929 mFrameStartTextElementCharIndex = mTextElementCharIndex; 1930 } 1931 1932 if (!mFrameIterator.GetCurrent()) { 1933 if (skip) { 1934 // That was the last frame, and we skipped this rendered run. So we 1935 // have no rendered run to return. 1936 mCurrent = TextRenderedRun(); 1937 return mCurrent; 1938 } 1939 break; 1940 } 1941 1942 if (length && !skip) { 1943 // Only return a rendered run if it didn't get collapsed away entirely 1944 // (due to it being all white space) and if we don't want to skip it. 1945 break; 1946 } 1947 } 1948 1949 mCurrent = TextRenderedRun(frame, GetRoot(), pt, rotate, mFontSizeScaleFactor, 1950 baseline, offset, length, charIndex); 1951 return mCurrent; 1952 } 1953 1954 TextRenderedRun TextRenderedRunIterator::First() { 1955 if (!mFrameIterator.GetCurrent()) { 1956 return TextRenderedRun(); 1957 } 1958 1959 if (GetRoot()->mPositions.IsEmpty()) { 1960 mFrameIterator.Close(); 1961 return TextRenderedRun(); 1962 } 1963 1964 // Get the character index for the start of this rendered run, by skipping 1965 // any undisplayed characters. 1966 mTextElementCharIndex = mFrameIterator.UndisplayedCharacters(); 1967 mFrameStartTextElementCharIndex = mTextElementCharIndex; 1968 1969 return Next(); 1970 } 1971 1972 // ----------------------------------------------------------------------------- 1973 // CharIterator 1974 1975 /** 1976 * Iterator for characters within an SVGTextFrame. 1977 */ 1978 class MOZ_STACK_CLASS CharIterator { 1979 using Range = gfxTextRun::Range; 1980 1981 public: 1982 /** 1983 * Values for the aFilter argument of the constructor, to indicate which 1984 * characters we should be iterating over. 1985 */ 1986 enum CharacterFilter { 1987 // Iterate over all original characters from the DOM that are within valid 1988 // text content elements. 1989 eOriginal, 1990 // Iterate only over characters that are not skipped characters. 1991 eUnskipped, 1992 // Iterate only over characters that are addressable by the positioning 1993 // attributes x="", y="", etc. This includes all characters after 1994 // collapsing white space as required by the value of 'white-space'. 1995 eAddressable, 1996 }; 1997 1998 /** 1999 * Constructs a CharIterator. 2000 * 2001 * @param aSVGTextFrame The SVGTextFrame whose characters to iterate 2002 * through. 2003 * @param aFilter Indicates which characters to iterate over. 2004 * @param aSubtree A content subtree to track whether the current character 2005 * is within. 2006 */ 2007 CharIterator(SVGTextFrame* aSVGTextFrame, CharacterFilter aFilter, 2008 nsIContent* aSubtree, bool aPostReflow = true); 2009 2010 /** 2011 * Ensure any cached PropertyProvider is cleared at the end of the iteration. 2012 */ 2013 ~CharIterator() { 2014 if (auto* root = mFrameIterator.GetRoot()) { 2015 root->ForgetCachedProvider(); 2016 } 2017 } 2018 2019 /** 2020 * Returns whether the iterator is finished. 2021 */ 2022 bool AtEnd() const { return !mFrameIterator.GetCurrent(); } 2023 2024 /** 2025 * Advances to the next matching character. Returns true if there was a 2026 * character to advance to, and false otherwise. 2027 */ 2028 bool Next(); 2029 2030 /** 2031 * Advances ahead aCount matching characters. Returns true if there were 2032 * enough characters to advance past, and false otherwise. 2033 */ 2034 bool Next(uint32_t aCount); 2035 2036 /** 2037 * Advances ahead up to aCount matching characters. 2038 */ 2039 void NextWithinSubtree(uint32_t aCount); 2040 2041 /** 2042 * Advances to the character with the specified index. The index is in the 2043 * space of original characters (i.e., all DOM characters under the <text> 2044 * that are within valid text content elements). 2045 */ 2046 bool AdvanceToCharacter(uint32_t aTextElementCharIndex); 2047 2048 /** 2049 * Advances to the first matching character after the current nsTextFrame. 2050 */ 2051 bool AdvancePastCurrentFrame(); 2052 2053 /** 2054 * Advances to the first matching character after the frames within 2055 * the current <textPath>. 2056 */ 2057 bool AdvancePastCurrentTextPathFrame(); 2058 2059 /** 2060 * Advances to the first matching character of the subtree. Returns true 2061 * if we successfully advance to the subtree, or if we are already within 2062 * the subtree. Returns false if we are past the subtree. 2063 */ 2064 bool AdvanceToSubtree(); 2065 2066 /** 2067 * Returns the nsTextFrame for the current character, or null if the end of 2068 * iteration has been reached. 2069 */ 2070 nsTextFrame* GetTextFrame() const { return mFrameIterator.GetCurrent(); } 2071 2072 /** 2073 * Returns whether the iterator is within the subtree. 2074 */ 2075 bool IsWithinSubtree() const { return mFrameIterator.IsWithinSubtree(); } 2076 2077 /** 2078 * Returns whether the iterator is past the subtree. 2079 */ 2080 bool IsAfterSubtree() const { return mFrameIterator.IsAfterSubtree(); } 2081 2082 /** 2083 * Returns the iterator's computed dominant-baseline value. 2084 */ 2085 StyleDominantBaseline DominantBaseline() const { 2086 return mFrameIterator.DominantBaseline(); 2087 } 2088 2089 /** 2090 * Returns whether the current character is a skipped character. 2091 */ 2092 bool IsOriginalCharSkipped() const { 2093 return mSkipCharsIterator.IsOriginalCharSkipped(); 2094 } 2095 2096 /** 2097 * Returns whether the current character is the start of a cluster and 2098 * ligature group. 2099 */ 2100 bool IsClusterAndLigatureGroupStart() const { 2101 return mTextRun->IsLigatureGroupStart( 2102 mSkipCharsIterator.GetSkippedOffset()) && 2103 mTextRun->IsClusterStart(mSkipCharsIterator.GetSkippedOffset()); 2104 } 2105 2106 /** 2107 * Returns the glyph run for the current character. 2108 */ 2109 const gfxTextRun::GlyphRun& GlyphRun() const { 2110 return *mTextRun->FindFirstGlyphRunContaining( 2111 mSkipCharsIterator.GetSkippedOffset()); 2112 } 2113 2114 /** 2115 * Returns whether the current character is trimmed away when painting, 2116 * due to it being leading/trailing white space. 2117 */ 2118 bool IsOriginalCharTrimmed() const; 2119 2120 /** 2121 * Returns whether the current character is unaddressable from the SVG glyph 2122 * positioning attributes. 2123 */ 2124 bool IsOriginalCharUnaddressable() const { 2125 return IsOriginalCharSkipped() || IsOriginalCharTrimmed(); 2126 } 2127 2128 /** 2129 * Returns the text run for the current character. 2130 */ 2131 gfxTextRun* TextRun() const { return mTextRun; } 2132 2133 /** 2134 * Returns the current character index. 2135 */ 2136 uint32_t TextElementCharIndex() const { return mTextElementCharIndex; } 2137 2138 /** 2139 * Returns the character index for the start of the cluster/ligature group it 2140 * is part of. 2141 */ 2142 uint32_t GlyphStartTextElementCharIndex() const { 2143 return mGlyphStartTextElementCharIndex; 2144 } 2145 2146 /** 2147 * Gets the advance, in user units, of the current character. If the 2148 * character is a part of ligature, then the advance returned will be 2149 * a fraction of the ligature glyph's advance. 2150 * 2151 * @param aContext The context to use for unit conversions. 2152 */ 2153 gfxFloat GetAdvance(nsPresContext* aContext) const; 2154 2155 /** 2156 * Returns the frame corresponding to the <textPath> that the current 2157 * character is within. 2158 */ 2159 nsIFrame* TextPathFrame() const { return mFrameIterator.TextPathFrame(); } 2160 2161 #ifdef DEBUG 2162 /** 2163 * Returns the subtree we were constructed with. 2164 */ 2165 nsIContent* GetSubtree() const { return mSubtree; } 2166 2167 /** 2168 * Returns the CharacterFilter mode in use. 2169 */ 2170 CharacterFilter Filter() const { return mFilter; } 2171 #endif 2172 2173 private: 2174 /** 2175 * Advances to the next character without checking it against the filter. 2176 * Returns true if there was a next character to advance to, or false 2177 * otherwise. 2178 */ 2179 bool NextCharacter(); 2180 2181 /** 2182 * Returns whether the current character matches the filter. 2183 */ 2184 bool MatchesFilter() const; 2185 2186 /** 2187 * If this is the start of a glyph, record it. 2188 */ 2189 void UpdateGlyphStartTextElementCharIndex() { 2190 if (!IsOriginalCharSkipped() && IsClusterAndLigatureGroupStart()) { 2191 mGlyphStartTextElementCharIndex = mTextElementCharIndex; 2192 } 2193 } 2194 2195 /** 2196 * The filter to use. 2197 */ 2198 CharacterFilter mFilter; 2199 2200 /** 2201 * The iterator for text frames. 2202 */ 2203 TextFrameIterator mFrameIterator; 2204 2205 #ifdef DEBUG 2206 /** 2207 * The subtree we were constructed with. 2208 */ 2209 nsIContent* const mSubtree; 2210 #endif 2211 2212 /** 2213 * A gfxSkipCharsIterator for the text frame the current character is 2214 * a part of. 2215 */ 2216 gfxSkipCharsIterator mSkipCharsIterator; 2217 2218 // Cache for information computed by IsOriginalCharTrimmed. 2219 mutable nsTextFrame* mFrameForTrimCheck; 2220 mutable uint32_t mTrimmedOffset; 2221 mutable uint32_t mTrimmedLength; 2222 2223 /** 2224 * The text run the current character is a part of. 2225 */ 2226 gfxTextRun* mTextRun; 2227 2228 /** 2229 * The current character's index. 2230 */ 2231 uint32_t mTextElementCharIndex; 2232 2233 /** 2234 * The index of the character that starts the cluster/ligature group the 2235 * current character is a part of. 2236 */ 2237 uint32_t mGlyphStartTextElementCharIndex; 2238 2239 /** 2240 * The scale factor to apply to glyph advances returned by 2241 * GetAdvance etc. to take into account textLength="". 2242 */ 2243 float mLengthAdjustScaleFactor; 2244 2245 /** 2246 * Whether the instance of this class is being used after reflow has occurred 2247 * or not. 2248 */ 2249 bool mPostReflow; 2250 }; 2251 2252 CharIterator::CharIterator(SVGTextFrame* aSVGTextFrame, 2253 CharIterator::CharacterFilter aFilter, 2254 nsIContent* aSubtree, bool aPostReflow) 2255 : mFilter(aFilter), 2256 mFrameIterator(aSVGTextFrame, aSubtree), 2257 #ifdef DEBUG 2258 mSubtree(aSubtree), 2259 #endif 2260 mFrameForTrimCheck(nullptr), 2261 mTrimmedOffset(0), 2262 mTrimmedLength(0), 2263 mTextRun(nullptr), 2264 mTextElementCharIndex(0), 2265 mGlyphStartTextElementCharIndex(0), 2266 mLengthAdjustScaleFactor(aSVGTextFrame->mLengthAdjustScaleFactor), 2267 mPostReflow(aPostReflow) { 2268 if (!AtEnd()) { 2269 mSkipCharsIterator = GetTextFrame()->EnsureTextRun(nsTextFrame::eInflated); 2270 mTextRun = GetTextFrame()->GetTextRun(nsTextFrame::eInflated); 2271 mTextElementCharIndex = mFrameIterator.UndisplayedCharacters(); 2272 UpdateGlyphStartTextElementCharIndex(); 2273 if (!MatchesFilter()) { 2274 Next(); 2275 } 2276 } 2277 } 2278 2279 bool CharIterator::Next() { 2280 while (NextCharacter()) { 2281 if (MatchesFilter()) { 2282 return true; 2283 } 2284 } 2285 return false; 2286 } 2287 2288 bool CharIterator::Next(uint32_t aCount) { 2289 if (aCount == 0 && AtEnd()) { 2290 return false; 2291 } 2292 while (aCount) { 2293 if (!Next()) { 2294 return false; 2295 } 2296 aCount--; 2297 } 2298 return true; 2299 } 2300 2301 void CharIterator::NextWithinSubtree(uint32_t aCount) { 2302 while (IsWithinSubtree() && aCount) { 2303 --aCount; 2304 if (!Next()) { 2305 return; 2306 } 2307 } 2308 } 2309 2310 bool CharIterator::AdvanceToCharacter(uint32_t aTextElementCharIndex) { 2311 while (mTextElementCharIndex < aTextElementCharIndex) { 2312 if (!Next()) { 2313 return false; 2314 } 2315 } 2316 return true; 2317 } 2318 2319 bool CharIterator::AdvancePastCurrentFrame() { 2320 // XXX Can do this better than one character at a time if it matters. 2321 nsTextFrame* currentFrame = GetTextFrame(); 2322 do { 2323 if (!Next()) { 2324 return false; 2325 } 2326 } while (GetTextFrame() == currentFrame); 2327 return true; 2328 } 2329 2330 bool CharIterator::AdvancePastCurrentTextPathFrame() { 2331 nsIFrame* currentTextPathFrame = TextPathFrame(); 2332 NS_ASSERTION(currentTextPathFrame, 2333 "expected AdvancePastCurrentTextPathFrame to be called only " 2334 "within a text path frame"); 2335 do { 2336 if (!AdvancePastCurrentFrame()) { 2337 return false; 2338 } 2339 } while (TextPathFrame() == currentTextPathFrame); 2340 return true; 2341 } 2342 2343 bool CharIterator::AdvanceToSubtree() { 2344 while (!IsWithinSubtree()) { 2345 if (IsAfterSubtree()) { 2346 return false; 2347 } 2348 if (!AdvancePastCurrentFrame()) { 2349 return false; 2350 } 2351 } 2352 return true; 2353 } 2354 2355 bool CharIterator::IsOriginalCharTrimmed() const { 2356 if (mFrameForTrimCheck != GetTextFrame()) { 2357 // Since we do a lot of trim checking, we cache the trimmed offsets and 2358 // lengths while we are in the same frame. 2359 mFrameForTrimCheck = GetTextFrame(); 2360 uint32_t offset = mFrameForTrimCheck->GetContentOffset(); 2361 uint32_t length = mFrameForTrimCheck->GetContentLength(); 2362 nsTextFrame::TrimmedOffsets trim = mFrameForTrimCheck->GetTrimmedOffsets( 2363 mFrameForTrimCheck->CharacterDataBuffer(), 2364 (mPostReflow ? nsTextFrame::TrimmedOffsetFlags::Default 2365 : nsTextFrame::TrimmedOffsetFlags::NotPostReflow)); 2366 TrimOffsets(offset, length, trim); 2367 mTrimmedOffset = offset; 2368 mTrimmedLength = length; 2369 } 2370 2371 // A character is trimmed if it is outside the mTrimmedOffset/mTrimmedLength 2372 // range and it is not a significant newline character. 2373 uint32_t index = mSkipCharsIterator.GetOriginalOffset(); 2374 return !( 2375 (index >= mTrimmedOffset && index < mTrimmedOffset + mTrimmedLength) || 2376 (index >= mTrimmedOffset + mTrimmedLength && 2377 mFrameForTrimCheck->StyleText()->NewlineIsSignificant( 2378 mFrameForTrimCheck) && 2379 mFrameForTrimCheck->CharacterDataBuffer().CharAt(index) == '\n')); 2380 } 2381 2382 gfxFloat CharIterator::GetAdvance(nsPresContext* aContext) const { 2383 float cssPxPerDevPx = 2384 nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); 2385 2386 auto& provider = 2387 mFrameIterator.GetRoot()->PropertyProviderFor(GetTextFrame()); 2388 uint32_t offset = mSkipCharsIterator.GetSkippedOffset(); 2389 gfxFloat advance = 2390 mTextRun->GetAdvanceWidth(Range(offset, offset + 1), &provider); 2391 return aContext->AppUnitsToGfxUnits(advance) * mLengthAdjustScaleFactor * 2392 cssPxPerDevPx; 2393 } 2394 2395 bool CharIterator::NextCharacter() { 2396 if (AtEnd()) { 2397 return false; 2398 } 2399 2400 mTextElementCharIndex++; 2401 2402 // Advance within the current text run. 2403 mSkipCharsIterator.AdvanceOriginal(1); 2404 if (mSkipCharsIterator.GetOriginalOffset() < 2405 GetTextFrame()->GetContentEnd()) { 2406 // We're still within the part of the text run for the current text frame. 2407 UpdateGlyphStartTextElementCharIndex(); 2408 return true; 2409 } 2410 2411 // Advance to the next frame. 2412 mFrameIterator.GetNext(); 2413 2414 // Skip any undisplayed characters. 2415 uint32_t undisplayed = mFrameIterator.UndisplayedCharacters(); 2416 mTextElementCharIndex += undisplayed; 2417 if (!GetTextFrame()) { 2418 // We're at the end. 2419 mSkipCharsIterator = gfxSkipCharsIterator(); 2420 return false; 2421 } 2422 2423 mSkipCharsIterator = GetTextFrame()->EnsureTextRun(nsTextFrame::eInflated); 2424 mTextRun = GetTextFrame()->GetTextRun(nsTextFrame::eInflated); 2425 UpdateGlyphStartTextElementCharIndex(); 2426 return true; 2427 } 2428 2429 bool CharIterator::MatchesFilter() const { 2430 switch (mFilter) { 2431 case eOriginal: 2432 return true; 2433 case eUnskipped: 2434 return !IsOriginalCharSkipped(); 2435 case eAddressable: 2436 return !IsOriginalCharSkipped() && !IsOriginalCharUnaddressable(); 2437 } 2438 MOZ_ASSERT_UNREACHABLE("Invalid mFilter value"); 2439 return true; 2440 } 2441 2442 // ----------------------------------------------------------------------------- 2443 // SVGTextDrawPathCallbacks 2444 2445 /** 2446 * Text frame draw callback class that paints the text and text decoration parts 2447 * of an nsTextFrame using SVG painting properties, and selection backgrounds 2448 * and decorations as they would normally. 2449 * 2450 * An instance of this class is passed to nsTextFrame::PaintText if painting 2451 * cannot be done directly (e.g. if we are using an SVG pattern fill, stroking 2452 * the text, etc.). 2453 */ 2454 class SVGTextDrawPathCallbacks final : public nsTextFrame::DrawPathCallbacks { 2455 using imgDrawingParams = image::imgDrawingParams; 2456 2457 public: 2458 /** 2459 * Constructs an SVGTextDrawPathCallbacks. 2460 * 2461 * @param aSVGTextFrame The ancestor text frame. 2462 * @param aContextPaint Used by context-fill and context-stroke. 2463 * @param aContext The context to use for painting. 2464 * @param aFrame The nsTextFrame to paint. 2465 * @param aCanvasTM The transformation matrix to set when painting; this 2466 * should be the FOR_OUTERSVG_TM canvas TM of the text, so that 2467 * paint servers are painted correctly. 2468 * @param aImgParams Whether we need to synchronously decode images. 2469 * @param aShouldPaintSVGGlyphs Whether SVG glyphs should be painted. 2470 */ 2471 SVGTextDrawPathCallbacks(SVGTextFrame* aSVGTextFrame, 2472 SVGContextPaint* aContextPaint, gfxContext& aContext, 2473 nsTextFrame* aFrame, const gfxMatrix& aCanvasTM, 2474 imgDrawingParams& aImgParams, 2475 bool aShouldPaintSVGGlyphs) 2476 : DrawPathCallbacks(aShouldPaintSVGGlyphs), 2477 mSVGTextFrame(aSVGTextFrame), 2478 mContextPaint(aContextPaint), 2479 mContext(aContext), 2480 mFrame(aFrame), 2481 mCanvasTM(aCanvasTM), 2482 mImgParams(aImgParams) {} 2483 2484 void NotifySelectionBackgroundNeedsFill(const Rect& aBackgroundRect, 2485 nscolor aColor, 2486 DrawTarget& aDrawTarget) override; 2487 void PaintDecorationLine(Rect aPath, bool aPaintingShadows, 2488 nscolor aColor) override; 2489 void PaintSelectionDecorationLine(Rect aPath, bool aPaintingShadows, 2490 nscolor aColor) override; 2491 void NotifyBeforeText(bool aPaintingShadows, nscolor aColor) override; 2492 void NotifyGlyphPathEmitted() override; 2493 void NotifyAfterText() override; 2494 2495 private: 2496 void SetupContext(); 2497 2498 bool IsClipPathChild() const { 2499 return mSVGTextFrame->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD); 2500 } 2501 2502 /** 2503 * Paints a piece of text geometry. This is called when glyphs 2504 * or text decorations have been emitted to the gfxContext. 2505 */ 2506 void HandleTextGeometry(); 2507 2508 /** 2509 * Sets the gfxContext paint to the appropriate color or pattern 2510 * for filling text geometry. 2511 */ 2512 void MakeFillPattern(GeneralPattern* aOutPattern); 2513 2514 /** 2515 * Fills and strokes a piece of text geometry, using group opacity 2516 * if the selection style requires it. 2517 */ 2518 void FillAndStrokeGeometry(); 2519 2520 /** 2521 * Fills a piece of text geometry. 2522 */ 2523 void FillGeometry(); 2524 2525 /** 2526 * Strokes a piece of text geometry. 2527 */ 2528 void StrokeGeometry(); 2529 2530 /* 2531 * Takes a colour and modifies it to account for opacity properties. 2532 */ 2533 void ApplyOpacity(sRGBColor& aColor, const StyleSVGPaint& aPaint, 2534 const StyleSVGOpacity& aOpacity) const; 2535 2536 SVGTextFrame* const mSVGTextFrame; 2537 SVGContextPaint* const mContextPaint; 2538 gfxContext& mContext; 2539 nsTextFrame* const mFrame; 2540 const gfxMatrix& mCanvasTM; 2541 imgDrawingParams& mImgParams; 2542 2543 /** 2544 * The color that we were last told from one of the path callback functions. 2545 * This color can be the special NS_SAME_AS_FOREGROUND_COLOR, 2546 * NS_40PERCENT_FOREGROUND_COLOR and NS_TRANSPARENT colors when we are 2547 * painting selections or IME decorations. 2548 */ 2549 nscolor mColor = NS_RGBA(0, 0, 0, 0); 2550 2551 /** 2552 * Whether we're painting text shadows. 2553 */ 2554 bool mPaintingShadows = false; 2555 }; 2556 2557 void SVGTextDrawPathCallbacks::NotifySelectionBackgroundNeedsFill( 2558 const Rect& aBackgroundRect, nscolor aColor, DrawTarget& aDrawTarget) { 2559 if (IsClipPathChild()) { 2560 // Don't paint selection backgrounds when in a clip path. 2561 return; 2562 } 2563 2564 mColor = aColor; // currently needed by MakeFillPattern 2565 mPaintingShadows = false; 2566 2567 GeneralPattern fillPattern; 2568 MakeFillPattern(&fillPattern); 2569 if (fillPattern.GetPattern()) { 2570 DrawOptions drawOptions(aColor == NS_40PERCENT_FOREGROUND_COLOR ? 0.4 2571 : 1.0); 2572 aDrawTarget.FillRect(aBackgroundRect, fillPattern, drawOptions); 2573 } 2574 } 2575 2576 void SVGTextDrawPathCallbacks::NotifyBeforeText(bool aPaintingShadows, 2577 nscolor aColor) { 2578 mColor = aColor; 2579 mPaintingShadows = aPaintingShadows; 2580 SetupContext(); 2581 mContext.NewPath(); 2582 } 2583 2584 void SVGTextDrawPathCallbacks::NotifyGlyphPathEmitted() { 2585 HandleTextGeometry(); 2586 mContext.NewPath(); 2587 } 2588 2589 void SVGTextDrawPathCallbacks::NotifyAfterText() { mContext.Restore(); } 2590 2591 void SVGTextDrawPathCallbacks::PaintDecorationLine(Rect aPath, 2592 bool aPaintingShadows, 2593 nscolor aColor) { 2594 mColor = aColor; 2595 mPaintingShadows = aPaintingShadows; 2596 AntialiasMode aaMode = 2597 SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering); 2598 2599 mContext.Save(); 2600 mContext.NewPath(); 2601 mContext.SetAntialiasMode(aaMode); 2602 mContext.Rectangle(ThebesRect(aPath)); 2603 HandleTextGeometry(); 2604 mContext.NewPath(); 2605 mContext.Restore(); 2606 } 2607 2608 void SVGTextDrawPathCallbacks::PaintSelectionDecorationLine( 2609 Rect aPath, bool aPaintingShadows, nscolor aColor) { 2610 if (IsClipPathChild()) { 2611 // Don't paint selection decorations when in a clip path. 2612 return; 2613 } 2614 2615 mColor = aColor; 2616 mPaintingShadows = aPaintingShadows; 2617 2618 mContext.Save(); 2619 mContext.NewPath(); 2620 mContext.Rectangle(ThebesRect(aPath)); 2621 FillAndStrokeGeometry(); 2622 mContext.Restore(); 2623 } 2624 2625 void SVGTextDrawPathCallbacks::SetupContext() { 2626 mContext.Save(); 2627 2628 // XXX This is copied from nsSVGGlyphFrame::Render, but cairo doesn't actually 2629 // seem to do anything with the antialias mode. So we can perhaps remove it, 2630 // or make SetAntialiasMode set cairo text antialiasing too. 2631 mContext.SetAntialiasMode( 2632 SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering)); 2633 } 2634 2635 void SVGTextDrawPathCallbacks::HandleTextGeometry() { 2636 if (IsClipPathChild()) { 2637 RefPtr<Path> path = mContext.GetPath(); 2638 ColorPattern white( 2639 DeviceColor(1.f, 1.f, 1.f, 1.f)); // for masking, so no ToDeviceColor 2640 mContext.GetDrawTarget()->Fill(path, white); 2641 } else { 2642 // Normal painting. 2643 gfxContextMatrixAutoSaveRestore saveMatrix(&mContext); 2644 mContext.SetMatrixDouble(mCanvasTM); 2645 2646 FillAndStrokeGeometry(); 2647 } 2648 } 2649 2650 void SVGTextDrawPathCallbacks::ApplyOpacity( 2651 sRGBColor& aColor, const StyleSVGPaint& aPaint, 2652 const StyleSVGOpacity& aOpacity) const { 2653 if (aPaint.kind.tag == StyleSVGPaintKind::Tag::Color) { 2654 aColor.a *= 2655 sRGBColor::FromABGR(aPaint.kind.AsColor().CalcColor(*mFrame->Style())) 2656 .a; 2657 } 2658 aColor.a *= SVGUtils::GetOpacity(aOpacity, mContextPaint); 2659 } 2660 2661 void SVGTextDrawPathCallbacks::MakeFillPattern(GeneralPattern* aOutPattern) { 2662 if (mColor == NS_SAME_AS_FOREGROUND_COLOR || 2663 mColor == NS_40PERCENT_FOREGROUND_COLOR) { 2664 SVGUtils::MakeFillPatternFor(mFrame, &mContext, aOutPattern, mImgParams, 2665 mContextPaint); 2666 return; 2667 } 2668 2669 if (mColor == NS_TRANSPARENT) { 2670 return; 2671 } 2672 2673 sRGBColor color(sRGBColor::FromABGR(mColor)); 2674 if (mPaintingShadows) { 2675 ApplyOpacity(color, mFrame->StyleSVG()->mFill, 2676 mFrame->StyleSVG()->mFillOpacity); 2677 } 2678 aOutPattern->InitColorPattern(ToDeviceColor(color)); 2679 } 2680 2681 void SVGTextDrawPathCallbacks::FillAndStrokeGeometry() { 2682 gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&mContext); 2683 if (mColor == NS_40PERCENT_FOREGROUND_COLOR) { 2684 autoGroupForBlend.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 0.4f); 2685 } 2686 2687 uint32_t paintOrder = mFrame->StyleSVG()->mPaintOrder; 2688 if (!paintOrder) { 2689 FillGeometry(); 2690 StrokeGeometry(); 2691 } else { 2692 while (paintOrder) { 2693 auto component = StylePaintOrder(paintOrder & kPaintOrderMask); 2694 switch (component) { 2695 case StylePaintOrder::Fill: 2696 FillGeometry(); 2697 break; 2698 case StylePaintOrder::Stroke: 2699 StrokeGeometry(); 2700 break; 2701 default: 2702 MOZ_FALLTHROUGH_ASSERT("Unknown paint-order value"); 2703 case StylePaintOrder::Markers: 2704 case StylePaintOrder::Normal: 2705 break; 2706 } 2707 paintOrder >>= kPaintOrderShift; 2708 } 2709 } 2710 } 2711 2712 void SVGTextDrawPathCallbacks::FillGeometry() { 2713 if (mFrame->StyleSVG()->mFill.kind.IsNone()) { 2714 return; 2715 } 2716 GeneralPattern fillPattern; 2717 MakeFillPattern(&fillPattern); 2718 if (fillPattern.GetPattern()) { 2719 RefPtr<Path> path = mContext.GetPath(); 2720 FillRule fillRule = SVGUtils::ToFillRule(mFrame->StyleSVG()->mFillRule); 2721 if (fillRule != path->GetFillRule()) { 2722 Path::SetFillRule(path, fillRule); 2723 } 2724 mContext.GetDrawTarget()->Fill(path, fillPattern); 2725 } 2726 } 2727 2728 void SVGTextDrawPathCallbacks::StrokeGeometry() { 2729 // We don't paint the stroke when we are filling with a selection color. 2730 if (!(mColor == NS_SAME_AS_FOREGROUND_COLOR || 2731 mColor == NS_40PERCENT_FOREGROUND_COLOR || mPaintingShadows)) { 2732 return; 2733 } 2734 2735 if (!SVGUtils::HasStroke(mFrame, mContextPaint)) { 2736 return; 2737 } 2738 2739 GeneralPattern strokePattern; 2740 if (mPaintingShadows) { 2741 sRGBColor color(sRGBColor::FromABGR(mColor)); 2742 ApplyOpacity(color, mFrame->StyleSVG()->mStroke, 2743 mFrame->StyleSVG()->mStrokeOpacity); 2744 strokePattern.InitColorPattern(ToDeviceColor(color)); 2745 } else { 2746 SVGUtils::MakeStrokePatternFor(mFrame, &mContext, &strokePattern, 2747 mImgParams, mContextPaint); 2748 } 2749 if (strokePattern.GetPattern()) { 2750 SVGElement* svgOwner = 2751 SVGElement::FromNode(mFrame->GetParent()->GetContent()); 2752 2753 // Apply any stroke-specific transform 2754 gfxMatrix outerSVGToUser; 2755 if (SVGUtils::GetNonScalingStrokeTransform(mFrame, &outerSVGToUser) && 2756 outerSVGToUser.Invert()) { 2757 mContext.Multiply(outerSVGToUser); 2758 } 2759 2760 RefPtr<Path> path = mContext.GetPath(); 2761 SVGContentUtils::AutoStrokeOptions strokeOptions; 2762 SVGContentUtils::GetStrokeOptions(&strokeOptions, svgOwner, mFrame->Style(), 2763 mContextPaint); 2764 DrawOptions drawOptions; 2765 drawOptions.mAntialiasMode = 2766 SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering); 2767 mContext.GetDrawTarget()->Stroke(path, strokePattern, strokeOptions); 2768 } 2769 } 2770 2771 // ============================================================================ 2772 // SVGTextFrame 2773 2774 // ---------------------------------------------------------------------------- 2775 // Display list item 2776 2777 class DisplaySVGText final : public DisplaySVGItem { 2778 public: 2779 DisplaySVGText(nsDisplayListBuilder* aBuilder, SVGTextFrame* aFrame) 2780 : DisplaySVGItem(aBuilder, aFrame) { 2781 MOZ_COUNT_CTOR(DisplaySVGText); 2782 } 2783 2784 MOZ_COUNTED_DTOR_FINAL(DisplaySVGText) 2785 2786 NS_DISPLAY_DECL_NAME("DisplaySVGText", TYPE_SVG_TEXT) 2787 2788 nsDisplayItemGeometry* AllocateGeometry( 2789 nsDisplayListBuilder* aBuilder) override { 2790 return new nsDisplayItemGenericGeometry(this, aBuilder); 2791 } 2792 2793 nsRect GetComponentAlphaBounds( 2794 nsDisplayListBuilder* aBuilder) const override { 2795 bool snap; 2796 return GetBounds(aBuilder, &snap); 2797 } 2798 }; 2799 2800 // --------------------------------------------------------------------- 2801 // nsQueryFrame methods 2802 2803 NS_QUERYFRAME_HEAD(SVGTextFrame) 2804 NS_QUERYFRAME_ENTRY(SVGTextFrame) 2805 NS_QUERYFRAME_TAIL_INHERITING(SVGDisplayContainerFrame) 2806 2807 } // namespace mozilla 2808 2809 // --------------------------------------------------------------------- 2810 // Implementation 2811 2812 nsIFrame* NS_NewSVGTextFrame(mozilla::PresShell* aPresShell, 2813 mozilla::ComputedStyle* aStyle) { 2814 return new (aPresShell) 2815 mozilla::SVGTextFrame(aStyle, aPresShell->GetPresContext()); 2816 } 2817 2818 namespace mozilla { 2819 2820 NS_IMPL_FRAMEARENA_HELPERS(SVGTextFrame) 2821 2822 // --------------------------------------------------------------------- 2823 // nsIFrame methods 2824 2825 void SVGTextFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 2826 nsIFrame* aPrevInFlow) { 2827 NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::text), 2828 "Content is not an SVG text"); 2829 2830 SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow); 2831 AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); 2832 2833 mMutationObserver = new MutationObserver(this); 2834 2835 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 2836 // We're inserting a new <text> element into a non-display context. 2837 // Ensure that we get reflowed. 2838 ScheduleReflowSVGNonDisplayText( 2839 IntrinsicDirty::FrameAncestorsAndDescendants); 2840 } 2841 } 2842 2843 void SVGTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 2844 const nsDisplayListSet& aLists) { 2845 if (IsSubtreeDirty()) { 2846 // We can sometimes be asked to paint before reflow happens and we 2847 // have updated mPositions, etc. In this case, we just avoid 2848 // painting. 2849 return; 2850 } 2851 if (!IsVisibleForPainting() && aBuilder->IsForPainting()) { 2852 return; 2853 } 2854 DisplayOutline(aBuilder, aLists); 2855 aLists.Content()->AppendNewToTop<DisplaySVGText>(aBuilder, this); 2856 } 2857 2858 void SVGTextFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { 2859 SVGDisplayContainerFrame::DidSetComputedStyle(aOldComputedStyle); 2860 if (StyleSVGReset()->HasNonScalingStroke() && 2861 (!aOldComputedStyle || 2862 !aOldComputedStyle->StyleSVGReset()->HasNonScalingStroke())) { 2863 SVGUtils::UpdateNonScalingStrokeStateBit(this); 2864 } 2865 } 2866 2867 nsresult SVGTextFrame::AttributeChanged(int32_t aNameSpaceID, 2868 nsAtom* aAttribute, AttrModType) { 2869 if (aNameSpaceID != kNameSpaceID_None) { 2870 return NS_OK; 2871 } 2872 2873 if (aAttribute == nsGkAtoms::transform) { 2874 // We don't invalidate for transform changes (the layers code does that). 2875 // Also note that SVGTransformableElement::GetAttributeChangeHint will 2876 // return nsChangeHint_UpdateOverflow for "transform" attribute changes 2877 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. 2878 2879 if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && mCanvasTM && 2880 mCanvasTM->IsSingular()) { 2881 // We won't have calculated the glyph positions correctly. 2882 NotifyGlyphMetricsChange(false); 2883 } 2884 mCanvasTM = nullptr; 2885 } else if (IsGlyphPositioningAttribute(aAttribute) || 2886 aAttribute == nsGkAtoms::textLength || 2887 aAttribute == nsGkAtoms::lengthAdjust) { 2888 NotifyGlyphMetricsChange(false); 2889 } 2890 2891 return NS_OK; 2892 } 2893 2894 void SVGTextFrame::ReflowSVGNonDisplayText() { 2895 MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this), 2896 "only call ReflowSVGNonDisplayText when an outer SVG frame is " 2897 "under ReflowSVG"); 2898 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), 2899 "only call ReflowSVGNonDisplayText if the frame is " 2900 "NS_FRAME_IS_NONDISPLAY"); 2901 2902 // We had a style change, so we mark this frame as dirty so that the next 2903 // time it is painted, we reflow the anonymous block frame. 2904 this->MarkSubtreeDirty(); 2905 2906 // Finally, we need to actually reflow the anonymous block frame and update 2907 // mPositions, in case we are being reflowed immediately after a DOM 2908 // mutation that needs frame reconstruction. 2909 MaybeReflowAnonymousBlockChild(); 2910 UpdateGlyphPositioning(); 2911 } 2912 2913 void SVGTextFrame::ScheduleReflowSVGNonDisplayText(IntrinsicDirty aReason) { 2914 MOZ_ASSERT(!SVGUtils::OuterSVGIsCallingReflowSVG(this), 2915 "do not call ScheduleReflowSVGNonDisplayText when the outer SVG " 2916 "frame is under ReflowSVG"); 2917 MOZ_ASSERT(!HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW), 2918 "do not call ScheduleReflowSVGNonDisplayText while reflowing the " 2919 "anonymous block child"); 2920 2921 // We need to find an ancestor frame that we can call FrameNeedsReflow 2922 // on that will cause the document to be marked as needing relayout, 2923 // and for that ancestor (or some further ancestor) to be marked as 2924 // a root to reflow. We choose the closest ancestor frame that is not 2925 // NS_FRAME_IS_NONDISPLAY and which is either an outer SVG frame or a 2926 // non-SVG frame. (We don't consider displayed SVG frame ancestors other 2927 // than SVGOuterSVGFrame, since calling FrameNeedsReflow on those other 2928 // SVG frames would do a bunch of unnecessary work on the SVG frames up to 2929 // the SVGOuterSVGFrame.) 2930 2931 nsIFrame* f = this; 2932 while (f) { 2933 if (!f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 2934 if (f->IsSubtreeDirty()) { 2935 // This is a displayed frame, so if it is already dirty, we will be 2936 // reflowed soon anyway. No need to call FrameNeedsReflow again, then. 2937 return; 2938 } 2939 if (!f->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 2940 break; 2941 } 2942 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); 2943 } 2944 f = f->GetParent(); 2945 } 2946 2947 MOZ_ASSERT(f, "should have found an ancestor frame to reflow"); 2948 2949 PresShell()->FrameNeedsReflow(f, aReason, NS_FRAME_IS_DIRTY); 2950 } 2951 2952 NS_IMPL_ISUPPORTS(SVGTextFrame::MutationObserver, nsIMutationObserver) 2953 2954 void SVGTextFrame::MutationObserver::ContentAppended( 2955 nsIContent* aFirstNewContent, const ContentAppendInfo&) { 2956 mFrame->NotifyGlyphMetricsChange(true); 2957 } 2958 2959 void SVGTextFrame::MutationObserver::ContentInserted(nsIContent* aChild, 2960 const ContentInsertInfo&) { 2961 mFrame->NotifyGlyphMetricsChange(true); 2962 } 2963 2964 void SVGTextFrame::MutationObserver::ContentWillBeRemoved( 2965 nsIContent* aChild, const ContentRemoveInfo& aInfo) { 2966 if (aInfo.mBatchRemovalState && !aInfo.mBatchRemovalState->mIsFirst) { 2967 return; 2968 } 2969 mFrame->NotifyGlyphMetricsChange(true); 2970 } 2971 2972 void SVGTextFrame::MutationObserver::CharacterDataChanged( 2973 nsIContent* aContent, const CharacterDataChangeInfo&) { 2974 mFrame->NotifyGlyphMetricsChange(true); 2975 } 2976 2977 void SVGTextFrame::MutationObserver::AttributeChanged( 2978 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, AttrModType, 2979 const nsAttrValue* aOldValue) { 2980 if (!aElement->IsSVGElement()) { 2981 return; 2982 } 2983 2984 // Attribute changes on this element will be handled by 2985 // SVGTextFrame::AttributeChanged. 2986 if (aElement == mFrame->GetContent()) { 2987 return; 2988 } 2989 2990 mFrame->HandleAttributeChangeInDescendant(aElement, aNameSpaceID, aAttribute); 2991 } 2992 2993 void SVGTextFrame::HandleAttributeChangeInDescendant(Element* aElement, 2994 int32_t aNameSpaceID, 2995 nsAtom* aAttribute) { 2996 if (aElement->IsSVGElement(nsGkAtoms::textPath)) { 2997 if (aNameSpaceID == kNameSpaceID_None && 2998 (aAttribute == nsGkAtoms::startOffset || 2999 aAttribute == nsGkAtoms::path || aAttribute == nsGkAtoms::side)) { 3000 NotifyGlyphMetricsChange(false); 3001 } else if ((aNameSpaceID == kNameSpaceID_XLink || 3002 aNameSpaceID == kNameSpaceID_None) && 3003 aAttribute == nsGkAtoms::href) { 3004 // Blow away our reference, if any 3005 nsIFrame* childElementFrame = aElement->GetPrimaryFrame(); 3006 if (childElementFrame) { 3007 SVGObserverUtils::RemoveTextPathObserver(childElementFrame); 3008 NotifyGlyphMetricsChange(false); 3009 } 3010 } 3011 } else { 3012 if (aNameSpaceID == kNameSpaceID_None && 3013 IsGlyphPositioningAttribute(aAttribute)) { 3014 NotifyGlyphMetricsChange(false); 3015 } 3016 } 3017 } 3018 3019 void SVGTextFrame::FindCloserFrameForSelection( 3020 const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) { 3021 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 3022 return; 3023 } 3024 3025 UpdateGlyphPositioning(); 3026 3027 nsPresContext* presContext = PresContext(); 3028 3029 // Find the frame that has the closest rendered run rect to aPoint. 3030 TextRenderedRunIterator it(this); 3031 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { 3032 uint32_t flags = TextRenderedRun::eIncludeFill | 3033 TextRenderedRun::eIncludeStroke | 3034 TextRenderedRun::eNoHorizontalOverflow; 3035 SVGBBox userRect = run.GetUserSpaceRect(presContext, flags); 3036 float devPxPerCSSPx = presContext->CSSPixelsToDevPixels(1.f); 3037 userRect.Scale(devPxPerCSSPx); 3038 3039 if (!userRect.IsEmpty()) { 3040 gfxMatrix m; 3041 nsRect rect = 3042 SVGUtils::ToCanvasBounds(userRect.ToThebesRect(), m, presContext); 3043 3044 if (nsLayoutUtils::PointIsCloserToRect(aPoint, rect, 3045 aCurrentBestFrame->mXDistance, 3046 aCurrentBestFrame->mYDistance)) { 3047 aCurrentBestFrame->mFrame = run.mFrame; 3048 } 3049 } 3050 } 3051 } 3052 3053 //---------------------------------------------------------------------- 3054 // ISVGDisplayableFrame methods 3055 3056 void SVGTextFrame::NotifySVGChanged(ChangeFlags aFlags) { 3057 MOZ_ASSERT(aFlags.contains(ChangeFlag::TransformChanged) || 3058 aFlags.contains(ChangeFlag::CoordContextChanged), 3059 "Invalidation logic may need adjusting"); 3060 3061 bool needNewBounds = false; 3062 bool needGlyphMetricsUpdate = false; 3063 if (aFlags.contains(ChangeFlag::CoordContextChanged) && 3064 HasAnyStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES)) { 3065 needGlyphMetricsUpdate = true; 3066 } 3067 3068 if (aFlags.contains(ChangeFlag::TransformChanged)) { 3069 if (mCanvasTM && mCanvasTM->IsSingular()) { 3070 // We won't have calculated the glyph positions correctly. 3071 needNewBounds = true; 3072 needGlyphMetricsUpdate = true; 3073 } 3074 mCanvasTM = nullptr; 3075 if (StyleSVGReset()->HasNonScalingStroke()) { 3076 // Stroke currently contributes to our mRect, and our stroke depends on 3077 // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|. 3078 needNewBounds = true; 3079 } 3080 3081 // If the scale at which we computed our mFontSizeScaleFactor has changed by 3082 // at least a factor of two, reflow the text. This avoids reflowing text at 3083 // every tick of a transform animation, but ensures our glyph metrics 3084 // do not get too far out of sync with the final font size on the screen. 3085 const float scale = GetContextScale(this); 3086 if (scale != mLastContextScale) { 3087 if (mLastContextScale == 0.0f) { 3088 needNewBounds = true; 3089 needGlyphMetricsUpdate = true; 3090 } else { 3091 float change = scale / mLastContextScale; 3092 if (change >= 2.0f || change <= 0.5f) { 3093 needNewBounds = true; 3094 needGlyphMetricsUpdate = true; 3095 } 3096 } 3097 } 3098 } 3099 3100 if (needNewBounds) { 3101 // Ancestor changes can't affect how we render from the perspective of 3102 // any rendering observers that we may have, so we don't need to 3103 // invalidate them. We also don't need to invalidate ourself, since our 3104 // changed ancestor will have invalidated its entire area, which includes 3105 // our area. 3106 ScheduleReflowSVG(); 3107 } 3108 3109 if (needGlyphMetricsUpdate) { 3110 // If we are positioned using percentage values we need to update our 3111 // position whenever our viewport's dimensions change. But only do this if 3112 // we have been reflowed once, otherwise the glyph positioning will be 3113 // wrong. (We need to wait until bidi reordering has been done.) 3114 if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 3115 NotifyGlyphMetricsChange(false); 3116 } 3117 } 3118 } 3119 3120 /** 3121 * Gets the offset into a DOM node that the specified caret is positioned at. 3122 */ 3123 static int32_t GetCaretOffset(nsCaret* aCaret) { 3124 RefPtr<Selection> selection = aCaret->GetSelection(); 3125 if (!selection) { 3126 return -1; 3127 } 3128 3129 return selection->AnchorOffset(); 3130 } 3131 3132 /** 3133 * Returns whether the caret should be painted for a given TextRenderedRun 3134 * by checking whether the caret is in the range covered by the rendered run. 3135 * 3136 * @param aThisRun The TextRenderedRun to be painted. 3137 * @param aCaret The caret. 3138 */ 3139 static bool ShouldPaintCaret(const TextRenderedRun& aThisRun, nsCaret* aCaret) { 3140 int32_t caretOffset = GetCaretOffset(aCaret); 3141 3142 if (caretOffset < 0) { 3143 return false; 3144 } 3145 3146 return uint32_t(caretOffset) >= aThisRun.mTextFrameContentOffset && 3147 uint32_t(caretOffset) < aThisRun.mTextFrameContentOffset + 3148 aThisRun.mTextFrameContentLength; 3149 } 3150 3151 void SVGTextFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform, 3152 imgDrawingParams& aImgParams) { 3153 DrawTarget& aDrawTarget = *aContext.GetDrawTarget(); 3154 nsIFrame* kid = PrincipalChildList().FirstChild(); 3155 if (!kid) { 3156 return; 3157 } 3158 3159 if (IsSubtreeDirty()) { 3160 return; 3161 } 3162 3163 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 3164 // Text frames inside <clipPath>, <mask>, etc. will never have had 3165 // ReflowSVG called on them, so call UpdateGlyphPositioning to do this now. 3166 UpdateGlyphPositioning(); 3167 } 3168 3169 const float epsilon = 0.0001; 3170 if (abs(mLengthAdjustScaleFactor) < epsilon) { 3171 // A zero scale factor can be caused by having forced the text length to 3172 // zero. In this situation there is nothing to show. 3173 return; 3174 } 3175 3176 if (aTransform.IsSingular()) { 3177 NS_WARNING("Can't render text element!"); 3178 return; 3179 } 3180 3181 gfxMatrix initialMatrix = aContext.CurrentMatrixDouble(); 3182 3183 gfxMatrix matrixForPaintServers = aTransform * initialMatrix; 3184 3185 // SVG frames' PaintSVG methods paint in CSS px, but normally frames paint in 3186 // dev pixels. Here we multiply a CSS-px-to-dev-pixel factor onto aTransform 3187 // so our non-SVG nsTextFrame children paint correctly. 3188 nsPresContext* presContext = PresContext(); 3189 auto auPerDevPx = presContext->AppUnitsPerDevPixel(); 3190 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(auPerDevPx); 3191 gfxMatrix canvasTMForChildren = aTransform; 3192 canvasTMForChildren.PreScale(cssPxPerDevPx, cssPxPerDevPx); 3193 initialMatrix.PreScale(1 / cssPxPerDevPx, 1 / cssPxPerDevPx); 3194 3195 gfxContextMatrixAutoSaveRestore matSR(&aContext); 3196 aContext.NewPath(); 3197 aContext.Multiply(canvasTMForChildren); 3198 gfxMatrix currentMatrix = aContext.CurrentMatrixDouble(); 3199 3200 RefPtr<nsCaret> caret = presContext->PresShell()->GetCaret(); 3201 nsIFrame* caretFrame = caret->GetPaintGeometry(); 3202 3203 gfxContextAutoSaveRestore ctxSR; 3204 TextRenderedRunIterator it(this, TextRenderedRunIterator::eVisibleFrames); 3205 TextRenderedRun run = it.Current(); 3206 3207 SVGContextPaint* outerContextPaint = 3208 SVGContextPaint::GetContextPaint(GetContent()); 3209 3210 while (run.mFrame) { 3211 nsTextFrame* frame = run.mFrame; 3212 3213 auto contextPaint = MakeRefPtr<SVGContextPaintImpl>(); 3214 DrawMode drawMode = contextPaint->Init(&aDrawTarget, initialMatrix, frame, 3215 outerContextPaint, aImgParams); 3216 if (drawMode & DrawMode::GLYPH_STROKE) { 3217 ctxSR.EnsureSaved(&aContext); 3218 // This may change the gfxContext's transform (for non-scaling stroke), 3219 // in which case this needs to happen before we call SetMatrix() below. 3220 SVGUtils::SetupStrokeGeometry(frame->GetParent(), &aContext, 3221 outerContextPaint); 3222 } 3223 3224 nscoord startEdge, endEdge; 3225 run.GetClipEdges(startEdge, endEdge); 3226 3227 // Set up the transform for painting the text frame for the substring 3228 // indicated by the run. 3229 gfxMatrix runTransform = run.GetTransformFromUserSpaceForPainting( 3230 presContext, startEdge, endEdge) * 3231 currentMatrix; 3232 aContext.SetMatrixDouble(runTransform); 3233 3234 if (drawMode != DrawMode(0)) { 3235 bool paintSVGGlyphs; 3236 nsTextFrame::PaintTextParams params(&aContext); 3237 params.framePt = Point(); 3238 params.dirtyRect = 3239 LayoutDevicePixel::FromAppUnits(frame->InkOverflowRect(), auPerDevPx); 3240 params.contextPaint = contextPaint; 3241 bool isSelected; 3242 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) { 3243 params.state = nsTextFrame::PaintTextParams::GenerateTextMask; 3244 isSelected = false; 3245 } else { 3246 isSelected = frame->IsSelected(); 3247 } 3248 gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aContext); 3249 float opacity = 1.0f; 3250 nsIFrame* ancestor = frame->GetParent(); 3251 while (ancestor != this) { 3252 opacity *= ancestor->StyleEffects()->mOpacity; 3253 ancestor = ancestor->GetParent(); 3254 } 3255 if (opacity < 1.0f) { 3256 autoGroupForBlend.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 3257 opacity); 3258 } 3259 3260 if (ShouldRenderAsPath(frame, outerContextPaint, paintSVGGlyphs)) { 3261 SVGTextDrawPathCallbacks callbacks(this, outerContextPaint, aContext, 3262 frame, matrixForPaintServers, 3263 aImgParams, paintSVGGlyphs); 3264 params.callbacks = &callbacks; 3265 frame->PaintText(params, startEdge, endEdge, nsPoint(), isSelected); 3266 } else { 3267 frame->PaintText(params, startEdge, endEdge, nsPoint(), isSelected); 3268 } 3269 } 3270 3271 if (frame == caretFrame && ShouldPaintCaret(run, caret)) { 3272 // XXX Should we be looking at the fill/stroke colours to paint the 3273 // caret with, rather than using the color property? 3274 caret->PaintCaret(aDrawTarget, frame, nsPoint()); 3275 aContext.NewPath(); 3276 } 3277 3278 run = it.Next(); 3279 } 3280 } 3281 3282 nsIFrame* SVGTextFrame::GetFrameForPoint(const gfxPoint& aPoint) { 3283 NS_ASSERTION(PrincipalChildList().FirstChild(), "must have a child frame"); 3284 3285 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 3286 // Text frames inside <clipPath> will never have had ReflowSVG called on 3287 // them, so call UpdateGlyphPositioning to do this now. (Text frames 3288 // inside <mask> and other non-display containers will never need to 3289 // be hit tested.) 3290 UpdateGlyphPositioning(); 3291 } else { 3292 NS_ASSERTION(!IsSubtreeDirty(), "reflow should have happened"); 3293 } 3294 3295 // Hit-testing any clip-path will typically be a lot quicker than the 3296 // hit-testing of our text frames in the loop below, so we do the former up 3297 // front to avoid unnecessarily wasting cycles on the latter. 3298 if (!SVGUtils::HitTestClip(this, aPoint)) { 3299 return nullptr; 3300 } 3301 3302 nsPresContext* presContext = PresContext(); 3303 3304 // Ideally we'd iterate backwards so that we can just return the first frame 3305 // that is under aPoint. In practice this will rarely matter though since it 3306 // is rare for text in/under an SVG <text> element to overlap (i.e. the first 3307 // text frame that is hit will likely be the only text frame that is hit). 3308 3309 TextRenderedRunIterator it(this); 3310 nsIFrame* hit = nullptr; 3311 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { 3312 uint16_t hitTestFlags = SVGUtils::GetGeometryHitTestFlags(run.mFrame); 3313 if (!hitTestFlags) { 3314 continue; 3315 } 3316 3317 gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext); 3318 if (!m.Invert()) { 3319 return nullptr; 3320 } 3321 3322 gfxPoint pointInRunUserSpace = m.TransformPoint(aPoint); 3323 gfxRect frameRect = run.GetRunUserSpaceRect(TextRenderedRun::eIncludeFill | 3324 TextRenderedRun::eIncludeStroke) 3325 .ToThebesRect(); 3326 3327 if (frameRect.Contains(pointInRunUserSpace)) { 3328 hit = run.mFrame; 3329 } 3330 } 3331 return hit; 3332 } 3333 3334 void SVGTextFrame::ReflowSVG() { 3335 MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this), 3336 "This call is probaby a wasteful mistake"); 3337 3338 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), 3339 "ReflowSVG mechanism not designed for this"); 3340 3341 if (!SVGUtils::NeedsReflowSVG(this)) { 3342 MOZ_ASSERT(!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | 3343 NS_STATE_SVG_POSITIONING_DIRTY), 3344 "How did this happen?"); 3345 return; 3346 } 3347 3348 MaybeReflowAnonymousBlockChild(); 3349 UpdateGlyphPositioning(); 3350 3351 nsPresContext* presContext = PresContext(); 3352 3353 SVGBBox r; 3354 TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames); 3355 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { 3356 uint32_t runFlags = 0; 3357 if (!run.mFrame->StyleSVG()->mFill.kind.IsNone()) { 3358 runFlags |= TextRenderedRun::eIncludeFill; 3359 } 3360 if (SVGUtils::HasStroke(run.mFrame)) { 3361 runFlags |= TextRenderedRun::eIncludeStroke; 3362 } 3363 // Our "visual" overflow rect needs to be valid for building display lists 3364 // for hit testing, which means that for certain values of 'pointer-events' 3365 // it needs to include the geometry of the fill or stroke even when the 3366 // fill/ stroke don't actually render (e.g. when stroke="none" or 3367 // stroke-opacity="0"). GetGeometryHitTestFlags accounts for 3368 // 'pointer-events'. The text-shadow is not part of the hit-test area. 3369 uint16_t hitTestFlags = SVGUtils::GetGeometryHitTestFlags(run.mFrame); 3370 if (hitTestFlags & SVG_HIT_TEST_FILL) { 3371 runFlags |= TextRenderedRun::eIncludeFill; 3372 } 3373 if (hitTestFlags & SVG_HIT_TEST_STROKE) { 3374 runFlags |= TextRenderedRun::eIncludeStroke; 3375 } 3376 3377 if (runFlags) { 3378 r.UnionEdges(run.GetUserSpaceRect(presContext, runFlags)); 3379 } 3380 } 3381 3382 if (r.IsEmpty()) { 3383 mRect.SetEmpty(); 3384 } else { 3385 mRect = nsLayoutUtils::RoundGfxRectToAppRect((const Rect&)r, 3386 AppUnitsPerCSSPixel()); 3387 3388 // Due to rounding issues when we have a transform applied, we sometimes 3389 // don't include an additional row of pixels. For now, just inflate our 3390 // covered region. 3391 if (mLastContextScale != 0.0f) { 3392 mRect.Inflate( 3393 ceil(presContext->AppUnitsPerDevPixel() / mLastContextScale)); 3394 } 3395 } 3396 3397 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 3398 // Make sure we have our filter property (if any) before calling 3399 // FinishAndStoreOverflow (subsequent filter changes are handled off 3400 // nsChangeHint_UpdateEffects): 3401 SVGObserverUtils::UpdateEffects(this); 3402 } 3403 3404 // Now unset the various reflow bits. Do this before calling 3405 // FinishAndStoreOverflow since FinishAndStoreOverflow can require glyph 3406 // positions (to resolve transform-origin). 3407 RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | 3408 NS_FRAME_HAS_DIRTY_CHILDREN); 3409 3410 nsRect overflow = nsRect(nsPoint(0, 0), mRect.Size()); 3411 OverflowAreas overflowAreas(overflow, overflow); 3412 FinishAndStoreOverflow(overflowAreas, mRect.Size()); 3413 } 3414 3415 /** 3416 * Converts SVGUtils::eBBox* flags into TextRenderedRun flags appropriate 3417 * for the specified rendered run. 3418 */ 3419 static uint32_t TextRenderedRunFlagsForBBoxContribution( 3420 const TextRenderedRun& aRun, uint32_t aBBoxFlags) { 3421 uint32_t flags = 0; 3422 if ((aBBoxFlags & SVGUtils::eBBoxIncludeFillGeometry) || 3423 ((aBBoxFlags & SVGUtils::eBBoxIncludeFill) && 3424 !aRun.mFrame->StyleSVG()->mFill.kind.IsNone())) { 3425 flags |= TextRenderedRun::eIncludeFill; 3426 } 3427 if ((aBBoxFlags & SVGUtils::eBBoxIncludeStrokeGeometry) || 3428 ((aBBoxFlags & SVGUtils::eBBoxIncludeStroke) && 3429 SVGUtils::HasStroke(aRun.mFrame))) { 3430 flags |= TextRenderedRun::eIncludeStroke; 3431 } 3432 return flags; 3433 } 3434 3435 SVGBBox SVGTextFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace, 3436 uint32_t aFlags) { 3437 NS_ASSERTION(PrincipalChildList().FirstChild(), "must have a child frame"); 3438 SVGBBox bbox; 3439 3440 if (aFlags & SVGUtils::eForGetClientRects) { 3441 if (!mRect.IsEmpty()) { 3442 Rect rect = NSRectToRect(mRect, AppUnitsPerCSSPixel()); 3443 bbox = aToBBoxUserspace.TransformBounds(rect); 3444 } 3445 return bbox; 3446 } 3447 3448 nsIFrame* kid = PrincipalChildList().FirstChild(); 3449 if (kid && kid->IsSubtreeDirty()) { 3450 // Return an empty bbox if our kid's subtree is dirty. This may be called 3451 // in that situation, e.g. when we're building a display list after an 3452 // interrupted reflow. This can also be called during reflow before we've 3453 // been reflowed, e.g. if an earlier sibling is calling 3454 // FinishAndStoreOverflow and needs our parent's perspective matrix, which 3455 // depends on the SVG bbox contribution of this frame. In the latter 3456 // situation, when all siblings have been reflowed, the parent will compute 3457 // its perspective and rerun FinishAndStoreOverflow for all its children. 3458 return bbox; 3459 } 3460 3461 UpdateGlyphPositioning(); 3462 3463 nsPresContext* presContext = PresContext(); 3464 3465 TextRenderedRunIterator it(this); 3466 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { 3467 uint32_t flags = TextRenderedRunFlagsForBBoxContribution(run, aFlags); 3468 gfxMatrix m = ThebesMatrix(aToBBoxUserspace); 3469 SVGBBox bboxForRun = run.GetUserSpaceRect(presContext, flags, &m); 3470 bbox.UnionEdges(bboxForRun); 3471 } 3472 3473 return bbox; 3474 } 3475 3476 //---------------------------------------------------------------------- 3477 // SVGTextFrame SVG DOM methods 3478 3479 /** 3480 * Returns whether the specified node has any non-empty Text 3481 * beneath it. 3482 */ 3483 static bool HasTextContent(nsIContent* aContent) { 3484 NS_ASSERTION(aContent, "expected non-null aContent"); 3485 3486 TextNodeIterator it(aContent); 3487 for (Text* text = it.GetCurrent(); text; text = it.GetNext()) { 3488 if (text->TextLength() != 0) { 3489 return true; 3490 } 3491 } 3492 return false; 3493 } 3494 3495 /** 3496 * Returns the number of DOM characters beneath the specified node. 3497 */ 3498 static uint32_t GetTextContentLength(nsIContent* aContent) { 3499 NS_ASSERTION(aContent, "expected non-null aContent"); 3500 3501 uint32_t length = 0; 3502 TextNodeIterator it(aContent); 3503 for (Text* text = it.GetCurrent(); text; text = it.GetNext()) { 3504 length += text->TextLength(); 3505 } 3506 return length; 3507 } 3508 3509 int32_t SVGTextFrame::ConvertTextElementCharIndexToAddressableIndex( 3510 int32_t aIndex, nsIContent* aContent) { 3511 CharIterator it(this, CharIterator::eOriginal, aContent); 3512 if (!it.AdvanceToSubtree()) { 3513 return -1; 3514 } 3515 int32_t result = 0; 3516 int32_t textElementCharIndex; 3517 while (!it.AtEnd() && it.IsWithinSubtree()) { 3518 bool addressable = !it.IsOriginalCharUnaddressable(); 3519 textElementCharIndex = it.TextElementCharIndex(); 3520 it.Next(); 3521 uint32_t delta = it.TextElementCharIndex() - textElementCharIndex; 3522 aIndex -= delta; 3523 if (addressable) { 3524 if (aIndex < 0) { 3525 return result; 3526 } 3527 result += delta; 3528 } 3529 } 3530 return -1; 3531 } 3532 3533 /** 3534 * Implements the SVG DOM GetNumberOfChars method for the specified 3535 * text content element. 3536 */ 3537 uint32_t SVGTextFrame::GetNumberOfChars(nsIContent* aContent) { 3538 nsIFrame* kid = PrincipalChildList().FirstChild(); 3539 if (kid->IsSubtreeDirty()) { 3540 // We're never reflowed if we're under a non-SVG element that is 3541 // never reflowed (such as the HTML 'caption' element). 3542 return 0; 3543 } 3544 3545 UpdateGlyphPositioning(); 3546 3547 uint32_t n = 0; 3548 CharIterator it(this, CharIterator::eAddressable, aContent); 3549 if (it.AdvanceToSubtree()) { 3550 while (!it.AtEnd() && it.IsWithinSubtree()) { 3551 n++; 3552 it.Next(); 3553 } 3554 } 3555 return n; 3556 } 3557 3558 /** 3559 * Implements the SVG DOM GetComputedTextLength method for the specified 3560 * text child element. 3561 */ 3562 float SVGTextFrame::GetComputedTextLength(nsIContent* aContent) { 3563 nsIFrame* kid = PrincipalChildList().FirstChild(); 3564 if (kid->IsSubtreeDirty()) { 3565 // We're never reflowed if we're under a non-SVG element that is 3566 // never reflowed (such as the HTML 'caption' element). 3567 // 3568 // If we ever decide that we need to return accurate values here, 3569 // we could do similar work to GetSubStringLength. 3570 return 0; 3571 } 3572 3573 UpdateGlyphPositioning(); 3574 3575 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels( 3576 PresContext()->AppUnitsPerDevPixel()); 3577 3578 nscoord length = 0; 3579 TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, 3580 aContent); 3581 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { 3582 length += run.GetAdvanceWidth(); 3583 } 3584 3585 return PresContext()->AppUnitsToGfxUnits(length) * cssPxPerDevPx * 3586 mLengthAdjustScaleFactor / mFontSizeScaleFactor; 3587 } 3588 3589 /** 3590 * Implements the SVG DOM SelectSubString method for the specified 3591 * text content element. 3592 */ 3593 void SVGTextFrame::SelectSubString(nsIContent* aContent, uint32_t charnum, 3594 uint32_t nchars, ErrorResult& aRv) { 3595 nsIFrame* kid = PrincipalChildList().FirstChild(); 3596 if (kid->IsSubtreeDirty()) { 3597 // We're never reflowed if we're under a non-SVG element that is 3598 // never reflowed (such as the HTML 'caption' element). 3599 // XXXbz Should this just return without throwing like the no-frame case? 3600 aRv.ThrowInvalidStateError("No layout information available for SVG text"); 3601 return; 3602 } 3603 3604 UpdateGlyphPositioning(); 3605 3606 RefPtr<nsIContent> content; 3607 3608 // Ensure the destructor of CharIterator runs before calling HandleClick. 3609 { 3610 // Convert charnum/nchars from addressable characters relative to 3611 // aContent to global character indices. 3612 CharIterator chit(this, CharIterator::eAddressable, aContent); 3613 if (!chit.AdvanceToSubtree() || !chit.Next(charnum) || 3614 chit.IsAfterSubtree()) { 3615 aRv.ThrowIndexSizeError("Character index out of range"); 3616 return; 3617 } 3618 charnum = chit.TextElementCharIndex(); 3619 content = chit.GetTextFrame()->GetContent(); 3620 chit.NextWithinSubtree(nchars); 3621 nchars = chit.TextElementCharIndex() - charnum; 3622 } 3623 3624 RefPtr<nsFrameSelection> frameSelection = GetFrameSelection(); 3625 3626 frameSelection->HandleClick(content, charnum, charnum + nchars, 3627 nsFrameSelection::FocusMode::kCollapseToNewPoint, 3628 CaretAssociationHint::Before); 3629 } 3630 3631 /** 3632 * For some content we cannot (or currently cannot) compute the length 3633 * without reflowing. In those cases we need to fall back to using 3634 * GetSubStringLengthSlowFallback. 3635 * 3636 * We fall back for textPath since we need glyph positioning in order to 3637 * tell if any characters should be ignored due to having fallen off the 3638 * end of the textPath. 3639 * 3640 * We fall back for bidi because GetTrimmedOffsets does not produce the 3641 * correct results for bidi continuations when passed aPostReflow = false. 3642 * XXX It may be possible to determine which continuations to trim from (and 3643 * which sides), but currently we don't do that. It would require us to 3644 * identify the visual (rather than logical) start and end of the line, to 3645 * avoid trimming at line-internal frame boundaries. Maybe nsBidiPresUtils 3646 * methods like GetFrameToRightOf and GetFrameToLeftOf would help? 3647 * 3648 */ 3649 bool SVGTextFrame::RequiresSlowFallbackForSubStringLength() { 3650 TextFrameIterator frameIter(this); 3651 for (nsTextFrame* frame = frameIter.GetCurrent(); frame; 3652 frame = frameIter.GetNext()) { 3653 if (frameIter.TextPathFrame() || frame->GetNextContinuation()) { 3654 return true; 3655 } 3656 } 3657 return false; 3658 } 3659 3660 /** 3661 * Implements the SVG DOM GetSubStringLength method for the specified 3662 * text content element. 3663 */ 3664 float SVGTextFrame::GetSubStringLengthFastPath(nsIContent* aContent, 3665 uint32_t charnum, 3666 uint32_t nchars, 3667 ErrorResult& aRv) { 3668 MOZ_ASSERT(!RequiresSlowFallbackForSubStringLength()); 3669 3670 // We only need our text correspondence to be up to date (no need to call 3671 // UpdateGlyphPositioning). 3672 TextNodeCorrespondenceRecorder::RecordCorrespondence(this); 3673 3674 // Convert charnum/nchars from addressable characters relative to 3675 // aContent to global character indices. 3676 CharIterator chit(this, CharIterator::eAddressable, aContent, 3677 /* aPostReflow */ false); 3678 if (!chit.AdvanceToSubtree() || !chit.Next(charnum) || 3679 chit.IsAfterSubtree()) { 3680 aRv.ThrowIndexSizeError("Character index out of range"); 3681 return 0; 3682 } 3683 3684 // We do this after the ThrowIndexSizeError() bit so JS calls correctly throw 3685 // when necessary. 3686 if (nchars == 0) { 3687 return 0.0f; 3688 } 3689 3690 charnum = chit.TextElementCharIndex(); 3691 chit.NextWithinSubtree(nchars); 3692 nchars = chit.TextElementCharIndex() - charnum; 3693 3694 // Sum of the substring advances. 3695 nscoord textLength = 0; 3696 3697 TextFrameIterator frit(this); // aSubtree = nullptr 3698 3699 // Index of the first non-skipped char in the frame, and of a subsequent char 3700 // that we're interested in. Both are relative to the index of the first 3701 // non-skipped char in the ancestor <text> element. 3702 uint32_t frameStartTextElementCharIndex = 0; 3703 uint32_t textElementCharIndex; 3704 3705 for (nsTextFrame* frame = frit.GetCurrent(); frame; frame = frit.GetNext()) { 3706 frameStartTextElementCharIndex += frit.UndisplayedCharacters(); 3707 textElementCharIndex = frameStartTextElementCharIndex; 3708 3709 // Offset into frame's Text: 3710 const uint32_t untrimmedOffset = frame->GetContentOffset(); 3711 const uint32_t untrimmedLength = frame->GetContentEnd() - untrimmedOffset; 3712 3713 // Trim the offset/length to remove any leading/trailing white space. 3714 uint32_t trimmedOffset = untrimmedOffset; 3715 uint32_t trimmedLength = untrimmedLength; 3716 nsTextFrame::TrimmedOffsets trimmedOffsets = frame->GetTrimmedOffsets( 3717 frame->CharacterDataBuffer(), 3718 nsTextFrame::TrimmedOffsetFlags::NotPostReflow); 3719 TrimOffsets(trimmedOffset, trimmedLength, trimmedOffsets); 3720 3721 textElementCharIndex += trimmedOffset - untrimmedOffset; 3722 3723 if (textElementCharIndex >= charnum + nchars) { 3724 break; // we're past the end of the substring 3725 } 3726 3727 uint32_t offset = textElementCharIndex; 3728 3729 // Intersect the substring we are interested in with the range covered by 3730 // the nsTextFrame. 3731 IntersectInterval(offset, trimmedLength, charnum, nchars); 3732 3733 if (trimmedLength != 0) { 3734 // Convert offset into an index into the frame. 3735 offset += trimmedOffset - textElementCharIndex; 3736 3737 gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated); 3738 gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated); 3739 auto& provider = PropertyProviderFor(frame); 3740 3741 Range range = ConvertOriginalToSkipped(it, offset, trimmedLength); 3742 3743 // Accumulate the advance. 3744 textLength += textRun->GetAdvanceWidth(range, &provider); 3745 } 3746 3747 // Advance, ready for next call: 3748 frameStartTextElementCharIndex += untrimmedLength; 3749 } 3750 3751 nsPresContext* presContext = PresContext(); 3752 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels( 3753 presContext->AppUnitsPerDevPixel()); 3754 3755 return presContext->AppUnitsToGfxUnits(textLength) * cssPxPerDevPx / 3756 mFontSizeScaleFactor; 3757 } 3758 3759 float SVGTextFrame::GetSubStringLengthSlowFallback(nsIContent* aContent, 3760 uint32_t charnum, 3761 uint32_t nchars, 3762 ErrorResult& aRv) { 3763 UpdateGlyphPositioning(); 3764 3765 // Convert charnum/nchars from addressable characters relative to 3766 // aContent to global character indices. 3767 CharIterator chit(this, CharIterator::eAddressable, aContent); 3768 if (!chit.AdvanceToSubtree() || !chit.Next(charnum) || 3769 chit.IsAfterSubtree()) { 3770 aRv.ThrowIndexSizeError("Character index out of range"); 3771 return 0; 3772 } 3773 3774 if (nchars == 0) { 3775 return 0.0f; 3776 } 3777 3778 charnum = chit.TextElementCharIndex(); 3779 chit.NextWithinSubtree(nchars); 3780 nchars = chit.TextElementCharIndex() - charnum; 3781 3782 // Find each rendered run that intersects with the range defined 3783 // by charnum/nchars. 3784 nscoord textLength = 0; 3785 TextRenderedRunIterator runIter(this, TextRenderedRunIterator::eAllFrames); 3786 TextRenderedRun run = runIter.Current(); 3787 while (run.mFrame) { 3788 // If this rendered run is past the substring we are interested in, we 3789 // are done. 3790 uint32_t offset = run.mTextElementCharIndex; 3791 if (offset >= charnum + nchars) { 3792 break; 3793 } 3794 3795 // Intersect the substring we are interested in with the range covered by 3796 // the rendered run. 3797 uint32_t length = run.mTextFrameContentLength; 3798 IntersectInterval(offset, length, charnum, nchars); 3799 3800 if (length != 0) { 3801 // Convert offset into an index into the frame. 3802 offset += run.mTextFrameContentOffset - run.mTextElementCharIndex; 3803 3804 gfxSkipCharsIterator it = 3805 run.mFrame->EnsureTextRun(nsTextFrame::eInflated); 3806 gfxTextRun* textRun = run.mFrame->GetTextRun(nsTextFrame::eInflated); 3807 auto& provider = PropertyProviderFor(run.mFrame); 3808 3809 Range range = ConvertOriginalToSkipped(it, offset, length); 3810 3811 // Accumulate the advance. 3812 textLength += textRun->GetAdvanceWidth(range, &provider); 3813 } 3814 3815 run = runIter.Next(); 3816 } 3817 3818 nsPresContext* presContext = PresContext(); 3819 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels( 3820 presContext->AppUnitsPerDevPixel()); 3821 3822 return presContext->AppUnitsToGfxUnits(textLength) * cssPxPerDevPx / 3823 mFontSizeScaleFactor; 3824 } 3825 3826 /** 3827 * Implements the SVG DOM GetCharNumAtPosition method for the specified 3828 * text content element. 3829 */ 3830 int32_t SVGTextFrame::GetCharNumAtPosition(nsIContent* aContent, 3831 const DOMPointInit& aPoint) { 3832 nsIFrame* kid = PrincipalChildList().FirstChild(); 3833 if (kid->IsSubtreeDirty()) { 3834 // We're never reflowed if we're under a non-SVG element that is 3835 // never reflowed (such as the HTML 'caption' element). 3836 return -1; 3837 } 3838 3839 UpdateGlyphPositioning(); 3840 3841 nsPresContext* context = PresContext(); 3842 3843 gfxPoint p(aPoint.mX, aPoint.mY); 3844 3845 int32_t result = -1; 3846 3847 TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, 3848 aContent); 3849 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { 3850 // Hit test this rendered run. Later runs will override earlier ones. 3851 int32_t index = run.GetCharNumAtPosition(context, p); 3852 if (index != -1) { 3853 result = index + run.mTextElementCharIndex; 3854 } 3855 } 3856 3857 if (result == -1) { 3858 return result; 3859 } 3860 3861 return ConvertTextElementCharIndexToAddressableIndex(result, aContent); 3862 } 3863 3864 /** 3865 * Implements the SVG DOM GetStartPositionOfChar method for the specified 3866 * text content element. 3867 */ 3868 already_AddRefed<DOMSVGPoint> SVGTextFrame::GetStartPositionOfChar( 3869 nsIContent* aContent, uint32_t aCharNum, ErrorResult& aRv) { 3870 nsIFrame* kid = PrincipalChildList().FirstChild(); 3871 if (kid->IsSubtreeDirty()) { 3872 // We're never reflowed if we're under a non-SVG element that is 3873 // never reflowed (such as the HTML 'caption' element). 3874 aRv.ThrowInvalidStateError("No layout information available for SVG text"); 3875 return nullptr; 3876 } 3877 3878 UpdateGlyphPositioning(); 3879 3880 CharIterator it(this, CharIterator::eAddressable, aContent); 3881 if (!it.AdvanceToSubtree() || !it.Next(aCharNum)) { 3882 aRv.ThrowIndexSizeError("Character index out of range"); 3883 return nullptr; 3884 } 3885 3886 // We need to return the start position of the whole glyph. 3887 uint32_t startIndex = it.GlyphStartTextElementCharIndex(); 3888 3889 return do_AddRef(new DOMSVGPoint(ToPoint(mPositions[startIndex].mPosition))); 3890 } 3891 3892 /** 3893 * Returns the advance of the entire glyph whose starting character is at 3894 * aTextElementCharIndex. 3895 * 3896 * aIterator, if provided, must be a CharIterator that already points to 3897 * aTextElementCharIndex that is restricted to aContent and is using 3898 * filter mode eAddressable. 3899 */ 3900 static gfxFloat GetGlyphAdvance(SVGTextFrame* aFrame, nsIContent* aContent, 3901 uint32_t aTextElementCharIndex, 3902 CharIterator* aIterator) { 3903 MOZ_ASSERT(!aIterator || (aIterator->Filter() == CharIterator::eAddressable && 3904 aIterator->GetSubtree() == aContent && 3905 aIterator->GlyphStartTextElementCharIndex() == 3906 aTextElementCharIndex), 3907 "Invalid aIterator"); 3908 3909 Maybe<CharIterator> newIterator; 3910 CharIterator* it = aIterator; 3911 if (!it) { 3912 newIterator.emplace(aFrame, CharIterator::eAddressable, aContent); 3913 if (!newIterator->AdvanceToSubtree()) { 3914 MOZ_ASSERT_UNREACHABLE("Invalid aContent"); 3915 return 0.0; 3916 } 3917 it = newIterator.ptr(); 3918 } 3919 3920 while (it->GlyphStartTextElementCharIndex() != aTextElementCharIndex) { 3921 if (!it->Next()) { 3922 MOZ_ASSERT_UNREACHABLE("Invalid aTextElementCharIndex"); 3923 return 0.0; 3924 } 3925 } 3926 3927 if (it->AtEnd()) { 3928 MOZ_ASSERT_UNREACHABLE("Invalid aTextElementCharIndex"); 3929 return 0.0; 3930 } 3931 3932 nsPresContext* presContext = aFrame->PresContext(); 3933 gfxFloat advance = 0.0; 3934 3935 for (;;) { 3936 advance += it->GetAdvance(presContext); 3937 if (!it->Next() || 3938 it->GlyphStartTextElementCharIndex() != aTextElementCharIndex) { 3939 break; 3940 } 3941 } 3942 3943 return advance; 3944 } 3945 3946 /** 3947 * Implements the SVG DOM GetEndPositionOfChar method for the specified 3948 * text content element. 3949 */ 3950 already_AddRefed<DOMSVGPoint> SVGTextFrame::GetEndPositionOfChar( 3951 nsIContent* aContent, uint32_t aCharNum, ErrorResult& aRv) { 3952 nsIFrame* kid = PrincipalChildList().FirstChild(); 3953 if (kid->IsSubtreeDirty()) { 3954 // We're never reflowed if we're under a non-SVG element that is 3955 // never reflowed (such as the HTML 'caption' element). 3956 aRv.ThrowInvalidStateError("No layout information available for SVG text"); 3957 return nullptr; 3958 } 3959 3960 UpdateGlyphPositioning(); 3961 3962 CharIterator it(this, CharIterator::eAddressable, aContent); 3963 if (!it.AdvanceToSubtree() || !it.Next(aCharNum)) { 3964 aRv.ThrowIndexSizeError("Character index out of range"); 3965 return nullptr; 3966 } 3967 3968 // We need to return the end position of the whole glyph. 3969 uint32_t startIndex = it.GlyphStartTextElementCharIndex(); 3970 3971 // Get the advance of the glyph. 3972 gfxFloat advance = 3973 GetGlyphAdvance(this, aContent, startIndex, 3974 it.IsClusterAndLigatureGroupStart() ? &it : nullptr) / 3975 mFontSizeScaleFactor; 3976 const gfxTextRun* textRun = it.TextRun(); 3977 if (textRun->IsInlineReversed()) { 3978 advance = -advance; 3979 } 3980 Point p = textRun->IsVertical() ? Point(0, advance) : Point(advance, 0); 3981 3982 // The end position is the start position plus the advance in the direction 3983 // of the glyph's rotation. 3984 Matrix m = Matrix::Rotation(mPositions[startIndex].mAngle) * 3985 Matrix::Translation(ToPoint(mPositions[startIndex].mPosition)); 3986 3987 return do_AddRef(new DOMSVGPoint(m.TransformPoint(p))); 3988 } 3989 3990 /** 3991 * Implements the SVG DOM GetExtentOfChar method for the specified 3992 * text content element. 3993 */ 3994 already_AddRefed<SVGRect> SVGTextFrame::GetExtentOfChar(nsIContent* aContent, 3995 uint32_t aCharNum, 3996 ErrorResult& aRv) { 3997 nsIFrame* kid = PrincipalChildList().FirstChild(); 3998 if (kid->IsSubtreeDirty()) { 3999 // We're never reflowed if we're under a non-SVG element that is 4000 // never reflowed (such as the HTML 'caption' element). 4001 aRv.ThrowInvalidStateError("No layout information available for SVG text"); 4002 return nullptr; 4003 } 4004 4005 UpdateGlyphPositioning(); 4006 4007 // Search for the character whose addressable index is aCharNum. 4008 CharIterator it(this, CharIterator::eAddressable, aContent); 4009 if (!it.AdvanceToSubtree() || !it.Next(aCharNum)) { 4010 aRv.ThrowIndexSizeError("Character index out of range"); 4011 return nullptr; 4012 } 4013 4014 nsPresContext* presContext = PresContext(); 4015 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels( 4016 presContext->AppUnitsPerDevPixel()); 4017 4018 nsTextFrame* textFrame = it.GetTextFrame(); 4019 uint32_t startIndex = it.GlyphStartTextElementCharIndex(); 4020 const gfxTextRun* textRun = it.TextRun(); 4021 4022 // Get the glyph advance. 4023 gfxFloat advance = 4024 GetGlyphAdvance(this, aContent, startIndex, 4025 it.IsClusterAndLigatureGroupStart() ? &it : nullptr); 4026 gfxFloat x = textRun->IsInlineReversed() ? -advance : 0.0; 4027 4028 // The ascent and descent gives the height of the glyph. 4029 gfxFloat ascent, descent; 4030 GetAscentAndDescentInAppUnits(textFrame, ascent, descent); 4031 4032 // The horizontal extent is the origin of the glyph plus the advance 4033 // in the direction of the glyph's rotation. 4034 gfxMatrix m; 4035 m.PreTranslate(mPositions[startIndex].mPosition); 4036 m.PreRotate(mPositions[startIndex].mAngle); 4037 m.PreScale(1 / mFontSizeScaleFactor, 1 / mFontSizeScaleFactor); 4038 4039 nscoord baseline = GetBaselinePosition( 4040 textFrame, textRun, it.DominantBaseline(), mFontSizeScaleFactor); 4041 4042 gfxRect glyphRect; 4043 if (textRun->IsVertical()) { 4044 glyphRect = gfxRect( 4045 -presContext->AppUnitsToGfxUnits(baseline) * cssPxPerDevPx, x, 4046 presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx, 4047 advance); 4048 } else { 4049 glyphRect = gfxRect( 4050 x, -presContext->AppUnitsToGfxUnits(baseline) * cssPxPerDevPx, advance, 4051 presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx); 4052 } 4053 4054 // Transform the glyph's rect into user space. 4055 gfxRect r = m.TransformBounds(glyphRect); 4056 4057 return do_AddRef(new SVGRect(aContent, ToRect(r))); 4058 } 4059 4060 /** 4061 * Implements the SVG DOM GetRotationOfChar method for the specified 4062 * text content element. 4063 */ 4064 float SVGTextFrame::GetRotationOfChar(nsIContent* aContent, uint32_t aCharNum, 4065 ErrorResult& aRv) { 4066 nsIFrame* kid = PrincipalChildList().FirstChild(); 4067 if (kid->IsSubtreeDirty()) { 4068 // We're never reflowed if we're under a non-SVG element that is 4069 // never reflowed (such as the HTML 'caption' element). 4070 aRv.ThrowInvalidStateError("No layout information available for SVG text"); 4071 return 0; 4072 } 4073 4074 UpdateGlyphPositioning(); 4075 4076 CharIterator it(this, CharIterator::eAddressable, aContent); 4077 if (!it.AdvanceToSubtree() || !it.Next(aCharNum)) { 4078 aRv.ThrowIndexSizeError("Character index out of range"); 4079 return 0; 4080 } 4081 4082 // We need to account for the glyph's underlying orientation. 4083 const gfxTextRun::GlyphRun& glyphRun = it.GlyphRun(); 4084 int32_t glyphOrientation = 4085 90 * (glyphRun.IsSidewaysRight() - glyphRun.IsSidewaysLeft()); 4086 4087 return mPositions[it.TextElementCharIndex()].mAngle * 180.0 / M_PI + 4088 glyphOrientation; 4089 } 4090 4091 //---------------------------------------------------------------------- 4092 // SVGTextFrame text layout methods 4093 4094 /** 4095 * Given the character position array before values have been filled in 4096 * to any unspecified positions, and an array of dx/dy values, returns whether 4097 * a character at a given index should start a new rendered run. 4098 * 4099 * @param aPositions The array of character positions before unspecified 4100 * positions have been filled in and dx/dy values have been added to them. 4101 * @param aDeltas The array of dx/dy values. 4102 * @param aIndex The character index in question. 4103 */ 4104 static bool ShouldStartRunAtIndex(const nsTArray<CharPosition>& aPositions, 4105 const nsTArray<gfxPoint>& aDeltas, 4106 uint32_t aIndex) { 4107 if (aIndex == 0) { 4108 return true; 4109 } 4110 4111 if (aIndex < aPositions.Length()) { 4112 // If an explicit x or y value was given, start a new run. 4113 if (aPositions[aIndex].IsXSpecified() || 4114 aPositions[aIndex].IsYSpecified()) { 4115 return true; 4116 } 4117 4118 // If a non-zero rotation was given, or the previous character had a non- 4119 // zero rotation, start a new run. 4120 if ((aPositions[aIndex].IsAngleSpecified() && 4121 aPositions[aIndex].mAngle != 0.0f) || 4122 (aPositions[aIndex - 1].IsAngleSpecified() && 4123 (aPositions[aIndex - 1].mAngle != 0.0f))) { 4124 return true; 4125 } 4126 } 4127 4128 if (aIndex < aDeltas.Length()) { 4129 // If a non-zero dx or dy value was given, start a new run. 4130 if (aDeltas[aIndex].x != 0.0 || aDeltas[aIndex].y != 0.0) { 4131 return true; 4132 } 4133 } 4134 4135 return false; 4136 } 4137 4138 bool SVGTextFrame::ResolvePositionsForNode(nsIContent* aContent, 4139 uint32_t& aIndex, bool aInTextPath, 4140 bool& aForceStartOfChunk, 4141 nsTArray<gfxPoint>& aDeltas) { 4142 if (aContent->IsText()) { 4143 // We found a text node. 4144 uint32_t length = aContent->AsText()->TextLength(); 4145 if (length) { 4146 uint32_t end = aIndex + length; 4147 if (MOZ_UNLIKELY(end > mPositions.Length())) { 4148 MOZ_ASSERT_UNREACHABLE( 4149 "length of mPositions does not match characters " 4150 "found by iterating content"); 4151 return false; 4152 } 4153 if (aForceStartOfChunk) { 4154 // Note this character as starting a new anchored chunk. 4155 mPositions[aIndex].mStartOfChunk = true; 4156 aForceStartOfChunk = false; 4157 } 4158 while (aIndex < end) { 4159 // Record whether each of these characters should start a new rendered 4160 // run. That is always the case for characters on a text path. 4161 // 4162 // Run boundaries due to rotate="" values are handled in 4163 // DoGlyphPositioning. 4164 if (aInTextPath || ShouldStartRunAtIndex(mPositions, aDeltas, aIndex)) { 4165 mPositions[aIndex].mRunBoundary = true; 4166 } 4167 aIndex++; 4168 } 4169 } 4170 return true; 4171 } 4172 4173 // Skip past elements that aren't text content elements. 4174 if (!IsTextContentElement(aContent)) { 4175 return true; 4176 } 4177 4178 if (aContent->IsSVGElement(nsGkAtoms::textPath)) { 4179 // Any ‘y’ attributes on horizontal <textPath> elements are ignored. 4180 // Similarly, for vertical <texPath>s x attributes are ignored. 4181 // <textPath> elements behave as if they have x="0" y="0" on them, but only 4182 // if there is not a value for the non-ignored coordinate that got inherited 4183 // from a parent. We skip this if there is no text content, so that empty 4184 // <textPath>s don't interrupt the layout of text in the parent element. 4185 if (HasTextContent(aContent)) { 4186 if (MOZ_UNLIKELY(aIndex >= mPositions.Length())) { 4187 MOZ_ASSERT_UNREACHABLE( 4188 "length of mPositions does not match characters " 4189 "found by iterating content"); 4190 return false; 4191 } 4192 bool vertical = GetWritingMode().IsVertical(); 4193 if (vertical || !mPositions[aIndex].IsXSpecified()) { 4194 mPositions[aIndex].mPosition.x = 0.0; 4195 } 4196 if (!vertical || !mPositions[aIndex].IsYSpecified()) { 4197 mPositions[aIndex].mPosition.y = 0.0; 4198 } 4199 mPositions[aIndex].mStartOfChunk = true; 4200 } 4201 } else if (!aContent->IsSVGElement(nsGkAtoms::a)) { 4202 MOZ_ASSERT(aContent->IsSVGElement()); 4203 4204 // We have a text content element that can have x/y/dx/dy/rotate attributes. 4205 SVGElement* element = static_cast<SVGElement*>(aContent); 4206 4207 // Get x, y, dx, dy. 4208 SVGUserUnitList x, y, dx, dy; 4209 element->GetAnimatedLengthListValues(&x, &y, &dx, &dy, nullptr); 4210 4211 // Get rotate. 4212 const SVGNumberList* rotate = nullptr; 4213 SVGAnimatedNumberList* animatedRotate = 4214 element->GetAnimatedNumberList(nsGkAtoms::rotate); 4215 if (animatedRotate) { 4216 rotate = &animatedRotate->GetAnimValue(); 4217 } 4218 4219 bool percentages = false; 4220 uint32_t count = GetTextContentLength(aContent); 4221 4222 if (MOZ_UNLIKELY(aIndex + count > mPositions.Length())) { 4223 MOZ_ASSERT_UNREACHABLE( 4224 "length of mPositions does not match characters " 4225 "found by iterating content"); 4226 return false; 4227 } 4228 4229 // New text anchoring chunks start at each character assigned a position 4230 // with x="" or y="", or if we forced one with aForceStartOfChunk due to 4231 // being just after a <textPath>. 4232 uint32_t newChunkCount = std::max(x.Length(), y.Length()); 4233 if (!newChunkCount && aForceStartOfChunk) { 4234 newChunkCount = 1; 4235 } 4236 for (uint32_t i = 0, j = 0; i < newChunkCount && j < count; j++) { 4237 if (!mPositions[aIndex + j].mUnaddressable) { 4238 mPositions[aIndex + j].mStartOfChunk = true; 4239 i++; 4240 } 4241 } 4242 4243 // Copy dx="" and dy="" values into aDeltas. 4244 if (!dx.IsEmpty() || !dy.IsEmpty()) { 4245 // Any unspecified deltas when we grow the array just get left as 0s. 4246 aDeltas.EnsureLengthAtLeast(aIndex + count); 4247 for (uint32_t i = 0, j = 0; i < dx.Length() && j < count; j++) { 4248 if (!mPositions[aIndex + j].mUnaddressable) { 4249 aDeltas[aIndex + j].x = dx[i]; 4250 percentages = percentages || dx.HasPercentageValueAt(i); 4251 i++; 4252 } 4253 } 4254 for (uint32_t i = 0, j = 0; i < dy.Length() && j < count; j++) { 4255 if (!mPositions[aIndex + j].mUnaddressable) { 4256 aDeltas[aIndex + j].y = dy[i]; 4257 percentages = percentages || dy.HasPercentageValueAt(i); 4258 i++; 4259 } 4260 } 4261 } 4262 4263 // Copy x="" and y="" values. 4264 for (uint32_t i = 0, j = 0; i < x.Length() && j < count; j++) { 4265 if (!mPositions[aIndex + j].mUnaddressable) { 4266 mPositions[aIndex + j].mPosition.x = x[i]; 4267 percentages = percentages || x.HasPercentageValueAt(i); 4268 i++; 4269 } 4270 } 4271 for (uint32_t i = 0, j = 0; i < y.Length() && j < count; j++) { 4272 if (!mPositions[aIndex + j].mUnaddressable) { 4273 mPositions[aIndex + j].mPosition.y = y[i]; 4274 percentages = percentages || y.HasPercentageValueAt(i); 4275 i++; 4276 } 4277 } 4278 4279 // Copy rotate="" values. 4280 if (rotate && !rotate->IsEmpty()) { 4281 uint32_t i = 0, j = 0; 4282 while (i < rotate->Length() && j < count) { 4283 if (!mPositions[aIndex + j].mUnaddressable) { 4284 mPositions[aIndex + j].mAngle = M_PI * (*rotate)[i] / 180.0; 4285 i++; 4286 } 4287 j++; 4288 } 4289 // Propagate final rotate="" value to the end of this element. 4290 while (j < count) { 4291 mPositions[aIndex + j].mAngle = mPositions[aIndex + j - 1].mAngle; 4292 j++; 4293 } 4294 } 4295 4296 if (percentages) { 4297 AddStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES); 4298 } 4299 } 4300 4301 // Recurse to children. 4302 bool inTextPath = aInTextPath || aContent->IsSVGElement(nsGkAtoms::textPath); 4303 for (nsIContent* child = aContent->GetFirstChild(); child; 4304 child = child->GetNextSibling()) { 4305 bool ok = ResolvePositionsForNode(child, aIndex, inTextPath, 4306 aForceStartOfChunk, aDeltas); 4307 if (!ok) { 4308 return false; 4309 } 4310 } 4311 4312 if (aContent->IsSVGElement(nsGkAtoms::textPath)) { 4313 // Force a new anchored chunk just after a <textPath>. 4314 aForceStartOfChunk = true; 4315 } 4316 4317 return true; 4318 } 4319 4320 bool SVGTextFrame::ResolvePositions(nsTArray<gfxPoint>& aDeltas, 4321 bool aRunPerGlyph) { 4322 NS_ASSERTION(mPositions.IsEmpty(), "expected mPositions to be empty"); 4323 RemoveStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES); 4324 4325 CharIterator it(this, CharIterator::eOriginal, /* aSubtree */ nullptr); 4326 if (it.AtEnd()) { 4327 return false; 4328 } 4329 4330 // We assume the first character position is (0,0) unless we later see 4331 // otherwise, and note it as unaddressable if it is. 4332 bool firstCharUnaddressable = it.IsOriginalCharUnaddressable(); 4333 mPositions.AppendElement(CharPosition::Unspecified(firstCharUnaddressable)); 4334 4335 // Fill in unspecified positions for all remaining characters, noting 4336 // them as unaddressable if they are. 4337 uint32_t index = 0; 4338 while (it.Next()) { 4339 while (++index < it.TextElementCharIndex()) { 4340 mPositions.AppendElement(CharPosition::Unspecified(false)); 4341 } 4342 mPositions.AppendElement( 4343 CharPosition::Unspecified(it.IsOriginalCharUnaddressable())); 4344 } 4345 while (++index < it.TextElementCharIndex()) { 4346 mPositions.AppendElement(CharPosition::Unspecified(false)); 4347 } 4348 4349 // Recurse over the content and fill in character positions as we go. 4350 bool forceStartOfChunk = false; 4351 index = 0; 4352 bool ok = ResolvePositionsForNode(mContent, index, aRunPerGlyph, 4353 forceStartOfChunk, aDeltas); 4354 return ok && index > 0; 4355 } 4356 4357 void SVGTextFrame::DetermineCharPositions(nsTArray<nsPoint>& aPositions) { 4358 NS_ASSERTION(aPositions.IsEmpty(), "expected aPositions to be empty"); 4359 4360 nsPoint position; 4361 4362 TextFrameIterator frit(this); 4363 for (nsTextFrame* frame = frit.GetCurrent(); frame; frame = frit.GetNext()) { 4364 gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated); 4365 gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated); 4366 auto& provider = PropertyProviderFor(frame); 4367 4368 // Reset the position to the new frame's position. 4369 position = frit.Position(); 4370 if (textRun->IsVertical()) { 4371 if (textRun->IsInlineReversed()) { 4372 position.y += frame->GetRect().height; 4373 } 4374 position.x += GetBaselinePosition(frame, textRun, frit.DominantBaseline(), 4375 mFontSizeScaleFactor); 4376 } else { 4377 if (textRun->IsInlineReversed()) { 4378 position.x += frame->GetRect().width; 4379 } 4380 position.y += GetBaselinePosition(frame, textRun, frit.DominantBaseline(), 4381 mFontSizeScaleFactor); 4382 } 4383 4384 // Any characters not in a frame, e.g. when display:none. 4385 for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) { 4386 aPositions.AppendElement(position); 4387 } 4388 4389 // Any white space characters trimmed at the start of the line of text. 4390 nsTextFrame::TrimmedOffsets trimmedOffsets = 4391 frame->GetTrimmedOffsets(frame->CharacterDataBuffer()); 4392 while (it.GetOriginalOffset() < trimmedOffsets.mStart) { 4393 aPositions.AppendElement(position); 4394 it.AdvanceOriginal(1); 4395 } 4396 4397 // Visible characters in the text frame. 4398 while (it.GetOriginalOffset() < frame->GetContentEnd()) { 4399 aPositions.AppendElement(position); 4400 if (!it.IsOriginalCharSkipped()) { 4401 // Accumulate partial ligature advance into position. (We must get 4402 // partial advances rather than get the advance of the whole ligature 4403 // group / cluster at once, since the group may span text frames, and 4404 // the PropertyProvider only has spacing information for the current 4405 // text frame.) 4406 uint32_t offset = it.GetSkippedOffset(); 4407 nscoord advance = 4408 textRun->GetAdvanceWidth(Range(offset, offset + 1), &provider); 4409 (textRun->IsVertical() ? position.y : position.x) += 4410 textRun->IsInlineReversed() ? -advance : advance; 4411 } 4412 it.AdvanceOriginal(1); 4413 } 4414 } 4415 4416 // Finally any characters at the end that are not in a frame. 4417 for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) { 4418 aPositions.AppendElement(position); 4419 } 4420 4421 // Clear any cached PropertyProvider, to avoid risk of re-using it after 4422 // style changes or other mutations may have invalidated it. 4423 ForgetCachedProvider(); 4424 } 4425 4426 /** 4427 * Physical text-anchor values. 4428 */ 4429 enum TextAnchorSide { eAnchorLeft, eAnchorMiddle, eAnchorRight }; 4430 4431 /** 4432 * Converts a logical text-anchor value to its physical value, based on whether 4433 * it is for an RTL frame. 4434 */ 4435 static TextAnchorSide ConvertLogicalTextAnchorToPhysical( 4436 StyleTextAnchor aTextAnchor, bool aIsRightToLeft) { 4437 NS_ASSERTION(uint8_t(aTextAnchor) <= 3, "unexpected value for aTextAnchor"); 4438 if (!aIsRightToLeft) { 4439 return TextAnchorSide(uint8_t(aTextAnchor)); 4440 } 4441 return TextAnchorSide(2 - uint8_t(aTextAnchor)); 4442 } 4443 4444 /** 4445 * Shifts the recorded character positions for an anchored chunk. 4446 * 4447 * @param aCharPositions The recorded character positions. 4448 * @param aChunkStart The character index the starts the anchored chunk. This 4449 * character's initial position is the anchor point. 4450 * @param aChunkEnd The character index just after the end of the anchored 4451 * chunk. 4452 * @param aVisIStartEdge The left/top-most edge of any of the glyphs within the 4453 * anchored chunk. 4454 * @param aVisIEndEdge The right/bottom-most edge of any of the glyphs within 4455 * the anchored chunk. 4456 * @param aAnchorSide The direction to anchor. 4457 */ 4458 static void ShiftAnchoredChunk(nsTArray<CharPosition>& aCharPositions, 4459 uint32_t aChunkStart, uint32_t aChunkEnd, 4460 gfxFloat aVisIStartEdge, gfxFloat aVisIEndEdge, 4461 TextAnchorSide aAnchorSide, bool aVertical) { 4462 NS_ASSERTION(aVisIStartEdge <= aVisIEndEdge, 4463 "unexpected anchored chunk edges"); 4464 NS_ASSERTION(aChunkStart < aChunkEnd, 4465 "unexpected values for aChunkStart and aChunkEnd"); 4466 4467 gfxFloat shift = aVertical ? aCharPositions[aChunkStart].mPosition.y 4468 : aCharPositions[aChunkStart].mPosition.x; 4469 switch (aAnchorSide) { 4470 case eAnchorLeft: 4471 shift -= aVisIStartEdge; 4472 break; 4473 case eAnchorMiddle: 4474 shift -= (aVisIStartEdge + aVisIEndEdge) / 2; 4475 break; 4476 case eAnchorRight: 4477 shift -= aVisIEndEdge; 4478 break; 4479 default: 4480 MOZ_ASSERT_UNREACHABLE("unexpected value for aAnchorSide"); 4481 } 4482 4483 if (shift != 0.0) { 4484 if (aVertical) { 4485 for (uint32_t i = aChunkStart; i < aChunkEnd; i++) { 4486 aCharPositions[i].mPosition.y += shift; 4487 } 4488 } else { 4489 for (uint32_t i = aChunkStart; i < aChunkEnd; i++) { 4490 aCharPositions[i].mPosition.x += shift; 4491 } 4492 } 4493 } 4494 } 4495 4496 void SVGTextFrame::AdjustChunksForLineBreaks() { 4497 nsBlockFrame* block = do_QueryFrame(PrincipalChildList().FirstChild()); 4498 NS_ASSERTION(block, "expected block frame"); 4499 4500 nsBlockFrame::LineIterator line = block->LinesBegin(); 4501 4502 CharIterator it(this, CharIterator::eOriginal, /* aSubtree */ nullptr); 4503 while (!it.AtEnd() && line != block->LinesEnd()) { 4504 if (it.GetTextFrame() == line->mFirstChild) { 4505 mPositions[it.TextElementCharIndex()].mStartOfChunk = true; 4506 line++; 4507 } 4508 it.AdvancePastCurrentFrame(); 4509 } 4510 } 4511 4512 void SVGTextFrame::AdjustPositionsForClusters() { 4513 nsPresContext* presContext = PresContext(); 4514 4515 // Find all of the characters that are in the middle of a cluster or 4516 // ligature group, and adjust their positions and rotations to match 4517 // the first character of the cluster/group. 4518 // 4519 // Also move the boundaries of text rendered runs and anchored chunks to 4520 // not lie in the middle of cluster/group. 4521 4522 // The partial advance of the current cluster or ligature group that we 4523 // have accumulated. 4524 gfxFloat partialAdvance = 0.0; 4525 4526 CharIterator it(this, CharIterator::eUnskipped, /* aSubtree */ nullptr); 4527 bool isFirst = true; 4528 while (!it.AtEnd()) { 4529 if (it.IsClusterAndLigatureGroupStart() || isFirst) { 4530 // If we're at the start of a new cluster or ligature group, reset our 4531 // accumulated partial advance. Also treat the beginning of the text as 4532 // an anchor, even if it is a combining character and therefore was 4533 // marked as being a Unicode cluster continuation. 4534 partialAdvance = 0.0; 4535 isFirst = false; 4536 } else { 4537 // Otherwise, we're in the middle of a cluster or ligature group, and 4538 // we need to use the currently accumulated partial advance to adjust 4539 // the character's position and rotation. 4540 4541 // Find the start of the cluster/ligature group. 4542 uint32_t charIndex = it.TextElementCharIndex(); 4543 uint32_t startIndex = it.GlyphStartTextElementCharIndex(); 4544 MOZ_ASSERT(charIndex != startIndex, 4545 "If the current character is in the middle of a cluster or " 4546 "ligature group, then charIndex must be different from " 4547 "startIndex"); 4548 4549 mPositions[charIndex].mClusterOrLigatureGroupMiddle = true; 4550 4551 // Don't allow different rotations on ligature parts. 4552 bool rotationAdjusted = false; 4553 double angle = mPositions[startIndex].mAngle; 4554 if (mPositions[charIndex].mAngle != angle) { 4555 mPositions[charIndex].mAngle = angle; 4556 rotationAdjusted = true; 4557 } 4558 4559 // Update the character position. 4560 gfxFloat advance = partialAdvance / mFontSizeScaleFactor; 4561 const gfxTextRun* textRun = it.TextRun(); 4562 gfxPoint direction = gfxPoint(cos(angle), sin(angle)) * 4563 (textRun->IsInlineReversed() ? -1.0 : 1.0); 4564 if (textRun->IsVertical()) { 4565 std::swap(direction.x, direction.y); 4566 } 4567 mPositions[charIndex].mPosition = 4568 mPositions[startIndex].mPosition + direction * advance; 4569 4570 // Ensure any runs that would end in the middle of a ligature now end just 4571 // after the ligature. 4572 if (mPositions[charIndex].mRunBoundary) { 4573 mPositions[charIndex].mRunBoundary = false; 4574 if (charIndex + 1 < mPositions.Length()) { 4575 mPositions[charIndex + 1].mRunBoundary = true; 4576 } 4577 } else if (rotationAdjusted) { 4578 if (charIndex + 1 < mPositions.Length()) { 4579 mPositions[charIndex + 1].mRunBoundary = true; 4580 } 4581 } 4582 4583 // Ensure any anchored chunks that would begin in the middle of a ligature 4584 // now begin just after the ligature. 4585 if (mPositions[charIndex].mStartOfChunk) { 4586 mPositions[charIndex].mStartOfChunk = false; 4587 if (charIndex + 1 < mPositions.Length()) { 4588 mPositions[charIndex + 1].mStartOfChunk = true; 4589 } 4590 } 4591 } 4592 4593 // Accumulate the current character's partial advance. 4594 partialAdvance += it.GetAdvance(presContext); 4595 4596 it.Next(); 4597 } 4598 } 4599 4600 already_AddRefed<Path> SVGTextFrame::GetTextPath(nsIFrame* aTextPathFrame) { 4601 nsIContent* content = aTextPathFrame->GetContent(); 4602 SVGTextPathElement* tp = static_cast<SVGTextPathElement*>(content); 4603 if (tp->mPath.IsRendered()) { 4604 // This is just an attribute so there's no transform that can apply 4605 // so we can just return the path directly. 4606 return tp->mPath.GetAnimValue().BuildPathForMeasuring( 4607 aTextPathFrame->Style()->EffectiveZoom().ToFloat()); 4608 } 4609 4610 SVGGeometryElement* geomElement = 4611 SVGObserverUtils::GetAndObserveTextPathsPath(aTextPathFrame); 4612 if (!geomElement) { 4613 return nullptr; 4614 } 4615 4616 RefPtr<Path> path = geomElement->GetOrBuildPathForMeasuring(); 4617 if (!path) { 4618 return nullptr; 4619 } 4620 4621 // Apply the geometry element's transform if appropriate. 4622 auto matrix = geomElement->LocalTransform(); 4623 if (!matrix.IsIdentity()) { 4624 Path::Transform(path, matrix); 4625 } 4626 4627 return path.forget(); 4628 } 4629 4630 gfxFloat SVGTextFrame::GetOffsetScale(nsIFrame* aTextPathFrame) { 4631 nsIContent* content = aTextPathFrame->GetContent(); 4632 SVGTextPathElement* tp = static_cast<SVGTextPathElement*>(content); 4633 if (tp->mPath.IsRendered()) { 4634 // A path attribute has no pathLength or transform 4635 // so we return a unit scale. 4636 return 1.0; 4637 } 4638 4639 SVGGeometryElement* geomElement = 4640 SVGObserverUtils::GetAndObserveTextPathsPath(aTextPathFrame); 4641 if (!geomElement) { 4642 return 1.0; 4643 } 4644 return geomElement->GetPathLengthScale(SVGGeometryElement::eForTextPath); 4645 } 4646 4647 gfxFloat SVGTextFrame::GetStartOffset(nsIFrame* aTextPathFrame) { 4648 SVGTextPathElement* tp = 4649 static_cast<SVGTextPathElement*>(aTextPathFrame->GetContent()); 4650 SVGAnimatedLength* length = 4651 &tp->mLengthAttributes[SVGTextPathElement::STARTOFFSET]; 4652 4653 if (length->IsPercentage()) { 4654 if (!std::isfinite(GetOffsetScale(aTextPathFrame))) { 4655 // Either pathLength="0" for this path or the path has 0 length. 4656 return 0.0; 4657 } 4658 RefPtr<Path> data = GetTextPath(aTextPathFrame); 4659 return data ? length->GetAnimValInSpecifiedUnits() * data->ComputeLength() / 4660 100.0 4661 : 0.0; 4662 } 4663 float lengthValue = length->GetAnimValueWithZoom(tp); 4664 // If offsetScale is infinity we want to return 0 not NaN 4665 return lengthValue == 0 ? 0.0 : lengthValue * GetOffsetScale(aTextPathFrame); 4666 } 4667 4668 void SVGTextFrame::DoTextPathLayout() { 4669 nsPresContext* context = PresContext(); 4670 4671 CharIterator it(this, CharIterator::eOriginal, /* aSubtree */ nullptr); 4672 while (!it.AtEnd()) { 4673 nsIFrame* textPathFrame = it.TextPathFrame(); 4674 if (!textPathFrame) { 4675 // Skip past this frame if we're not in a text path. 4676 it.AdvancePastCurrentFrame(); 4677 continue; 4678 } 4679 4680 // Get the path itself. 4681 RefPtr<Path> path = GetTextPath(textPathFrame); 4682 if (!path) { 4683 uint32_t start = it.TextElementCharIndex(); 4684 it.AdvancePastCurrentTextPathFrame(); 4685 uint32_t end = it.TextElementCharIndex(); 4686 for (uint32_t i = start; i < end; i++) { 4687 mPositions[i].mHidden = true; 4688 } 4689 continue; 4690 } 4691 4692 SVGTextPathElement* textPath = 4693 static_cast<SVGTextPathElement*>(textPathFrame->GetContent()); 4694 uint16_t side = 4695 textPath->EnumAttributes()[SVGTextPathElement::SIDE].GetAnimValue(); 4696 4697 gfxFloat offset = GetStartOffset(textPathFrame); 4698 Float pathLength = path->ComputeLength(); 4699 4700 // If the first character within the text path is in the middle of a 4701 // cluster or ligature group, just skip it and don't apply text path 4702 // positioning. 4703 while (!it.AtEnd()) { 4704 if (it.IsOriginalCharSkipped()) { 4705 it.Next(); 4706 continue; 4707 } 4708 if (it.IsClusterAndLigatureGroupStart()) { 4709 break; 4710 } 4711 it.Next(); 4712 } 4713 4714 bool skippedEndOfTextPath = false; 4715 4716 // Loop for each character in the text path. 4717 while (!it.AtEnd() && it.TextPathFrame() && 4718 it.TextPathFrame()->GetContent() == textPath) { 4719 // The index of the cluster or ligature group's first character. 4720 uint32_t i = it.TextElementCharIndex(); 4721 4722 // The index of the next character of the cluster or ligature. 4723 // We track this as we loop over the characters below so that we 4724 // can detect undisplayed characters and append entries into 4725 // partialAdvances for them. 4726 uint32_t j = i + 1; 4727 4728 MOZ_ASSERT(!mPositions[i].mClusterOrLigatureGroupMiddle); 4729 4730 const gfxTextRun* textRun = it.TextRun(); 4731 bool vertical = textRun->IsVertical(); 4732 4733 // Compute cumulative advances for each character of the cluster or 4734 // ligature group. 4735 AutoTArray<gfxFloat, 4> partialAdvances; 4736 gfxFloat partialAdvance = it.GetAdvance(context); 4737 partialAdvances.AppendElement(partialAdvance); 4738 while (it.Next()) { 4739 // Append entries for any undisplayed characters the CharIterator 4740 // skipped over. 4741 MOZ_ASSERT(j <= it.TextElementCharIndex()); 4742 while (j < it.TextElementCharIndex()) { 4743 partialAdvances.AppendElement(partialAdvance); 4744 ++j; 4745 } 4746 // This loop may end up outside of the current text path, but 4747 // that's OK; we'll consider any complete cluster or ligature 4748 // group that begins inside the text path as being affected 4749 // by it. 4750 if (it.IsOriginalCharSkipped()) { 4751 if (!it.TextPathFrame()) { 4752 skippedEndOfTextPath = true; 4753 break; 4754 } 4755 // Leave partialAdvance unchanged. 4756 } else if (it.IsClusterAndLigatureGroupStart()) { 4757 break; 4758 } else { 4759 partialAdvance += it.GetAdvance(context); 4760 } 4761 partialAdvances.AppendElement(partialAdvance); 4762 } 4763 4764 if (!skippedEndOfTextPath) { 4765 // Any final undisplayed characters the CharIterator skipped over. 4766 MOZ_ASSERT(j <= it.TextElementCharIndex()); 4767 while (j < it.TextElementCharIndex()) { 4768 partialAdvances.AppendElement(partialAdvance); 4769 ++j; 4770 } 4771 } 4772 4773 gfxFloat halfAdvance = 4774 partialAdvances.LastElement() / mFontSizeScaleFactor / 2.0; 4775 if (textRun->IsInlineReversed()) { 4776 halfAdvance = -halfAdvance; 4777 } 4778 gfxFloat midx = 4779 (vertical ? mPositions[i].mPosition.y : mPositions[i].mPosition.x) + 4780 halfAdvance + offset; 4781 4782 // Hide the character if it falls off the end of the path. 4783 mPositions[i].mHidden = midx < 0 || midx > pathLength; 4784 4785 // Position the character on the path at the right angle. 4786 Point tangent; // Unit vector tangent to the point we find. 4787 Point pt; 4788 if (side == TEXTPATH_SIDETYPE_RIGHT) { 4789 pt = path->ComputePointAtLength(Float(pathLength - midx), &tangent); 4790 tangent = -tangent; 4791 } else { 4792 pt = path->ComputePointAtLength(Float(midx), &tangent); 4793 } 4794 Float rotation = vertical ? atan2f(-tangent.x, tangent.y) 4795 : atan2f(tangent.y, tangent.x); 4796 Point normal(-tangent.y, tangent.x); // Unit vector normal to the point. 4797 Point offsetFromPath = normal * (vertical ? -mPositions[i].mPosition.x 4798 : mPositions[i].mPosition.y); 4799 pt += offsetFromPath; 4800 Point direction = textRun->IsInlineReversed() ? -tangent : tangent; 4801 mPositions[i].mPosition = 4802 ThebesPoint(pt) - ThebesPoint(direction) * halfAdvance; 4803 mPositions[i].mAngle += rotation; 4804 4805 // Position any characters for a partial ligature. 4806 for (uint32_t k = i + 1; k < j; k++) { 4807 gfxPoint partialAdvance = ThebesPoint(direction) * 4808 partialAdvances[k - i] / mFontSizeScaleFactor; 4809 mPositions[k].mPosition = mPositions[i].mPosition + partialAdvance; 4810 mPositions[k].mAngle = mPositions[i].mAngle; 4811 mPositions[k].mHidden = mPositions[i].mHidden; 4812 } 4813 } 4814 } 4815 } 4816 4817 void SVGTextFrame::DoAnchoring() { 4818 nsPresContext* presContext = PresContext(); 4819 4820 CharIterator it(this, CharIterator::eOriginal, /* aSubtree */ nullptr); 4821 4822 // Don't need to worry about skipped or trimmed characters. 4823 while (!it.AtEnd() && 4824 (it.IsOriginalCharSkipped() || it.IsOriginalCharTrimmed())) { 4825 it.Next(); 4826 } 4827 4828 bool vertical = GetWritingMode().IsVertical(); 4829 for (uint32_t start = it.TextElementCharIndex(); start < mPositions.Length(); 4830 start = it.TextElementCharIndex()) { 4831 it.AdvanceToCharacter(start); 4832 nsTextFrame* chunkFrame = it.GetTextFrame(); 4833 4834 // Measure characters in this chunk to find the left-most and right-most 4835 // edges of all glyphs within the chunk. 4836 uint32_t index = it.TextElementCharIndex(); 4837 uint32_t end = start; 4838 gfxFloat left = std::numeric_limits<gfxFloat>::infinity(); 4839 gfxFloat right = -std::numeric_limits<gfxFloat>::infinity(); 4840 do { 4841 if (!it.IsOriginalCharSkipped() && !it.IsOriginalCharTrimmed()) { 4842 gfxFloat advance = it.GetAdvance(presContext) / mFontSizeScaleFactor; 4843 const gfxTextRun* textRun = it.TextRun(); 4844 gfxFloat pos = textRun->IsVertical() ? mPositions[index].mPosition.y 4845 : mPositions[index].mPosition.x; 4846 if (textRun->IsInlineReversed()) { 4847 left = std::min(left, pos - advance); 4848 right = std::max(right, pos); 4849 } else { 4850 left = std::min(left, pos); 4851 right = std::max(right, pos + advance); 4852 } 4853 } 4854 it.Next(); 4855 index = end = it.TextElementCharIndex(); 4856 } while (!it.AtEnd() && !mPositions[end].mStartOfChunk); 4857 4858 if (left != std::numeric_limits<gfxFloat>::infinity()) { 4859 bool isRTL = 4860 chunkFrame->StyleVisibility()->mDirection == StyleDirection::Rtl; 4861 TextAnchorSide anchor = ConvertLogicalTextAnchorToPhysical( 4862 chunkFrame->StyleSVG()->mTextAnchor, isRTL); 4863 4864 ShiftAnchoredChunk(mPositions, start, end, left, right, anchor, vertical); 4865 } 4866 } 4867 } 4868 4869 void SVGTextFrame::DoGlyphPositioning() { 4870 mPositions.Clear(); 4871 RemoveStateBits(NS_STATE_SVG_POSITIONING_DIRTY); 4872 4873 nsIFrame* kid = PrincipalChildList().FirstChild(); 4874 if (kid && kid->IsSubtreeDirty()) { 4875 MOZ_ASSERT(false, "should have already reflowed the kid"); 4876 return; 4877 } 4878 4879 // Since we can be called directly via GetBBoxContribution, our correspondence 4880 // may not be up to date. 4881 TextNodeCorrespondenceRecorder::RecordCorrespondence(this); 4882 4883 // Determine the positions of each character in app units. 4884 AutoTArray<nsPoint, 64> charPositions; 4885 DetermineCharPositions(charPositions); 4886 4887 if (charPositions.IsEmpty()) { 4888 // No characters, so nothing to do. 4889 return; 4890 } 4891 4892 // If the textLength="" attribute was specified, then we need ResolvePositions 4893 // to record that a new run starts with each glyph. 4894 SVGTextContentElement* element = 4895 static_cast<SVGTextContentElement*>(GetContent()); 4896 SVGAnimatedLength* textLengthAttr = 4897 element->GetAnimatedLength(nsGkAtoms::textLength); 4898 uint16_t lengthAdjust = 4899 element->EnumAttributes()[SVGTextContentElement::LENGTHADJUST] 4900 .GetAnimValue(); 4901 bool adjustingTextLength = textLengthAttr->IsExplicitlySet(); 4902 float expectedTextLength = textLengthAttr->GetAnimValueWithZoom(element); 4903 4904 if (adjustingTextLength && 4905 (expectedTextLength < 0.0f || lengthAdjust == LENGTHADJUST_UNKNOWN)) { 4906 // If textLength="" is less than zero or lengthAdjust is unknown, ignore it. 4907 adjustingTextLength = false; 4908 } 4909 4910 // Get the x, y, dx, dy, rotate values for the subtree. 4911 AutoTArray<gfxPoint, 16> deltas; 4912 if (!ResolvePositions(deltas, adjustingTextLength)) { 4913 // If ResolvePositions returned false, it means either there were some 4914 // characters in the DOM but none of them are displayed, or there was 4915 // an error in processing mPositions. Clear out mPositions so that we don't 4916 // attempt to do any painting later. 4917 mPositions.Clear(); 4918 return; 4919 } 4920 4921 // XXX We might be able to do less work when there is at most a single 4922 // x/y/dx/dy position. 4923 4924 // Truncate the positioning arrays to the actual number of characters present. 4925 TruncateTo(deltas, charPositions); 4926 TruncateTo(mPositions, charPositions); 4927 4928 // Fill in an unspecified position for the first addressable character. 4929 uint32_t first = 0; 4930 while (first + 1 < mPositions.Length() && mPositions[first].mUnaddressable) { 4931 ++first; 4932 } 4933 if (!mPositions[first].IsXSpecified()) { 4934 mPositions[first].mPosition.x = 0.0; 4935 } 4936 if (!mPositions[first].IsYSpecified()) { 4937 mPositions[first].mPosition.y = 0.0; 4938 } 4939 if (!mPositions[first].IsAngleSpecified()) { 4940 mPositions[first].mAngle = 0.0; 4941 } 4942 4943 nsPresContext* presContext = PresContext(); 4944 bool vertical = GetWritingMode().IsVertical(); 4945 4946 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels( 4947 presContext->AppUnitsPerDevPixel()); 4948 double factor = cssPxPerDevPx / mFontSizeScaleFactor; 4949 4950 // Determine how much to compress or expand glyph positions due to 4951 // textLength="" and lengthAdjust="". 4952 double adjustment = 0.0; 4953 mLengthAdjustScaleFactor = 1.0f; 4954 if (adjustingTextLength) { 4955 nscoord frameLength = 4956 vertical ? PrincipalChildList().FirstChild()->GetRect().height 4957 : PrincipalChildList().FirstChild()->GetRect().width; 4958 float actualTextLength = static_cast<float>( 4959 presContext->AppUnitsToGfxUnits(frameLength) * factor); 4960 4961 switch (lengthAdjust) { 4962 case LENGTHADJUST_SPACINGANDGLYPHS: 4963 // Scale the glyphs and their positions. 4964 if (actualTextLength > 0) { 4965 mLengthAdjustScaleFactor = expectedTextLength / actualTextLength; 4966 } 4967 break; 4968 4969 default: 4970 MOZ_ASSERT(lengthAdjust == LENGTHADJUST_SPACING); 4971 // Just add space between each glyph. 4972 int32_t adjustableSpaces = 0; 4973 for (uint32_t i = 1; i < mPositions.Length(); i++) { 4974 if (!mPositions[i].mUnaddressable) { 4975 adjustableSpaces++; 4976 } 4977 } 4978 if (adjustableSpaces) { 4979 adjustment = 4980 (expectedTextLength - actualTextLength) / adjustableSpaces; 4981 } 4982 break; 4983 } 4984 } 4985 4986 // Fill in any unspecified character positions based on the positions recorded 4987 // in charPositions, and also add in the dx/dy values. 4988 if (!deltas.IsEmpty()) { 4989 mPositions[0].mPosition += deltas[0]; 4990 } 4991 4992 gfxFloat xLengthAdjustFactor = vertical ? 1.0 : mLengthAdjustScaleFactor; 4993 gfxFloat yLengthAdjustFactor = vertical ? mLengthAdjustScaleFactor : 1.0; 4994 for (uint32_t i = 1; i < mPositions.Length(); i++) { 4995 // Fill in unspecified x position. 4996 if (!mPositions[i].IsXSpecified()) { 4997 nscoord d = charPositions[i].x - charPositions[i - 1].x; 4998 mPositions[i].mPosition.x = 4999 mPositions[i - 1].mPosition.x + 5000 presContext->AppUnitsToGfxUnits(d) * factor * xLengthAdjustFactor; 5001 if (!vertical && !mPositions[i].mUnaddressable) { 5002 mPositions[i].mPosition.x += adjustment; 5003 } 5004 } 5005 // Fill in unspecified y position. 5006 if (!mPositions[i].IsYSpecified()) { 5007 nscoord d = charPositions[i].y - charPositions[i - 1].y; 5008 mPositions[i].mPosition.y = 5009 mPositions[i - 1].mPosition.y + 5010 presContext->AppUnitsToGfxUnits(d) * factor * yLengthAdjustFactor; 5011 if (vertical && !mPositions[i].mUnaddressable) { 5012 mPositions[i].mPosition.y += adjustment; 5013 } 5014 } 5015 // Add in dx/dy. 5016 if (i < deltas.Length()) { 5017 mPositions[i].mPosition += deltas[i]; 5018 } 5019 // Fill in unspecified rotation values. 5020 if (!mPositions[i].IsAngleSpecified()) { 5021 mPositions[i].mAngle = 0.0f; 5022 } 5023 } 5024 5025 MOZ_ASSERT(mPositions.Length() == charPositions.Length()); 5026 5027 AdjustChunksForLineBreaks(); 5028 AdjustPositionsForClusters(); 5029 DoAnchoring(); 5030 DoTextPathLayout(); 5031 } 5032 5033 bool SVGTextFrame::ShouldRenderAsPath(nsTextFrame* aFrame, 5034 SVGContextPaint* aContextPaint, 5035 bool& aShouldPaintSVGGlyphs) { 5036 // Rendering to a clip path. 5037 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) { 5038 aShouldPaintSVGGlyphs = false; 5039 return true; 5040 } 5041 5042 aShouldPaintSVGGlyphs = true; 5043 5044 const nsStyleSVG* style = aFrame->StyleSVG(); 5045 5046 // Fill is a non-solid paint or is not opaque. 5047 if (!(style->mFill.kind.IsNone() || 5048 (style->mFill.kind.IsColor() && 5049 SVGUtils::GetOpacity(style->mFillOpacity, aContextPaint) == 1.0f))) { 5050 return true; 5051 } 5052 5053 // If we're going to need to draw a non-opaque shadow. 5054 // It's possible nsTextFrame will support non-opaque shadows in the future, 5055 // in which case this test can be removed. 5056 if (style->mFill.kind.IsColor() && aFrame->StyleText()->HasTextShadow() && 5057 NS_GET_A(style->mFill.kind.AsColor().CalcColor(*aFrame->Style())) != 5058 0xFF) { 5059 return true; 5060 } 5061 5062 // Text has a stroke. 5063 if (style->HasStroke()) { 5064 if (style->mStrokeWidth.IsContextValue()) { 5065 return true; 5066 } 5067 if (SVGContentUtils::CoordToFloat( 5068 static_cast<SVGElement*>(GetContent()), 5069 style->mStrokeWidth.AsLengthPercentage()) > 0) { 5070 return true; 5071 } 5072 } 5073 5074 return false; 5075 } 5076 5077 void SVGTextFrame::ScheduleReflowSVG() { 5078 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 5079 ScheduleReflowSVGNonDisplayText( 5080 IntrinsicDirty::FrameAncestorsAndDescendants); 5081 } else { 5082 SVGUtils::ScheduleReflowSVG(this); 5083 } 5084 } 5085 5086 void SVGTextFrame::NotifyGlyphMetricsChange(bool aUpdateTextCorrespondence) { 5087 if (aUpdateTextCorrespondence) { 5088 AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY); 5089 } 5090 AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY); 5091 nsLayoutUtils::PostRestyleEvent(mContent->AsElement(), RestyleHint{0}, 5092 nsChangeHint_InvalidateRenderingObservers); 5093 ScheduleReflowSVG(); 5094 } 5095 5096 void SVGTextFrame::UpdateGlyphPositioning() { 5097 nsIFrame* kid = PrincipalChildList().FirstChild(); 5098 if (!kid) { 5099 return; 5100 } 5101 5102 if (HasAnyStateBits(NS_STATE_SVG_POSITIONING_DIRTY)) { 5103 DoGlyphPositioning(); 5104 } 5105 } 5106 5107 void SVGTextFrame::MaybeResolveBidiForAnonymousBlockChild() { 5108 nsIFrame* kid = PrincipalChildList().FirstChild(); 5109 5110 if (kid && kid->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) && 5111 PresContext()->BidiEnabled()) { 5112 MOZ_ASSERT(static_cast<nsBlockFrame*>(do_QueryFrame(kid)), 5113 "Expect anonymous child to be an nsBlockFrame"); 5114 nsBidiPresUtils::Resolve(static_cast<nsBlockFrame*>(kid)); 5115 } 5116 } 5117 5118 void SVGTextFrame::MaybeReflowAnonymousBlockChild() { 5119 nsIFrame* kid = PrincipalChildList().FirstChild(); 5120 if (!kid) { 5121 return; 5122 } 5123 5124 NS_ASSERTION(!kid->HasAnyStateBits(NS_FRAME_IN_REFLOW), 5125 "should not be in reflow when about to reflow again"); 5126 5127 if (IsSubtreeDirty()) { 5128 if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 5129 // If we require a full reflow, ensure our kid is marked fully dirty. 5130 // (Note that our anonymous nsBlockFrame is not an ISVGDisplayableFrame, 5131 // so even when we are called via our ReflowSVG this will not be done for 5132 // us by SVGDisplayContainerFrame::ReflowSVG.) 5133 kid->MarkSubtreeDirty(); 5134 } 5135 5136 // The RecordCorrespondence and DoReflow calls can result in new text frames 5137 // being created (due to bidi resolution or reflow). We set this bit to 5138 // guard against unnecessarily calling back in to 5139 // ScheduleReflowSVGNonDisplayText from nsIFrame::DidSetComputedStyle on 5140 // those new text frames. 5141 AddStateBits(NS_STATE_SVG_TEXT_IN_REFLOW); 5142 5143 TextNodeCorrespondenceRecorder::RecordCorrespondence(this); 5144 5145 MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this), 5146 "should be under ReflowSVG"); 5147 nsPresContext::InterruptPreventer noInterrupts(PresContext()); 5148 DoReflow(); 5149 5150 RemoveStateBits(NS_STATE_SVG_TEXT_IN_REFLOW); 5151 } 5152 } 5153 5154 void SVGTextFrame::DoReflow() { 5155 MOZ_ASSERT(HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW)); 5156 5157 // Since we are going to reflow the anonymous block frame, we will 5158 // need to update mPositions. 5159 // We also mark our text correspondence as dirty since we can end up needing 5160 // reflow in ways that do not set NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY. 5161 // (We'd then fail the "expected a TextNodeCorrespondenceProperty" assertion 5162 // when UpdateGlyphPositioning() is called after we return.) 5163 AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY | 5164 NS_STATE_SVG_POSITIONING_DIRTY); 5165 5166 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 5167 // Normally, these dirty flags would be cleared in ReflowSVG(), but that 5168 // doesn't get called for non-display frames. We don't want to reflow our 5169 // descendants every time SVGTextFrame::PaintSVG makes sure that we have 5170 // valid positions by calling UpdateGlyphPositioning(), so we need to clear 5171 // these dirty bits. Note that this also breaks an invalidation loop where 5172 // our descendants invalidate as they reflow, which invalidates rendering 5173 // observers, which reschedules the frame that is currently painting by 5174 // referencing us to paint again. See bug 839958 comment 7. Hopefully we 5175 // will break that loop more convincingly at some point. 5176 RemoveStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); 5177 } 5178 5179 // Forget any cached measurements of one of our children. 5180 mFrameForCachedRanges = nullptr; 5181 5182 nsPresContext* presContext = PresContext(); 5183 nsIFrame* kid = PrincipalChildList().FirstChild(); 5184 if (!kid) { 5185 return; 5186 } 5187 5188 UniquePtr<gfxContext> renderingContext = 5189 presContext->PresShell()->CreateReferenceRenderingContext(); 5190 5191 if (UpdateFontSizeScaleFactor()) { 5192 // If the font size scale factor changed, we need the block to report 5193 // an updated preferred width. 5194 kid->MarkIntrinsicISizesDirty(); 5195 } 5196 5197 const IntrinsicSizeInput input(renderingContext.get(), Nothing(), Nothing()); 5198 nscoord inlineSize = kid->GetPrefISize(input); 5199 WritingMode wm = kid->GetWritingMode(); 5200 ReflowInput reflowInput(presContext, kid, renderingContext.get(), 5201 LogicalSize(wm, inlineSize, NS_UNCONSTRAINEDSIZE)); 5202 ReflowOutput desiredSize(reflowInput); 5203 nsReflowStatus status; 5204 5205 NS_ASSERTION( 5206 reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) && 5207 reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0), 5208 "style system should ensure that :-moz-svg-text " 5209 "does not get styled"); 5210 5211 kid->Reflow(presContext, desiredSize, reflowInput, status); 5212 kid->DidReflow(presContext, &reflowInput); 5213 kid->SetSize(wm, desiredSize.Size(wm)); 5214 } 5215 5216 // Usable font size range in devpixels / user-units 5217 #define CLAMP_MIN_SIZE 8.0 5218 #define CLAMP_MAX_SIZE 200.0 5219 #define PRECISE_SIZE 200.0 5220 5221 bool SVGTextFrame::UpdateFontSizeScaleFactor() { 5222 float contextScale = GetContextScale(this); 5223 mLastContextScale = contextScale; 5224 5225 double oldFontSizeScaleFactor = mFontSizeScaleFactor; 5226 5227 bool geometricPrecision = false; 5228 // We may need to invert a matrix with these values later. 5229 CSSCoord min = std::sqrt(std::numeric_limits<float>::max()); 5230 CSSCoord max = std::sqrt(std::numeric_limits<float>::min()); 5231 bool anyText = false; 5232 5233 // Find the minimum and maximum font sizes used over all the 5234 // nsTextFrames. 5235 TextFrameIterator it(this); 5236 nsTextFrame* f = it.GetCurrent(); 5237 while (f) { 5238 if (!geometricPrecision) { 5239 // Unfortunately we can't treat text-rendering:geometricPrecision 5240 // separately for each text frame. 5241 geometricPrecision = f->StyleText()->mTextRendering == 5242 StyleTextRendering::Geometricprecision; 5243 } 5244 const auto& fontSize = f->StyleFont()->mFont.size; 5245 if (!fontSize.IsZero()) { 5246 min = std::min(min, fontSize.ToCSSPixels()); 5247 max = std::max(max, fontSize.ToCSSPixels()); 5248 anyText = true; 5249 } 5250 f = it.GetNext(); 5251 } 5252 5253 if (!anyText) { 5254 // No text, so no need for scaling. 5255 mFontSizeScaleFactor = 1.0; 5256 return mFontSizeScaleFactor != oldFontSizeScaleFactor; 5257 } 5258 5259 if (geometricPrecision) { 5260 // We want to ensure minSize is scaled to PRECISE_SIZE. 5261 mFontSizeScaleFactor = PRECISE_SIZE / min; 5262 return mFontSizeScaleFactor != oldFontSizeScaleFactor; 5263 } 5264 5265 double minTextRunSize = min * contextScale; 5266 double maxTextRunSize = max * contextScale; 5267 5268 if (minTextRunSize >= CLAMP_MIN_SIZE && maxTextRunSize <= CLAMP_MAX_SIZE) { 5269 // We are already in the ideal font size range for all text frames, 5270 // so we only have to take into account the contextScale. 5271 mFontSizeScaleFactor = contextScale; 5272 } else if (max / min > CLAMP_MAX_SIZE / CLAMP_MIN_SIZE) { 5273 // We can't scale the font sizes so that all of the text frames lie 5274 // within our ideal font size range. 5275 // Heuristically, if the maxTextRunSize is within the CLAMP_MAX_SIZE 5276 // as a reasonable value, it's likely to be the user's intent to 5277 // get a valid font for the maxTextRunSize one, we should honor it. 5278 // The same for minTextRunSize. 5279 if (maxTextRunSize <= CLAMP_MAX_SIZE) { 5280 mFontSizeScaleFactor = CLAMP_MAX_SIZE / max; 5281 } else if (minTextRunSize >= CLAMP_MIN_SIZE) { 5282 mFontSizeScaleFactor = CLAMP_MIN_SIZE / min; 5283 } else { 5284 // So maxTextRunSize is too big, minTextRunSize is too small, 5285 // we can't really do anything for this case, just leave it as is. 5286 mFontSizeScaleFactor = contextScale; 5287 } 5288 } else if (minTextRunSize < CLAMP_MIN_SIZE) { 5289 mFontSizeScaleFactor = CLAMP_MIN_SIZE / min; 5290 } else { 5291 mFontSizeScaleFactor = CLAMP_MAX_SIZE / max; 5292 } 5293 5294 return mFontSizeScaleFactor != oldFontSizeScaleFactor; 5295 } 5296 5297 double SVGTextFrame::GetFontSizeScaleFactor() const { 5298 return mFontSizeScaleFactor; 5299 } 5300 5301 /** 5302 * Take aPoint, which is in the <text> element's user space, and convert 5303 * it to the appropriate frame user space of aChildFrame according to 5304 * which rendered run the point hits. 5305 */ 5306 Point SVGTextFrame::TransformFramePointToTextChild( 5307 const Point& aPoint, const nsIFrame* aChildFrame) { 5308 NS_ASSERTION(aChildFrame && nsLayoutUtils::GetClosestFrameOfType( 5309 aChildFrame->GetParent(), 5310 LayoutFrameType::SVGText) == this, 5311 "aChildFrame must be a descendant of this frame"); 5312 5313 UpdateGlyphPositioning(); 5314 5315 nsPresContext* presContext = PresContext(); 5316 5317 // Add in the mRect offset to aPoint, as that will have been taken into 5318 // account when transforming the point from the ancestor frame down 5319 // to this one. 5320 float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels( 5321 presContext->AppUnitsPerDevPixel()); 5322 float factor = AppUnitsPerCSSPixel(); 5323 Point framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), 5324 NSAppUnitsToFloatPixels(mRect.y, factor)); 5325 Point pointInUserSpace = aPoint * cssPxPerDevPx + framePosition; 5326 5327 // Find the closest rendered run for the text frames beneath aChildFrame. 5328 TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, 5329 aChildFrame); 5330 TextRenderedRun hit; 5331 gfxPoint pointInRun; 5332 nscoord dx = nscoord_MAX; 5333 nscoord dy = nscoord_MAX; 5334 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { 5335 uint32_t flags = TextRenderedRun::eIncludeFill | 5336 TextRenderedRun::eIncludeStroke | 5337 TextRenderedRun::eNoHorizontalOverflow; 5338 gfxRect runRect = run.GetRunUserSpaceRect(flags).ToThebesRect(); 5339 5340 gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext); 5341 if (!m.Invert()) { 5342 return aPoint; 5343 } 5344 gfxPoint pointInRunUserSpace = 5345 m.TransformPoint(ThebesPoint(pointInUserSpace)); 5346 5347 if (runRect.Contains(pointInRunUserSpace)) { 5348 // The point was inside the rendered run's rect, so we choose it. 5349 dx = 0; 5350 dy = 0; 5351 pointInRun = pointInRunUserSpace; 5352 hit = run; 5353 } else if (nsLayoutUtils::PointIsCloserToRect(pointInRunUserSpace, runRect, 5354 dx, dy)) { 5355 // The point was closer to this rendered run's rect than any others 5356 // we've seen so far. 5357 pointInRun.x = 5358 std::clamp(pointInRunUserSpace.x.value, runRect.X(), runRect.XMost()); 5359 pointInRun.y = 5360 std::clamp(pointInRunUserSpace.y.value, runRect.Y(), runRect.YMost()); 5361 hit = run; 5362 } 5363 } 5364 5365 if (!hit.mFrame) { 5366 // We didn't find any rendered runs for the frame. 5367 return aPoint; 5368 } 5369 5370 // Return the point in user units relative to the nsTextFrame, 5371 // but taking into account mFontSizeScaleFactor. 5372 gfxMatrix m = hit.GetTransformFromRunUserSpaceToFrameUserSpace(presContext); 5373 m.PreScale(mFontSizeScaleFactor, mFontSizeScaleFactor); 5374 return ToPoint(m.TransformPoint(pointInRun) / cssPxPerDevPx); 5375 } 5376 5377 /** 5378 * For each rendered run beneath aChildFrame, translate aRect from 5379 * aChildFrame to the run's text frame, transform it then into 5380 * the run's frame user space, intersect it with the run's 5381 * frame user space rect, then transform it up to user space. 5382 * The result is the union of all of these. 5383 */ 5384 gfxRect SVGTextFrame::TransformFrameRectFromTextChild( 5385 const nsRect& aRect, const nsIFrame* aChildFrame) { 5386 NS_ASSERTION(aChildFrame && nsLayoutUtils::GetClosestFrameOfType( 5387 aChildFrame->GetParent(), 5388 LayoutFrameType::SVGText) == this, 5389 "aChildFrame must be a descendant of this frame"); 5390 5391 UpdateGlyphPositioning(); 5392 5393 nsPresContext* presContext = PresContext(); 5394 5395 gfxRect result; 5396 TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, 5397 aChildFrame); 5398 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { 5399 // First, translate aRect from aChildFrame to this run's frame. 5400 nsRect rectInTextFrame = aRect + aChildFrame->GetOffsetTo(run.mFrame); 5401 5402 // Scale it into frame user space. 5403 gfxRect rectInFrameUserSpace = AppUnitsToFloatCSSPixels(rectInTextFrame); 5404 5405 // Intersect it with the run. 5406 uint32_t flags = 5407 TextRenderedRun::eIncludeFill | TextRenderedRun::eIncludeStroke; 5408 5409 if (rectInFrameUserSpace.IntersectRect( 5410 rectInFrameUserSpace, 5411 run.GetFrameUserSpaceRect(presContext, flags).ToThebesRect())) { 5412 // Transform it up to user space of the <text> 5413 gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext); 5414 gfxRect rectInUserSpace = m.TransformRect(rectInFrameUserSpace); 5415 5416 // Union it into the result. 5417 result.UnionRect(result, rectInUserSpace); 5418 } 5419 } 5420 5421 // Subtract the mRect offset from the result, as our user space for 5422 // this frame is relative to the top-left of mRect. 5423 float factor = AppUnitsPerCSSPixel(); 5424 gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), 5425 NSAppUnitsToFloatPixels(mRect.y, factor)); 5426 5427 return result - framePosition; 5428 } 5429 5430 Rect SVGTextFrame::TransformFrameRectFromTextChild( 5431 const Rect& aRect, const nsIFrame* aChildFrame) { 5432 nscoord appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); 5433 nsRect r = LayoutDevicePixel::ToAppUnits( 5434 LayoutDeviceRect::FromUnknownRect(aRect), appUnitsPerDevPixel); 5435 gfxRect resultCssUnits = TransformFrameRectFromTextChild(r, aChildFrame); 5436 float devPixelPerCSSPixel = 5437 float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel; 5438 resultCssUnits.Scale(devPixelPerCSSPixel); 5439 return ToRect(resultCssUnits); 5440 } 5441 5442 Point SVGTextFrame::TransformFramePointFromTextChild( 5443 const Point& aPoint, const nsIFrame* aChildFrame) { 5444 return TransformFrameRectFromTextChild(Rect(aPoint, Size(1, 1)), aChildFrame) 5445 .TopLeft(); 5446 } 5447 5448 void SVGTextFrame::AppendDirectlyOwnedAnonBoxes( 5449 nsTArray<OwnedAnonBox>& aResult) { 5450 MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box"); 5451 aResult.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild())); 5452 } 5453 5454 } // namespace mozilla