SVGContentUtils.cpp (31496B)
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 // This is also necessary to ensure our definition of M_SQRT1_2 is picked up 9 #include "SVGContentUtils.h" 10 11 // Keep others in (case-insensitive) order: 12 #include "SVGAnimatedPreserveAspectRatio.h" 13 #include "SVGGeometryProperty.h" 14 #include "SVGOuterSVGFrame.h" 15 #include "SVGPathData.h" 16 #include "SVGPathElement.h" 17 #include "gfx2DGlue.h" 18 #include "gfxMatrix.h" 19 #include "gfxPlatform.h" 20 #include "mozilla/ComputedStyle.h" 21 #include "mozilla/PresShell.h" 22 #include "mozilla/RefPtr.h" 23 #include "mozilla/SVGContextPaint.h" 24 #include "mozilla/SVGUtils.h" 25 #include "mozilla/TextUtils.h" 26 #include "mozilla/dom/SVGSVGElement.h" 27 #include "mozilla/gfx/2D.h" 28 #include "mozilla/gfx/Types.h" 29 #include "nsComputedDOMStyle.h" 30 #include "nsContainerFrame.h" 31 #include "nsContentUtils.h" 32 #include "nsFontMetrics.h" 33 #include "nsIFrame.h" 34 #include "nsIScriptError.h" 35 #include "nsLayoutUtils.h" 36 #include "nsMathUtils.h" 37 #include "nsWhitespaceTokenizer.h" 38 39 using namespace mozilla; 40 using namespace mozilla::dom; 41 using namespace mozilla::dom::SVGPreserveAspectRatio_Binding; 42 using namespace mozilla::gfx; 43 44 static bool StringToValue(const nsAString& aString, float& aValue) { 45 nsresult errorCode; 46 aValue = aString.ToFloat(&errorCode); 47 return NS_SUCCEEDED(errorCode); 48 } 49 50 static bool StringToValue(const nsAString& aString, double& aValue) { 51 nsresult errorCode; 52 aValue = aString.ToDouble(&errorCode); 53 return NS_SUCCEEDED(errorCode); 54 } 55 56 namespace mozilla { 57 58 SVGSVGElement* SVGContentUtils::GetOuterSVGElement(SVGElement* aSVGElement) { 59 Element* element = nullptr; 60 Element* ancestor = aSVGElement->GetParentElementCrossingShadowRoot(); 61 62 while (ancestor && ancestor->IsSVGElement() && 63 !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { 64 element = ancestor; 65 ancestor = element->GetParentElementCrossingShadowRoot(); 66 } 67 68 return SVGSVGElement::FromNodeOrNull(element); 69 } 70 71 enum DashState { 72 eDashedStroke, 73 eContinuousStroke, //< all dashes, no gaps 74 eNoStroke //< all gaps, no dashes 75 }; 76 77 static DashState GetStrokeDashData( 78 SVGContentUtils::AutoStrokeOptions* aStrokeOptions, SVGElement* aElement, 79 const nsStyleSVG* aStyleSVG, const SVGContextPaint* aContextPaint) { 80 size_t dashArrayLength; 81 Float totalLengthOfDashes = 0.0, totalLengthOfGaps = 0.0; 82 Float pathScale = 1.0; 83 84 if (aStyleSVG->mStrokeDasharray.IsContextValue()) { 85 if (!aContextPaint) { 86 return eContinuousStroke; 87 } 88 const FallibleTArray<Float>& dashSrc = aContextPaint->GetStrokeDashArray(); 89 dashArrayLength = dashSrc.Length(); 90 if (dashArrayLength <= 0) { 91 return eContinuousStroke; 92 } 93 Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength); 94 if (!dashPattern) { 95 return eContinuousStroke; 96 } 97 for (size_t i = 0; i < dashArrayLength; i++) { 98 if (dashSrc[i] < 0.0) { 99 return eContinuousStroke; // invalid 100 } 101 dashPattern[i] = Float(dashSrc[i]); 102 (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashSrc[i]; 103 } 104 } else { 105 const auto dasharray = aStyleSVG->mStrokeDasharray.AsValues().AsSpan(); 106 dashArrayLength = dasharray.Length(); 107 if (dashArrayLength <= 0) { 108 return eContinuousStroke; 109 } 110 if (auto* shapeElement = SVGGeometryElement::FromNode(aElement)) { 111 pathScale = 112 shapeElement->GetPathLengthScale(SVGGeometryElement::eForStroking); 113 if (pathScale <= 0 || !std::isfinite(pathScale)) { 114 return eContinuousStroke; 115 } 116 } 117 Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength); 118 if (!dashPattern) { 119 return eContinuousStroke; 120 } 121 for (uint32_t i = 0; i < dashArrayLength; i++) { 122 Float dashLength = 123 SVGContentUtils::CoordToFloat(aElement, dasharray[i]) * pathScale; 124 if (dashLength < 0.0) { 125 return eContinuousStroke; // invalid 126 } 127 dashPattern[i] = dashLength; 128 (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashLength; 129 } 130 } 131 132 // Now that aStrokeOptions.mDashPattern is fully initialized (we didn't 133 // return early above) we can safely set mDashLength: 134 aStrokeOptions->mDashLength = dashArrayLength; 135 136 if ((dashArrayLength % 2) == 1) { 137 // If we have a dash pattern with an odd number of lengths the pattern 138 // repeats a second time, per the SVG spec., and as implemented by Moz2D. 139 // When deciding whether to return eNoStroke or eContinuousStroke below we 140 // need to take into account that in the repeat pattern the dashes become 141 // gaps, and the gaps become dashes. 142 Float origTotalLengthOfDashes = totalLengthOfDashes; 143 totalLengthOfDashes += totalLengthOfGaps; 144 totalLengthOfGaps += origTotalLengthOfDashes; 145 } 146 147 // Stroking using dashes is much slower than stroking a continuous line 148 // (see bug 609361 comment 40), and much, much slower than not stroking the 149 // line at all. Here we check for cases when the dash pattern causes the 150 // stroke to essentially be continuous or to be nonexistent in which case 151 // we can avoid expensive stroking operations (the underlying platform 152 // graphics libraries don't seem to optimize for this). 153 if (totalLengthOfGaps <= 0) { 154 return eContinuousStroke; 155 } 156 // We can only return eNoStroke if the value of stroke-linecap isn't 157 // adding caps to zero length dashes. 158 if (totalLengthOfDashes <= 0 && 159 aStyleSVG->mStrokeLinecap == StyleStrokeLinecap::Butt) { 160 return eNoStroke; 161 } 162 163 if (aStyleSVG->mStrokeDashoffset.IsContextValue()) { 164 aStrokeOptions->mDashOffset = 165 Float(aContextPaint ? aContextPaint->GetStrokeDashOffset() : 0); 166 } else { 167 aStrokeOptions->mDashOffset = 168 SVGContentUtils::CoordToFloat( 169 aElement, aStyleSVG->mStrokeDashoffset.AsLengthPercentage()) * 170 pathScale; 171 } 172 173 return eDashedStroke; 174 } 175 176 void SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions, 177 SVGElement* aElement, 178 const ComputedStyle* aComputedStyle, 179 const SVGContextPaint* aContextPaint, 180 StrokeOptionFlags aFlags) { 181 auto doCompute = [&](const ComputedStyle* computedStyle) { 182 const nsStyleSVG* styleSVG = computedStyle->StyleSVG(); 183 184 bool checkedDashAndStrokeIsDashed = false; 185 if (!aFlags.contains(StrokeOptionFlag::IgnoreStrokeDashing)) { 186 DashState dashState = 187 GetStrokeDashData(aStrokeOptions, aElement, styleSVG, aContextPaint); 188 189 if (dashState == eNoStroke) { 190 // Hopefully this will shortcircuit any stroke operations: 191 aStrokeOptions->mLineWidth = 0; 192 return; 193 } 194 if (dashState == eContinuousStroke && aStrokeOptions->mDashPattern) { 195 // Prevent our caller from wasting time looking at a pattern without 196 // gaps: 197 aStrokeOptions->DiscardDashPattern(); 198 } 199 checkedDashAndStrokeIsDashed = (dashState == eDashedStroke); 200 } 201 202 aStrokeOptions->mLineWidth = 203 GetStrokeWidth(aElement, computedStyle, aContextPaint); 204 205 aStrokeOptions->mMiterLimit = Float(styleSVG->mStrokeMiterlimit); 206 207 switch (styleSVG->mStrokeLinejoin) { 208 case StyleStrokeLinejoin::Miter: 209 aStrokeOptions->mLineJoin = JoinStyle::MITER_OR_BEVEL; 210 break; 211 case StyleStrokeLinejoin::Round: 212 aStrokeOptions->mLineJoin = JoinStyle::ROUND; 213 break; 214 case StyleStrokeLinejoin::Bevel: 215 aStrokeOptions->mLineJoin = JoinStyle::BEVEL; 216 break; 217 } 218 219 if (ShapeTypeHasNoCorners(aElement) && !checkedDashAndStrokeIsDashed) { 220 // Note: if aFlags == eIgnoreStrokeDashing then we may be returning the 221 // wrong linecap value here, since the actual linecap used on render in 222 // this case depends on whether the stroke is dashed or not. 223 aStrokeOptions->mLineCap = CapStyle::BUTT; 224 } else { 225 switch (styleSVG->mStrokeLinecap) { 226 case StyleStrokeLinecap::Butt: 227 aStrokeOptions->mLineCap = CapStyle::BUTT; 228 break; 229 case StyleStrokeLinecap::Round: 230 aStrokeOptions->mLineCap = CapStyle::ROUND; 231 break; 232 case StyleStrokeLinecap::Square: 233 aStrokeOptions->mLineCap = CapStyle::SQUARE; 234 break; 235 } 236 } 237 }; 238 239 if (aComputedStyle) { 240 doCompute(aComputedStyle); 241 } else { 242 SVGGeometryProperty::DoForComputedStyle(aElement, doCompute); 243 } 244 } 245 246 Float SVGContentUtils::GetStrokeWidth(const SVGElement* aElement, 247 const ComputedStyle* aComputedStyle, 248 const SVGContextPaint* aContextPaint) { 249 Float res = 0.0; 250 251 auto doCompute = [&](const ComputedStyle* computedStyle) { 252 const nsStyleSVG* styleSVG = computedStyle->StyleSVG(); 253 254 if (styleSVG->mStrokeWidth.IsContextValue()) { 255 res = aContextPaint ? aContextPaint->GetStrokeWidth() : 1.0; 256 } else { 257 auto& lp = styleSVG->mStrokeWidth.AsLengthPercentage(); 258 if (lp.HasPercent() && aElement) { 259 auto counter = 260 aElement->IsSVGElement(nsGkAtoms::text) 261 ? UseCounter::eUseCounter_custom_PercentageStrokeWidthInSVGText 262 : UseCounter::eUseCounter_custom_PercentageStrokeWidthInSVG; 263 aElement->OwnerDoc()->SetUseCounter(counter); 264 } 265 res = SVGContentUtils::CoordToFloat(aElement, lp); 266 } 267 }; 268 269 if (aComputedStyle) { 270 doCompute(aComputedStyle); 271 } else { 272 SVGGeometryProperty::DoForComputedStyle(aElement, doCompute); 273 } 274 275 return res; 276 } 277 278 float SVGContentUtils::GetFontSize(const Element* aElement) { 279 if (!aElement) { 280 return 1.0f; 281 } 282 283 nsPresContext* pc = nsContentUtils::GetContextForContent(aElement); 284 if (!pc) { 285 return 1.0f; 286 } 287 288 if (auto* f = aElement->GetPrimaryFrame()) { 289 return GetFontSize(f->Style(), pc); 290 } 291 292 if (RefPtr<const ComputedStyle> style = 293 nsComputedDOMStyle::GetComputedStyleNoFlush(aElement)) { 294 return GetFontSize(style, pc); 295 } 296 297 // ReportToConsole 298 NS_WARNING("Couldn't get ComputedStyle for content in GetFontStyle"); 299 return 1.0f; 300 } 301 302 float SVGContentUtils::GetFontSize(const nsIFrame* aFrame) { 303 MOZ_ASSERT(aFrame, "NULL frame in GetFontSize"); 304 return GetFontSize(aFrame->Style(), aFrame->PresContext()); 305 } 306 307 float SVGContentUtils::GetFontSize(const ComputedStyle* aComputedStyle, 308 nsPresContext* aPresContext) { 309 MOZ_ASSERT(aComputedStyle); 310 MOZ_ASSERT(aPresContext); 311 312 return aComputedStyle->StyleFont()->mSize.ToCSSPixels() / 313 aPresContext->TextZoom(); 314 } 315 316 float SVGContentUtils::GetFontXHeight(const Element* aElement) { 317 if (!aElement) { 318 return 1.0f; 319 } 320 321 nsPresContext* pc = nsContentUtils::GetContextForContent(aElement); 322 if (!pc) { 323 return 1.0f; 324 } 325 326 if (auto* f = aElement->GetPrimaryFrame()) { 327 return GetFontXHeight(f->Style(), pc); 328 } 329 330 if (RefPtr<const ComputedStyle> style = 331 nsComputedDOMStyle::GetComputedStyleNoFlush(aElement)) { 332 return GetFontXHeight(style, pc); 333 } 334 335 // ReportToConsole 336 NS_WARNING("Couldn't get ComputedStyle for content in GetFontStyle"); 337 return 1.0f; 338 } 339 340 float SVGContentUtils::GetFontXHeight(const nsIFrame* aFrame) { 341 MOZ_ASSERT(aFrame, "NULL frame in GetFontXHeight"); 342 return GetFontXHeight(aFrame->Style(), aFrame->PresContext()); 343 } 344 345 float SVGContentUtils::GetFontXHeight(const ComputedStyle* aComputedStyle, 346 nsPresContext* aPresContext) { 347 MOZ_ASSERT(aComputedStyle && aPresContext); 348 349 RefPtr<nsFontMetrics> fontMetrics = 350 nsLayoutUtils::GetFontMetricsForComputedStyle(aComputedStyle, 351 aPresContext); 352 353 if (!fontMetrics) { 354 // ReportToConsole 355 NS_WARNING("no FontMetrics in GetFontXHeight()"); 356 return 1.0f; 357 } 358 359 nscoord xHeight = fontMetrics->XHeight(); 360 return nsPresContext::AppUnitsToFloatCSSPixels(xHeight) / 361 aPresContext->TextZoom(); 362 } 363 364 float SVGContentUtils::GetLineHeight(const Element* aElement) { 365 float result = 16.0f * ReflowInput::kNormalLineHeightFactor; 366 if (!aElement) { 367 return result; 368 } 369 SVGGeometryProperty::DoForComputedStyle( 370 aElement, [&](const ComputedStyle* style) { 371 auto* context = nsContentUtils::GetContextForContent(aElement); 372 if (!context) { 373 return; 374 } 375 const auto lineHeightAu = ReflowInput::CalcLineHeight( 376 *style, context, aElement, NS_UNCONSTRAINEDSIZE, 1.0f); 377 result = CSSPixel::FromAppUnits(lineHeightAu); 378 }); 379 380 return result; 381 } 382 383 nsresult SVGContentUtils::ReportToConsole(const Document* doc, 384 const char* aWarning, 385 const nsTArray<nsString>& aParams) { 386 return nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "SVG"_ns, 387 doc, nsContentUtils::eSVG_PROPERTIES, 388 aWarning, aParams); 389 } 390 391 static bool EstablishesViewport(const nsIContent* aContent) { 392 MOZ_ASSERT(aContent, "Expecting aContent to be non-null"); 393 394 // A symbol element only establishes a viewport if it is instanced by a use 395 // element. 396 if (aContent->IsSVGElement(nsGkAtoms::symbol) && 397 aContent->IsInSVGUseShadowTree()) { 398 return true; 399 } 400 return aContent->IsSVGElement(nsGkAtoms::svg); 401 } 402 403 SVGViewportElement* SVGContentUtils::GetNearestViewportElement( 404 const nsIContent* aContent) { 405 nsIContent* element = aContent->GetFlattenedTreeParent(); 406 407 while (element && element->IsSVGElement()) { 408 if (element->IsSVGElement(nsGkAtoms::foreignObject)) { 409 return nullptr; 410 } 411 if (EstablishesViewport(element)) { 412 return static_cast<SVGViewportElement*>(element); 413 } 414 element = element->GetFlattenedTreeParent(); 415 } 416 return nullptr; 417 } 418 419 enum class CTMType { NearestViewport, NonScalingStroke, Screen }; 420 421 static gfx::Matrix GetCTMInternal(SVGElement* aElement, CTMType aCTMType, 422 bool aHaveRecursed) { 423 auto getLocalTransformHelper = 424 [](SVGElement const* e, bool shouldIncludeChildToUserSpace) -> gfxMatrix { 425 gfxMatrix ret; 426 if (auto* f = e->GetPrimaryFrame()) { 427 ret = SVGUtils::GetTransformMatrixInUserSpace(f); 428 } 429 if (shouldIncludeChildToUserSpace) { 430 auto t = e->ChildToUserSpaceTransform(); 431 if (!t.IsSingular()) { 432 ret = t * ret; 433 } 434 } 435 return ret; 436 }; 437 438 auto postTranslateFrameOffset = [](nsIFrame* aFrame, nsIFrame* aAncestorFrame, 439 gfx::Matrix& aMatrix) { 440 auto point = aFrame->GetOffsetTo(aAncestorFrame); 441 aMatrix.PostTranslate(nsPresContext::AppUnitsToFloatCSSPixels(point.x), 442 nsPresContext::AppUnitsToFloatCSSPixels(point.y)); 443 }; 444 445 gfxMatrix matrix = getLocalTransformHelper(aElement, aHaveRecursed); 446 447 SVGElement* element = aElement; 448 nsIContent* ancestor = aElement->GetFlattenedTreeParent(); 449 450 while (ancestor && ancestor->IsSVGElement() && 451 !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { 452 element = static_cast<SVGElement*>(ancestor); 453 if (aCTMType == CTMType::NonScalingStroke) { 454 if (auto* el = SVGSVGElement::FromNode(element); el && !el->IsInner()) { 455 if (SVGOuterSVGFrame* frame = 456 do_QueryFrame(element->GetPrimaryFrame())) { 457 Matrix childTransform; 458 if (frame->HasChildrenOnlyTransform(&childTransform) && 459 !childTransform.IsSingular()) { 460 return gfx::ToMatrix(matrix) * childTransform; 461 } 462 } 463 return gfx::ToMatrix(matrix); 464 } 465 } 466 matrix *= getLocalTransformHelper(element, true); 467 if (aCTMType == CTMType::NearestViewport) { 468 if (element->IsSVGElement(nsGkAtoms::foreignObject)) { 469 return {}; 470 } 471 if (EstablishesViewport(element)) { 472 // XXX spec seems to say x,y translation should be undone for IsInnerSVG 473 return gfx::ToMatrix(matrix); 474 } 475 } 476 ancestor = ancestor->GetFlattenedTreeParent(); 477 } 478 if (aCTMType == CTMType::NearestViewport) { 479 // didn't find a nearestViewportElement 480 return {}; 481 } 482 if (!element->IsSVGElement(nsGkAtoms::svg)) { 483 // Not a valid SVG fragment 484 return {}; 485 } 486 if (element == aElement && !aHaveRecursed) { 487 // We get here when getScreenCTM() is called on an outer-<svg>. 488 // Consistency with other elements would have us include only the 489 // eFromUserSpace transforms, but we include the eAllTransforms 490 // transforms in this case since that's what we've been doing for 491 // a while, and it keeps us consistent with WebKit and Opera (if not 492 // really with the ambiguous spec). 493 matrix = getLocalTransformHelper(aElement, true); 494 } 495 496 gfx::Matrix tm = gfx::ToMatrix(matrix); 497 nsIFrame* frame = element->GetPrimaryFrame(); 498 if (!frame) { 499 return tm; 500 } 501 if (frame->IsSVGOuterSVGFrame()) { 502 nsMargin bp = frame->GetUsedBorderAndPadding(); 503 int32_t appUnitsPerCSSPixel = AppUnitsPerCSSPixel(); 504 nscoord xOffset, yOffset; 505 // See 506 // https://drafts.csswg.org/css-transforms/#valdef-transform-box-fill-box 507 // For elements with associated CSS layout box, the used value for fill-box 508 // is content-box and for stroke-box and view-box is border-box. 509 switch (frame->StyleDisplay()->mTransformBox) { 510 case StyleTransformBox::FillBox: 511 case StyleTransformBox::ContentBox: 512 xOffset = bp.left; 513 yOffset = bp.top; 514 break; 515 case StyleTransformBox::StrokeBox: 516 case StyleTransformBox::ViewBox: 517 case StyleTransformBox::BorderBox: { 518 // Extract the rotation component of the matrix. 519 float angle = std::atan2(tm._12, tm._11); 520 float cosAngle = std::cos(angle); 521 float sinAngle = std::sin(angle); 522 // Apply that rotation to bp.left and bp.top. 523 xOffset = bp.left * cosAngle - bp.top * sinAngle; 524 yOffset = bp.top * cosAngle + bp.left * sinAngle; 525 break; 526 } 527 } 528 tm.PostTranslate(NSAppUnitsToFloatPixels(xOffset, appUnitsPerCSSPixel), 529 NSAppUnitsToFloatPixels(yOffset, appUnitsPerCSSPixel)); 530 } 531 532 if (!ancestor || !ancestor->IsElement()) { 533 return tm; 534 } 535 if (auto* ancestorSVG = SVGElement::FromNode(ancestor)) { 536 return tm * GetCTMInternal(ancestorSVG, aCTMType, true); 537 } 538 nsIFrame* parentFrame = frame->GetParent(); 539 if (!parentFrame) { 540 return tm; 541 } 542 postTranslateFrameOffset(frame, parentFrame, tm); 543 544 nsIContent* nearestSVGAncestor = ancestor; 545 while (nearestSVGAncestor && !nearestSVGAncestor->IsSVGElement()) { 546 nearestSVGAncestor = nearestSVGAncestor->GetFlattenedTreeParent(); 547 } 548 549 nsIFrame* ancestorFrame; 550 if (nearestSVGAncestor) { 551 ancestorFrame = nearestSVGAncestor->GetPrimaryFrame(); 552 } else { 553 Document* currentDoc = aElement->GetComposedDoc(); 554 PresShell* presShell = currentDoc ? currentDoc->GetPresShell() : nullptr; 555 ancestorFrame = presShell ? presShell->GetRootFrame() : nullptr; 556 } 557 if (!ancestorFrame) { 558 return tm; 559 } 560 auto transformToAncestor = nsLayoutUtils::GetTransformToAncestor( 561 RelativeTo{parentFrame, ViewportType::Layout}, 562 RelativeTo{ancestorFrame, ViewportType::Layout}, nsIFrame::IN_CSS_UNITS); 563 gfx::Matrix result2d; 564 if (transformToAncestor.CanDraw2D(&result2d)) { 565 tm = tm * result2d; 566 } else { 567 // The transform from our outer SVG matrix to the root is a 3D 568 // transform. We can't really process that so give up and just 569 // return the overall translation from the outer SVG to the root. 570 postTranslateFrameOffset(parentFrame, ancestorFrame, tm); 571 } 572 return nearestSVGAncestor 573 ? tm * GetCTMInternal(static_cast<SVGElement*>(nearestSVGAncestor), 574 aCTMType, true) 575 : tm; 576 } 577 578 gfx::Matrix SVGContentUtils::GetCTM(SVGElement* aElement) { 579 return GetCTMInternal(aElement, CTMType::NearestViewport, false); 580 } 581 582 gfx::Matrix SVGContentUtils::GetNonScalingStrokeCTM(SVGElement* aElement) { 583 return GetCTMInternal(aElement, CTMType::NonScalingStroke, false); 584 } 585 586 gfx::Matrix SVGContentUtils::GetScreenCTM(SVGElement* aElement) { 587 return GetCTMInternal(aElement, CTMType::Screen, false); 588 } 589 590 void SVGContentUtils::RectilinearGetStrokeBounds( 591 const Rect& aRect, const Matrix& aToBoundsSpace, 592 const Matrix& aToNonScalingStrokeSpace, float aStrokeWidth, Rect* aBounds) { 593 MOZ_ASSERT(aToBoundsSpace.IsRectilinear(), 594 "aToBoundsSpace must be rectilinear"); 595 MOZ_ASSERT(aToNonScalingStrokeSpace.IsRectilinear(), 596 "aToNonScalingStrokeSpace must be rectilinear"); 597 598 Matrix nonScalingToSource = aToNonScalingStrokeSpace.Inverse(); 599 Matrix nonScalingToBounds = nonScalingToSource * aToBoundsSpace; 600 601 *aBounds = aToBoundsSpace.TransformBounds(aRect); 602 603 // Compute the amounts dx and dy that nonScalingToBounds scales a half-width 604 // stroke in the x and y directions, and then inflate aBounds by those amounts 605 // so that when aBounds is transformed back to non-scaling-stroke space 606 // it will map onto the correct stroked bounds. 607 608 Float dx = 0.0f; 609 Float dy = 0.0f; 610 // nonScalingToBounds is rectilinear, so either _12 and _21 are zero or _11 611 // and _22 are zero, and in each case the non-zero entries (from among _11, 612 // _12, _21, _22) simply scale the stroke width in the x and y directions. 613 if (FuzzyEqual(nonScalingToBounds._12, 0) && 614 FuzzyEqual(nonScalingToBounds._21, 0)) { 615 dx = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._11); 616 dy = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._22); 617 } else { 618 dx = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._21); 619 dy = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._12); 620 } 621 622 aBounds->Inflate(dx, dy); 623 } 624 625 double SVGContentUtils::ComputeNormalizedHypotenuse(double aWidth, 626 double aHeight) { 627 return NS_hypot(aWidth, aHeight) / M_SQRT2; 628 } 629 630 float SVGContentUtils::AngleBisect(float a1, float a2) { 631 float delta = std::fmod(a2 - a1, static_cast<float>(2 * M_PI)); 632 if (delta < 0) { 633 delta += static_cast<float>(2 * M_PI); 634 } 635 /* delta is now the angle from a1 around to a2, in the range [0, 2*M_PI) */ 636 float r = a1 + delta / 2; 637 if (delta >= M_PI) { 638 /* the arc from a2 to a1 is smaller, so use the ray on that side */ 639 r += static_cast<float>(M_PI); 640 } 641 return r; 642 } 643 644 gfx::Matrix SVGContentUtils::GetViewBoxTransform( 645 float aViewportWidth, float aViewportHeight, float aViewboxX, 646 float aViewboxY, float aViewboxWidth, float aViewboxHeight, 647 const SVGAnimatedPreserveAspectRatio& aPreserveAspectRatio) { 648 return GetViewBoxTransform(aViewportWidth, aViewportHeight, aViewboxX, 649 aViewboxY, aViewboxWidth, aViewboxHeight, 650 aPreserveAspectRatio.GetAnimValue()); 651 } 652 653 gfx::Matrix SVGContentUtils::GetViewBoxTransform( 654 float aViewportWidth, float aViewportHeight, float aViewboxX, 655 float aViewboxY, float aViewboxWidth, float aViewboxHeight, 656 const SVGPreserveAspectRatio& aPreserveAspectRatio) { 657 NS_ASSERTION(aViewportWidth >= 0, "viewport width must be nonnegative!"); 658 NS_ASSERTION(aViewportHeight >= 0, "viewport height must be nonnegative!"); 659 NS_ASSERTION(aViewboxWidth > 0, "viewBox width must be greater than zero!"); 660 NS_ASSERTION(aViewboxHeight > 0, "viewBox height must be greater than zero!"); 661 662 uint16_t align = aPreserveAspectRatio.GetAlign(); 663 uint16_t meetOrSlice = aPreserveAspectRatio.GetMeetOrSlice(); 664 665 // default to the defaults 666 if (align == SVG_PRESERVEASPECTRATIO_UNKNOWN) 667 align = SVG_PRESERVEASPECTRATIO_XMIDYMID; 668 if (meetOrSlice == SVG_MEETORSLICE_UNKNOWN) 669 meetOrSlice = SVG_MEETORSLICE_MEET; 670 671 float a, d, e, f; 672 a = aViewportWidth / aViewboxWidth; 673 d = aViewportHeight / aViewboxHeight; 674 e = 0.0f; 675 f = 0.0f; 676 677 if (align != SVG_PRESERVEASPECTRATIO_NONE && a != d) { 678 if ((meetOrSlice == SVG_MEETORSLICE_MEET && a < d) || 679 (meetOrSlice == SVG_MEETORSLICE_SLICE && d < a)) { 680 d = a; 681 switch (align) { 682 case SVG_PRESERVEASPECTRATIO_XMINYMIN: 683 case SVG_PRESERVEASPECTRATIO_XMIDYMIN: 684 case SVG_PRESERVEASPECTRATIO_XMAXYMIN: 685 break; 686 case SVG_PRESERVEASPECTRATIO_XMINYMID: 687 case SVG_PRESERVEASPECTRATIO_XMIDYMID: 688 case SVG_PRESERVEASPECTRATIO_XMAXYMID: 689 f = (aViewportHeight - a * aViewboxHeight) / 2.0f; 690 break; 691 case SVG_PRESERVEASPECTRATIO_XMINYMAX: 692 case SVG_PRESERVEASPECTRATIO_XMIDYMAX: 693 case SVG_PRESERVEASPECTRATIO_XMAXYMAX: 694 f = aViewportHeight - a * aViewboxHeight; 695 break; 696 default: 697 MOZ_ASSERT_UNREACHABLE("Unknown value for align"); 698 } 699 } else if ((meetOrSlice == SVG_MEETORSLICE_MEET && d < a) || 700 (meetOrSlice == SVG_MEETORSLICE_SLICE && a < d)) { 701 a = d; 702 switch (align) { 703 case SVG_PRESERVEASPECTRATIO_XMINYMIN: 704 case SVG_PRESERVEASPECTRATIO_XMINYMID: 705 case SVG_PRESERVEASPECTRATIO_XMINYMAX: 706 break; 707 case SVG_PRESERVEASPECTRATIO_XMIDYMIN: 708 case SVG_PRESERVEASPECTRATIO_XMIDYMID: 709 case SVG_PRESERVEASPECTRATIO_XMIDYMAX: 710 e = (aViewportWidth - a * aViewboxWidth) / 2.0f; 711 break; 712 case SVG_PRESERVEASPECTRATIO_XMAXYMIN: 713 case SVG_PRESERVEASPECTRATIO_XMAXYMID: 714 case SVG_PRESERVEASPECTRATIO_XMAXYMAX: 715 e = aViewportWidth - a * aViewboxWidth; 716 break; 717 default: 718 MOZ_ASSERT_UNREACHABLE("Unknown value for align"); 719 } 720 } else 721 MOZ_ASSERT_UNREACHABLE("Unknown value for meetOrSlice"); 722 } 723 724 if (aViewboxX) e += -a * aViewboxX; 725 if (aViewboxY) f += -d * aViewboxY; 726 727 return gfx::Matrix(a, 0.0f, 0.0f, d, e, f); 728 } 729 730 template <class floatType> 731 bool SVGContentUtils::ParseNumber(nsAString::const_iterator& aIter, 732 const nsAString::const_iterator& aEnd, 733 floatType& aValue) { 734 const nsAString::const_iterator start(aIter); 735 auto resetIterator = mozilla::MakeScopeExit([&]() { aIter = start; }); 736 int32_t sign; 737 if (!SVGContentUtils::ParseOptionalSign(aIter, aEnd, sign)) { 738 return false; 739 } 740 741 bool gotDot = *aIter == '.'; 742 743 if (!gotDot) { 744 if (!mozilla::IsAsciiDigit(*aIter)) { 745 return false; 746 } 747 do { 748 ++aIter; 749 } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); 750 751 if (aIter != aEnd) { 752 gotDot = *aIter == '.'; 753 } 754 } 755 756 if (gotDot) { 757 ++aIter; 758 if (aIter == aEnd || !mozilla::IsAsciiDigit(*aIter)) { 759 return false; 760 } 761 762 do { 763 ++aIter; 764 } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); 765 } 766 767 bool gotE = false; 768 769 if (aIter != aEnd && (*aIter == 'e' || *aIter == 'E')) { 770 nsAString::const_iterator expIter(aIter); 771 772 ++expIter; 773 if (expIter != aEnd) { 774 if (*expIter == '-' || *expIter == '+') { 775 ++expIter; 776 } 777 if (expIter != aEnd && mozilla::IsAsciiDigit(*expIter)) { 778 // At this point we're sure this is an exponent 779 // and not the start of a unit such as em or ex. 780 gotE = true; 781 } 782 } 783 784 if (gotE) { 785 aIter = expIter; 786 do { 787 ++aIter; 788 } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); 789 } 790 } 791 792 resetIterator.release(); 793 return ::StringToValue(Substring(start, aIter), aValue); 794 } 795 796 template bool SVGContentUtils::ParseNumber<float>( 797 nsAString::const_iterator& aIter, const nsAString::const_iterator& aEnd, 798 float& aValue); 799 template bool SVGContentUtils::ParseNumber<double>( 800 nsAString::const_iterator& aIter, const nsAString::const_iterator& aEnd, 801 double& aValue); 802 803 template <class floatType> 804 bool SVGContentUtils::ParseNumber(const nsAString& aString, floatType& aValue) { 805 nsAString::const_iterator iter, end; 806 aString.BeginReading(iter); 807 aString.EndReading(end); 808 809 return ParseNumber(iter, end, aValue) && iter == end; 810 } 811 812 template bool SVGContentUtils::ParseNumber<float>(const nsAString& aString, 813 float& aValue); 814 template bool SVGContentUtils::ParseNumber<double>(const nsAString& aString, 815 double& aValue); 816 817 /* static */ 818 bool SVGContentUtils::ParseInteger(nsAString::const_iterator& aIter, 819 const nsAString::const_iterator& aEnd, 820 int32_t& aValue) { 821 nsAString::const_iterator iter(aIter); 822 823 int32_t sign; 824 if (!ParseOptionalSign(iter, aEnd, sign)) { 825 return false; 826 } 827 828 if (!mozilla::IsAsciiDigit(*iter)) { 829 return false; 830 } 831 832 int64_t value = 0; 833 834 do { 835 if (value <= std::numeric_limits<int32_t>::max()) { 836 value = 10 * value + mozilla::AsciiAlphanumericToNumber(*iter); 837 } 838 ++iter; 839 } while (iter != aEnd && mozilla::IsAsciiDigit(*iter)); 840 841 aIter = iter; 842 aValue = int32_t(std::clamp(sign * value, 843 int64_t(std::numeric_limits<int32_t>::min()), 844 int64_t(std::numeric_limits<int32_t>::max()))); 845 return true; 846 } 847 848 /* static */ 849 bool SVGContentUtils::ParseInteger(const nsAString& aString, int32_t& aValue) { 850 nsAString::const_iterator iter, end; 851 aString.BeginReading(iter); 852 aString.EndReading(end); 853 854 return ParseInteger(iter, end, aValue) && iter == end; 855 } 856 857 float SVGContentUtils::CoordToFloat(const SVGElement* aContent, 858 const LengthPercentage& aLength, 859 uint8_t aCtxType) { 860 float result = aLength.ResolveToCSSPixelsWith([&] { 861 SVGViewportElement* ctx = aContent->GetCtx(); 862 return CSSCoord(ctx ? ctx->GetLength(aCtxType) : 0.0f); 863 }); 864 if (aLength.IsCalc()) { 865 const auto& calc = aLength.AsCalc(); 866 if (calc.clamping_mode == StyleAllowedNumericType::NonNegative) { 867 result = std::max(result, 0.0f); 868 } else { 869 MOZ_ASSERT(calc.clamping_mode == StyleAllowedNumericType::All); 870 } 871 } 872 return result; 873 } 874 875 already_AddRefed<gfx::Path> SVGContentUtils::GetPath( 876 const nsACString& aPathString) { 877 SVGPathData pathData(aPathString); 878 if (pathData.IsEmpty()) { 879 return nullptr; 880 } 881 882 RefPtr<DrawTarget> drawTarget = 883 gfxPlatform::ThreadLocalScreenReferenceDrawTarget(); 884 RefPtr<PathBuilder> builder = 885 drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); 886 887 // This is called from canvas, so we don't need to get the effective zoom here 888 // or so. 889 return pathData.BuildPath(builder, StyleStrokeLinecap::Butt, 1, 1.0f); 890 } 891 892 bool SVGContentUtils::ShapeTypeHasNoCorners(const nsIContent* aContent) { 893 return aContent && 894 aContent->IsAnyOfSVGElements(nsGkAtoms::circle, nsGkAtoms::ellipse); 895 } 896 897 nsDependentSubstring SVGContentUtils::GetAndEnsureOneToken( 898 const nsAString& aString, bool& aSuccess) { 899 nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tokenizer( 900 aString); 901 902 aSuccess = false; 903 if (!tokenizer.hasMoreTokens()) { 904 return {}; 905 } 906 auto token = tokenizer.nextToken(); 907 if (tokenizer.hasMoreTokens()) { 908 return {}; 909 } 910 911 aSuccess = true; 912 return token; 913 } 914 915 } // namespace mozilla