tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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