tor-browser

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

SVGUtils.cpp (55930B)


      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 "SVGUtils.h"
     10 
     11 #include <algorithm>
     12 
     13 // Keep others in (case-insensitive) order:
     14 #include "SVGAnimatedLength.h"
     15 #include "SVGPaintServerFrame.h"
     16 #include "gfx2DGlue.h"
     17 #include "gfxContext.h"
     18 #include "gfxMatrix.h"
     19 #include "gfxPlatform.h"
     20 #include "gfxRect.h"
     21 #include "gfxUtils.h"
     22 #include "mozilla/CSSClipPathInstance.h"
     23 #include "mozilla/FilterInstance.h"
     24 #include "mozilla/ISVGDisplayableFrame.h"
     25 #include "mozilla/Preferences.h"
     26 #include "mozilla/PresShell.h"
     27 #include "mozilla/SVGClipPathFrame.h"
     28 #include "mozilla/SVGContainerFrame.h"
     29 #include "mozilla/SVGContentUtils.h"
     30 #include "mozilla/SVGContextPaint.h"
     31 #include "mozilla/SVGForeignObjectFrame.h"
     32 #include "mozilla/SVGGeometryFrame.h"
     33 #include "mozilla/SVGIntegrationUtils.h"
     34 #include "mozilla/SVGMaskFrame.h"
     35 #include "mozilla/SVGObserverUtils.h"
     36 #include "mozilla/SVGOuterSVGFrame.h"
     37 #include "mozilla/SVGTextFrame.h"
     38 #include "mozilla/StaticPrefs_svg.h"
     39 #include "mozilla/dom/Document.h"
     40 #include "mozilla/dom/SVGClipPathElement.h"
     41 #include "mozilla/dom/SVGGeometryElement.h"
     42 #include "mozilla/dom/SVGPathElement.h"
     43 #include "mozilla/dom/SVGUnitTypesBinding.h"
     44 #include "mozilla/dom/SVGViewportElement.h"
     45 #include "mozilla/gfx/2D.h"
     46 #include "mozilla/gfx/PatternHelpers.h"
     47 #include "nsCSSFrameConstructor.h"
     48 #include "nsDisplayList.h"
     49 #include "nsFrameList.h"
     50 #include "nsGkAtoms.h"
     51 #include "nsIContent.h"
     52 #include "nsIFrame.h"
     53 #include "nsIFrameInlines.h"
     54 #include "nsLayoutUtils.h"
     55 #include "nsPresContext.h"
     56 #include "nsStyleStruct.h"
     57 #include "nsStyleTransformMatrix.h"
     58 #include "nsTextFrame.h"
     59 
     60 using namespace mozilla::dom;
     61 using namespace mozilla::dom::SVGUnitTypes_Binding;
     62 using namespace mozilla::gfx;
     63 using namespace mozilla::image;
     64 
     65 bool NS_SVGNewGetBBoxEnabled() {
     66  return mozilla::StaticPrefs::svg_new_getBBox_enabled();
     67 }
     68 
     69 namespace mozilla {
     70 
     71 // we only take the address of this:
     72 static gfx::UserDataKey sSVGAutoRenderStateKey;
     73 
     74 SVGAutoRenderState::SVGAutoRenderState(DrawTarget* aDrawTarget)
     75    : mDrawTarget(aDrawTarget),
     76      mOriginalRenderState(nullptr),
     77      mPaintingToWindow(false) {
     78  mOriginalRenderState = aDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
     79  // We always remove ourselves from aContext before it dies, so
     80  // passing nullptr as the destroy function is okay.
     81  aDrawTarget->AddUserData(&sSVGAutoRenderStateKey, this, nullptr);
     82 }
     83 
     84 SVGAutoRenderState::~SVGAutoRenderState() {
     85  mDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
     86  if (mOriginalRenderState) {
     87    mDrawTarget->AddUserData(&sSVGAutoRenderStateKey, mOriginalRenderState,
     88                             nullptr);
     89  }
     90 }
     91 
     92 void SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) {
     93  mPaintingToWindow = aPaintingToWindow;
     94 }
     95 
     96 /* static */
     97 bool SVGAutoRenderState::IsPaintingToWindow(DrawTarget* aDrawTarget) {
     98  void* state = aDrawTarget->GetUserData(&sSVGAutoRenderStateKey);
     99  if (state) {
    100    return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow;
    101  }
    102  return false;
    103 }
    104 
    105 // Unlike containers, leaf frames do not include GetPosition() in
    106 // GetCanvasTM().
    107 static bool FrameDoesNotIncludePositionInTM(const nsIFrame* aFrame) {
    108  return aFrame->IsSVGGeometryFrame() || aFrame->IsSVGImageFrame() ||
    109         aFrame->IsInSVGTextSubtree();
    110 }
    111 
    112 nsRect SVGUtils::GetPostFilterInkOverflowRect(nsIFrame* aFrame,
    113                                              const nsRect& aPreFilterRect) {
    114  MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
    115             "Called on invalid frame type");
    116 
    117  // Note: we do not return here for eHasNoRefs since we must still handle any
    118  // CSS filter functions.
    119  // in that case we disable painting of the element.
    120  nsTArray<SVGFilterFrame*> filterFrames;
    121  if (!aFrame->StyleEffects()->HasFilters() ||
    122      SVGObserverUtils::GetAndObserveFilters(aFrame, &filterFrames) ==
    123          SVGObserverUtils::eHasRefsSomeInvalid) {
    124    return aPreFilterRect;
    125  }
    126 
    127  return FilterInstance::GetPostFilterBounds(aFrame, filterFrames, nullptr,
    128                                             &aPreFilterRect)
    129      .valueOr(aPreFilterRect);
    130 }
    131 
    132 bool SVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame* aFrame) {
    133  return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG();
    134 }
    135 
    136 bool SVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) {
    137  SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
    138  do {
    139    if (outer->IsCallingReflowSVG()) {
    140      return true;
    141    }
    142    outer = GetOuterSVGFrame(outer->GetParent());
    143  } while (outer);
    144  return false;
    145 }
    146 
    147 void SVGUtils::ScheduleReflowSVG(nsIFrame* aFrame) {
    148  MOZ_ASSERT(aFrame->IsSVGFrame(), "Passed bad frame!");
    149 
    150  // If this is triggered, the callers should be fixed to call us before
    151  // ReflowSVG is called. If we try to mark dirty bits on frames while we're
    152  // in the process of removing them, things will get messed up.
    153  MOZ_ASSERT(!OuterSVGIsCallingReflowSVG(aFrame),
    154             "Do not call under ISVGDisplayableFrame::ReflowSVG!");
    155 
    156  // We don't call SVGObserverUtils::InvalidateRenderingObservers here because
    157  // we should only be called under InvalidateAndScheduleReflowSVG (which
    158  // calls InvalidateBounds) or SVGDisplayContainerFrame::InsertFrames
    159  // (at which point the frame has no observers).
    160 
    161  if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
    162    return;
    163  }
    164 
    165  if (aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) {
    166    // Nothing to do if we're already dirty, or if the outer-<svg>
    167    // hasn't yet had its initial reflow.
    168    return;
    169  }
    170 
    171  SVGOuterSVGFrame* outerSVGFrame = nullptr;
    172 
    173  // We must not add dirty bits to the SVGOuterSVGFrame or else
    174  // PresShell::FrameNeedsReflow won't work when we pass it in below.
    175  if (aFrame->IsSVGOuterSVGFrame()) {
    176    outerSVGFrame = static_cast<SVGOuterSVGFrame*>(aFrame);
    177  } else {
    178    aFrame->MarkSubtreeDirty();
    179 
    180    nsIFrame* f = aFrame->GetParent();
    181    while (f && !f->IsSVGOuterSVGFrame()) {
    182      if (f->HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) {
    183        return;
    184      }
    185      f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
    186      f = f->GetParent();
    187      MOZ_ASSERT(f->IsSVGFrame(), "IsSVGOuterSVGFrame check above not valid!");
    188    }
    189 
    190    outerSVGFrame = static_cast<SVGOuterSVGFrame*>(f);
    191 
    192    MOZ_ASSERT(outerSVGFrame && outerSVGFrame->IsSVGOuterSVGFrame(),
    193               "Did not find SVGOuterSVGFrame!");
    194  }
    195 
    196  if (outerSVGFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) {
    197    // We're currently under an SVGOuterSVGFrame::Reflow call so there is no
    198    // need to call PresShell::FrameNeedsReflow, since we have an
    199    // SVGOuterSVGFrame::DidReflow call pending.
    200    return;
    201  }
    202 
    203  nsFrameState dirtyBit =
    204      (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY
    205                               : NS_FRAME_HAS_DIRTY_CHILDREN);
    206 
    207  aFrame->PresShell()->FrameNeedsReflow(outerSVGFrame, IntrinsicDirty::None,
    208                                        dirtyBit);
    209 }
    210 
    211 bool SVGUtils::NeedsReflowSVG(const nsIFrame* aFrame) {
    212  MOZ_ASSERT(aFrame->IsSVGFrame(), "SVG uses bits differently!");
    213 
    214  // The flags we test here may change, hence why we have this separate
    215  // function.
    216  return aFrame->IsSubtreeDirty();
    217 }
    218 
    219 Size SVGUtils::GetContextSize(const nsIFrame* aFrame) {
    220  Size size;
    221 
    222  MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
    223  const SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
    224 
    225  SVGViewportElement* ctx = element->GetCtx();
    226  if (ctx) {
    227    size.width = ctx->GetLength(SVGContentUtils::X);
    228    size.height = ctx->GetLength(SVGContentUtils::Y);
    229  }
    230  return size;
    231 }
    232 
    233 float SVGUtils::ObjectSpace(const gfxRect& aRect,
    234                            const dom::UserSpaceMetrics& aMetrics,
    235                            const SVGAnimatedLength* aLength) {
    236  float axis;
    237 
    238  switch (aLength->GetCtxType()) {
    239    case SVGContentUtils::X:
    240      axis = aRect.Width();
    241      break;
    242    case SVGContentUtils::Y:
    243      axis = aRect.Height();
    244      break;
    245    case SVGContentUtils::XY:
    246      axis = float(SVGContentUtils::ComputeNormalizedHypotenuse(
    247          aRect.Width(), aRect.Height()));
    248      break;
    249    default:
    250      MOZ_ASSERT_UNREACHABLE("unexpected ctx type");
    251      axis = 0.0f;
    252      break;
    253  }
    254  if (aLength->IsPercentage()) {
    255    // Multiply first to avoid precision errors:
    256    return axis * aLength->GetAnimValInSpecifiedUnits() / 100;
    257  }
    258  return aLength->GetAnimValueWithZoom(aMetrics) * axis;
    259 }
    260 
    261 float SVGUtils::UserSpace(nsIFrame* aNonSVGContext,
    262                          const SVGAnimatedLength* aLength) {
    263  MOZ_ASSERT(!aNonSVGContext->IsTextFrame(), "Not expecting text content");
    264  return aLength->GetAnimValueWithZoom(aNonSVGContext);
    265 }
    266 
    267 float SVGUtils::UserSpace(const UserSpaceMetrics& aMetrics,
    268                          const SVGAnimatedLength* aLength) {
    269  return aLength->GetAnimValueWithZoom(aMetrics);
    270 }
    271 
    272 SVGOuterSVGFrame* SVGUtils::GetOuterSVGFrame(nsIFrame* aFrame) {
    273  return static_cast<SVGOuterSVGFrame*>(nsLayoutUtils::GetClosestFrameOfType(
    274      aFrame, LayoutFrameType::SVGOuterSVG));
    275 }
    276 
    277 nsIFrame* SVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame,
    278                                                     nsRect* aRect) {
    279  ISVGDisplayableFrame* svg = do_QueryFrame(aFrame);
    280  if (!svg) {
    281    return nullptr;
    282  }
    283  SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
    284  if (outer == svg) {
    285    return nullptr;
    286  }
    287 
    288  if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
    289    *aRect = nsRect();
    290    return outer;
    291  }
    292 
    293  auto ctm = nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame},
    294                                                   RelativeTo{outer});
    295 
    296  Matrix mm;
    297  ctm.ProjectTo2D();
    298  ctm.CanDraw2D(&mm);
    299  gfxMatrix m = ThebesMatrix(mm);
    300 
    301  float appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
    302  float devPixelPerCSSPixel =
    303      float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel;
    304 
    305  // The matrix that GetBBox accepts should operate on "user space",
    306  // i.e. with CSS pixel unit.
    307  m.PreScale(devPixelPerCSSPixel, devPixelPerCSSPixel);
    308 
    309  auto initPosition = gfxPoint(
    310      NSAppUnitsToFloatPixels(aFrame->GetPosition().x, AppUnitsPerCSSPixel()),
    311      NSAppUnitsToFloatPixels(aFrame->GetPosition().y, AppUnitsPerCSSPixel()));
    312 
    313  // Both SVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor
    314  // will count this displacement, we should remove it here to avoid
    315  // double-counting.
    316  m.PreTranslate(-initPosition);
    317 
    318  uint32_t flags = SVGUtils::eForGetClientRects | SVGUtils::eBBoxIncludeFill |
    319                   SVGUtils::eBBoxIncludeStroke |
    320                   SVGUtils::eBBoxIncludeMarkers |
    321                   SVGUtils::eUseUserSpaceOfUseElement;
    322 
    323  gfxRect bbox = SVGUtils::GetBBox(aFrame, flags, &m);
    324  *aRect = nsLayoutUtils::RoundGfxRectToAppRect(bbox, appUnitsPerDevPixel);
    325 
    326  return outer;
    327 }
    328 
    329 gfxMatrix SVGUtils::GetCanvasTM(nsIFrame* aFrame) {
    330  // XXX yuck, we really need a common interface for GetCanvasTM
    331 
    332  if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
    333    return GetCSSPxToDevPxMatrix(aFrame);
    334  }
    335 
    336  if (aFrame->IsSVGForeignObjectFrame()) {
    337    return static_cast<SVGForeignObjectFrame*>(aFrame)->GetCanvasTM();
    338  }
    339 
    340  if (SVGContainerFrame* containerFrame = do_QueryFrame(aFrame)) {
    341    return containerFrame->GetCanvasTM();
    342  }
    343 
    344  MOZ_ASSERT(aFrame->GetParent()->IsSVGContainerFrame());
    345 
    346  auto* parent = static_cast<SVGContainerFrame*>(aFrame->GetParent());
    347  auto* content = static_cast<SVGElement*>(aFrame->GetContent());
    348 
    349  return content->ChildToUserSpaceTransform() * parent->GetCanvasTM();
    350 }
    351 
    352 bool SVGUtils::GetParentSVGTransforms(const nsIFrame* aFrame,
    353                                      gfx::Matrix* aFromParentTransform) {
    354  MOZ_ASSERT(aFrame->HasAllStateBits(NS_FRAME_SVG_LAYOUT |
    355                                     NS_FRAME_MAY_BE_TRANSFORMED),
    356             "Expecting an SVG frame that can be transformed");
    357  if (SVGContainerFrame* parent = do_QueryFrame(aFrame->GetParent())) {
    358    return parent->HasChildrenOnlyTransform(aFromParentTransform);
    359  }
    360  return false;
    361 }
    362 
    363 void SVGUtils::NotifyChildrenOfSVGChange(
    364    nsIFrame* aFrame, ISVGDisplayableFrame::ChangeFlags aFlags) {
    365  for (nsIFrame* kid : aFrame->PrincipalChildList()) {
    366    ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
    367    if (SVGFrame) {
    368      SVGFrame->NotifySVGChanged(aFlags);
    369    } else {
    370      NS_ASSERTION(kid->IsSVGFrame() || kid->IsInSVGTextSubtree(),
    371                   "SVG frame expected");
    372      // recurse into the children of container frames e.g. <clipPath>, <mask>
    373      // in case they have child frames with transformation matrices
    374      if (kid->IsSVGFrame()) {
    375        NotifyChildrenOfSVGChange(kid, aFlags);
    376      }
    377    }
    378  }
    379 }
    380 
    381 // ************************************************************
    382 
    383 float SVGUtils::ComputeOpacity(const nsIFrame* aFrame, bool aHandleOpacity) {
    384  if (!aHandleOpacity) {
    385    return 1.0f;
    386  }
    387 
    388  const auto* styleEffects = aFrame->StyleEffects();
    389 
    390  if (!styleEffects->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame)) {
    391    return 1.0f;
    392  }
    393 
    394  return styleEffects->mOpacity;
    395 }
    396 
    397 SVGUtils::MaskUsage SVGUtils::DetermineMaskUsage(const nsIFrame* aFrame,
    398                                                 bool aHandleOpacity) {
    399  MaskUsage usage;
    400 
    401  using ClipPathType = StyleClipPath::Tag;
    402 
    403  usage.mOpacity = ComputeOpacity(aFrame, aHandleOpacity);
    404 
    405  nsIFrame* firstFrame =
    406      nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
    407 
    408  const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
    409 
    410  if (SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) !=
    411      SVGObserverUtils::eHasNoRefs) {
    412    usage.mShouldGenerateMaskLayer = true;
    413  }
    414 
    415  SVGClipPathFrame* clipPathFrame;
    416  // XXX check return value?
    417  SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
    418  MOZ_ASSERT(!clipPathFrame || svgReset->mClipPath.IsUrl());
    419 
    420  switch (svgReset->mClipPath.tag) {
    421    case ClipPathType::Url:
    422      if (clipPathFrame) {
    423        if (clipPathFrame->IsTrivial()) {
    424          usage.mShouldApplyClipPath = true;
    425        } else {
    426          usage.mShouldGenerateClipMaskLayer = true;
    427        }
    428      }
    429      break;
    430    case ClipPathType::Shape: {
    431      usage.mShouldApplyBasicShapeOrPath = true;
    432      const auto& shape = svgReset->mClipPath.AsShape()._0;
    433      usage.mIsSimpleClipShape =
    434          !usage.mShouldGenerateMaskLayer &&
    435          (shape->IsRect() || shape->IsCircle() || shape->IsEllipse());
    436      break;
    437    }
    438    case ClipPathType::Box:
    439      usage.mShouldApplyBasicShapeOrPath = true;
    440      break;
    441    case ClipPathType::None:
    442      MOZ_ASSERT(!usage.mShouldGenerateClipMaskLayer &&
    443                 !usage.mShouldApplyClipPath &&
    444                 !usage.mShouldApplyBasicShapeOrPath);
    445      break;
    446    default:
    447      MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
    448      break;
    449  }
    450  return usage;
    451 }
    452 
    453 class MixModeBlender {
    454 public:
    455  using Factory = gfx::Factory;
    456 
    457  MixModeBlender(nsIFrame* aFrame, gfxContext* aContext)
    458      : mFrame(aFrame), mSourceCtx(aContext) {
    459    MOZ_ASSERT(mFrame && mSourceCtx);
    460  }
    461 
    462  bool ShouldCreateDrawTargetForBlend() const {
    463    return mFrame->StyleEffects()->HasMixBlendMode();
    464  }
    465 
    466  gfxContext* CreateBlendTarget(const gfxMatrix& aTransform) {
    467    MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
    468 
    469    // Create a temporary context to draw to so we can blend it back with
    470    // another operator.
    471    IntRect drawRect = ComputeClipExtsInDeviceSpace(aTransform);
    472    if (drawRect.IsEmpty()) {
    473      return nullptr;
    474    }
    475 
    476    RefPtr<DrawTarget> targetDT =
    477        mSourceCtx->GetDrawTarget()->CreateSimilarDrawTarget(
    478            drawRect.Size(), SurfaceFormat::B8G8R8A8);
    479    if (!targetDT || !targetDT->IsValid()) {
    480      return nullptr;
    481    }
    482 
    483    MOZ_ASSERT(!mTargetCtx,
    484               "CreateBlendTarget is designed to be used once only.");
    485 
    486    mTargetCtx = gfxContext::CreateOrNull(targetDT);
    487    MOZ_ASSERT(mTargetCtx);  // already checked the draw target above
    488    mTargetCtx->SetMatrix(mSourceCtx->CurrentMatrix() *
    489                          Matrix::Translation(-drawRect.TopLeft()));
    490 
    491    mTargetOffset = drawRect.TopLeft();
    492 
    493    return mTargetCtx.get();
    494  }
    495 
    496  void BlendToTarget() {
    497    MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
    498    MOZ_ASSERT(mTargetCtx,
    499               "BlendToTarget should be used after CreateBlendTarget.");
    500 
    501    RefPtr<SourceSurface> targetSurf = mTargetCtx->GetDrawTarget()->Snapshot();
    502 
    503    gfxContextAutoSaveRestore save(mSourceCtx);
    504    mSourceCtx->SetMatrix(Matrix());  // This will be restored right after.
    505    auto pattern = MakeRefPtr<gfxPattern>(
    506        targetSurf, Matrix::Translation(mTargetOffset.x, mTargetOffset.y));
    507    mSourceCtx->SetPattern(pattern);
    508    mSourceCtx->Paint();
    509  }
    510 
    511 private:
    512  MixModeBlender() = delete;
    513 
    514  IntRect ComputeClipExtsInDeviceSpace(const gfxMatrix& aTransform) {
    515    // These are used if we require a temporary surface for a custom blend
    516    // mode. Clip the source context first, so that we can generate a smaller
    517    // temporary surface. (Since we will clip this context in
    518    // SetupContextMatrix, a pair of save/restore is needed.)
    519    gfxContextAutoSaveRestore saver;
    520 
    521    if (!mFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
    522      saver.SetContext(mSourceCtx);
    523      // aFrame has a valid ink overflow rect, so clip to it before calling
    524      // PushGroup() to minimize the size of the surfaces we'll composite:
    525      gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(mSourceCtx);
    526      mSourceCtx->Multiply(aTransform);
    527      nsRect overflowRect = mFrame->InkOverflowRectRelativeToSelf();
    528      if (FrameDoesNotIncludePositionInTM(mFrame)) {
    529        overflowRect = overflowRect + mFrame->GetPosition();
    530      }
    531      mSourceCtx->Clip(NSRectToSnappedRect(
    532          overflowRect, mFrame->PresContext()->AppUnitsPerDevPixel(),
    533          *mSourceCtx->GetDrawTarget()));
    534    }
    535 
    536    // Get the clip extents in device space.
    537    gfxRect clippedFrameSurfaceRect =
    538        mSourceCtx->GetClipExtents(gfxContext::eDeviceSpace);
    539    clippedFrameSurfaceRect.RoundOut();
    540 
    541    IntRect result;
    542    ToRect(clippedFrameSurfaceRect).ToIntRect(&result);
    543 
    544    return mSourceCtx->GetDrawTarget()->CanCreateSimilarDrawTarget(
    545               result.Size(), SurfaceFormat::B8G8R8A8)
    546               ? result
    547               : IntRect();
    548  }
    549 
    550  nsIFrame* mFrame;
    551  gfxContext* mSourceCtx;
    552  UniquePtr<gfxContext> mTargetCtx;
    553  IntPoint mTargetOffset;
    554 };
    555 
    556 void SVGUtils::PaintFrameWithEffects(nsIFrame* aFrame, gfxContext& aContext,
    557                                     const gfxMatrix& aTransform,
    558                                     imgDrawingParams& aImgParams) {
    559  NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
    560                   aFrame->PresContext()->Document()->IsSVGGlyphsDocument(),
    561               "Only painting of non-display SVG should take this code path");
    562 
    563  ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame);
    564  if (!svgFrame) {
    565    return;
    566  }
    567 
    568  MaskUsage maskUsage = DetermineMaskUsage(aFrame, true);
    569  if (maskUsage.IsTransparent()) {
    570    return;
    571  }
    572 
    573  if (auto* svg = SVGElement::FromNode(aFrame->GetContent())) {
    574    if (!svg->HasValidDimensions()) {
    575      return;
    576    }
    577    if (aFrame->IsSVGSymbolFrame() && !svg->IsInSVGUseShadowTree()) {
    578      return;
    579    }
    580  }
    581 
    582  /* SVG defines the following rendering model:
    583   *
    584   *  1. Render fill
    585   *  2. Render stroke
    586   *  3. Render markers
    587   *  4. Apply filter
    588   *  5. Apply clipping, masking, group opacity
    589   *
    590   * We follow this, but perform a couple of optimizations:
    591   *
    592   * + Use cairo's clipPath when representable natively (single object
    593   *   clip region).
    594   *
    595   * + Merge opacity and masking if both used together.
    596   */
    597 
    598  /* Properties are added lazily and may have been removed by a restyle,
    599     so make sure all applicable ones are set again. */
    600  SVGClipPathFrame* clipPathFrame;
    601  nsTArray<SVGMaskFrame*> maskFrames;
    602  nsTArray<SVGFilterFrame*> filterFrames;
    603  const bool hasInvalidFilter =
    604      SVGObserverUtils::GetAndObserveFilters(aFrame, &filterFrames) ==
    605      SVGObserverUtils::eHasRefsSomeInvalid;
    606  SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame);
    607  SVGObserverUtils::GetAndObserveMasks(aFrame, &maskFrames);
    608 
    609  SVGMaskFrame* maskFrame = maskFrames.IsEmpty() ? nullptr : maskFrames[0];
    610 
    611  MixModeBlender blender(aFrame, &aContext);
    612  gfxContext* target = blender.ShouldCreateDrawTargetForBlend()
    613                           ? blender.CreateBlendTarget(aTransform)
    614                           : &aContext;
    615 
    616  if (!target) {
    617    return;
    618  }
    619 
    620  /* Check if we need to do additional operations on this child's
    621   * rendering, which necessitates rendering into another surface. */
    622  bool shouldPushMask = false;
    623 
    624  if (maskUsage.ShouldGenerateMask()) {
    625    RefPtr<SourceSurface> maskSurface;
    626 
    627    // maskFrame can be nullptr even if maskUsage.ShouldGenerateMaskLayer() is
    628    // true. That happens when a user gives an unresolvable mask-id, such as
    629    //   mask:url()
    630    //   mask:url(#id-which-does-not-exist)
    631    // Since we only uses SVGUtils with SVG elements, not like mask on an
    632    // HTML element, we should treat an unresolvable mask as no-mask here.
    633    if (maskUsage.ShouldGenerateMaskLayer() && maskFrame) {
    634      StyleMaskMode maskMode =
    635          aFrame->StyleSVGReset()->mMask.mLayers[0].mMaskMode;
    636      SVGMaskFrame::MaskParams params(aContext.GetDrawTarget(), aFrame,
    637                                      aTransform, maskUsage.Opacity(), maskMode,
    638                                      aImgParams);
    639 
    640      maskSurface = maskFrame->GetMaskForMaskedFrame(params);
    641 
    642      if (!maskSurface) {
    643        // Either entire surface is clipped out, or gfx buffer allocation
    644        // failure in SVGMaskFrame::GetMaskForMaskedFrame.
    645        return;
    646      }
    647      shouldPushMask = true;
    648    }
    649 
    650    if (maskUsage.ShouldGenerateClipMaskLayer()) {
    651      RefPtr<SourceSurface> clipMaskSurface =
    652          clipPathFrame->GetClipMask(aContext, aFrame, aTransform, maskSurface);
    653      if (clipMaskSurface) {
    654        maskSurface = clipMaskSurface;
    655      } else {
    656        // Either entire surface is clipped out, or gfx buffer allocation
    657        // failure in SVGClipPathFrame::GetClipMask.
    658        return;
    659      }
    660      shouldPushMask = true;
    661    }
    662 
    663    if (!maskUsage.ShouldGenerateLayer()) {
    664      shouldPushMask = true;
    665    }
    666 
    667    // SVG mask multiply opacity into maskSurface already, so we do not bother
    668    // to apply opacity again.
    669    if (shouldPushMask) {
    670      // We want the mask to be untransformed so use the inverse of the
    671      // current transform as the maskTransform to compensate.
    672      Matrix maskTransform = aContext.CurrentMatrix();
    673      maskTransform.Invert();
    674      target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
    675                                    maskFrame ? 1.0f : maskUsage.Opacity(),
    676                                    maskSurface, maskTransform);
    677    }
    678  }
    679 
    680  /* If this frame has only a trivial clipPath, set up cairo's clipping now so
    681   * we can just do normal painting and get it clipped appropriately.
    682   */
    683  if (maskUsage.ShouldApplyClipPath() ||
    684      maskUsage.ShouldApplyBasicShapeOrPath()) {
    685    if (maskUsage.ShouldApplyClipPath()) {
    686      clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform);
    687    } else {
    688      CSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame,
    689                                                     aTransform);
    690    }
    691  }
    692 
    693  /* Paint the child */
    694 
    695  // Invalid filters should render the unfiltered contents per spec.
    696  if (aFrame->StyleEffects()->HasFilters() && !hasInvalidFilter) {
    697    gfxContextMatrixAutoSaveRestore autoSR(target);
    698 
    699    // 'target' is currently scaled such that its user space units are CSS
    700    // pixels (SVG user space units). But PaintFilteredFrame expects it to be
    701    // scaled in such a way that its user space units are device pixels. So we
    702    // have to adjust the scale.
    703    gfxMatrix reverseScaleMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aFrame);
    704    DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
    705    target->SetMatrixDouble(reverseScaleMatrix * aTransform *
    706                            target->CurrentMatrixDouble());
    707 
    708    auto callback = [&](gfxContext& aContext, imgDrawingParams& aImgParams,
    709                        const gfxMatrix* aFilterTransform,
    710                        const nsIntRect* aDirtyRect) {
    711      svgFrame->PaintSVG(aContext,
    712                         aFilterTransform
    713                             ? SVGUtils::GetCSSPxToDevPxMatrix(aFrame)
    714                             : aTransform,
    715                         aImgParams);
    716    };
    717    // If we're masking a userSpaceOnUse mask we may need to include the
    718    // stroke too. Err on the side of caution and include it always.
    719    gfxRect bbox = GetBBox(aFrame, SVGUtils::eUseFrameBoundsForOuterSVG |
    720                                       SVGUtils::eBBoxIncludeFillGeometry |
    721                                       SVGUtils::eBBoxIncludeStroke);
    722    FilterInstance::PaintFilteredFrame(
    723        aFrame, aFrame->StyleEffects()->mFilters.AsSpan(), filterFrames, target,
    724        callback, nullptr, aImgParams, 1.0f, &bbox);
    725  } else {
    726    svgFrame->PaintSVG(*target, aTransform, aImgParams);
    727  }
    728 
    729  if (maskUsage.ShouldApplyClipPath() ||
    730      maskUsage.ShouldApplyBasicShapeOrPath()) {
    731    aContext.PopClip();
    732  }
    733 
    734  if (shouldPushMask) {
    735    target->PopGroupAndBlend();
    736  }
    737 
    738  if (blender.ShouldCreateDrawTargetForBlend()) {
    739    MOZ_ASSERT(target != &aContext);
    740    blender.BlendToTarget();
    741  }
    742 }
    743 
    744 bool SVGUtils::HitTestClip(nsIFrame* aFrame, const gfxPoint& aPoint) {
    745  const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
    746  if (!svgReset->HasClipPath()) {
    747    return true;
    748  }
    749  if (svgReset->mClipPath.IsUrl()) {
    750    // If the clip-path property references non-existent or invalid clipPath
    751    // element(s) we ignore it.
    752    SVGClipPathFrame* clipPathFrame;
    753    SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame);
    754    return !clipPathFrame ||
    755           clipPathFrame->PointIsInsideClipPath(aFrame, aPoint);
    756  }
    757  return CSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame, aPoint);
    758 }
    759 
    760 IntSize SVGUtils::ConvertToSurfaceSize(const gfxSize& aSize,
    761                                       bool* aResultOverflows) {
    762  IntSize surfaceSize(ClampToInt(ceil(aSize.width)),
    763                      ClampToInt(ceil(aSize.height)));
    764 
    765  *aResultOverflows = surfaceSize.width != ceil(aSize.width) ||
    766                      surfaceSize.height != ceil(aSize.height);
    767 
    768  if (!Factory::AllowedSurfaceSize(surfaceSize)) {
    769    surfaceSize.width = std::min(kReasonableSurfaceSize, surfaceSize.width);
    770    surfaceSize.height = std::min(kReasonableSurfaceSize, surfaceSize.height);
    771    *aResultOverflows = true;
    772  }
    773 
    774  return surfaceSize;
    775 }
    776 
    777 bool SVGUtils::HitTestRect(const gfx::Matrix& aMatrix, float aRX, float aRY,
    778                           float aRWidth, float aRHeight, float aX, float aY) {
    779  gfx::Rect rect(aRX, aRY, aRWidth, aRHeight);
    780  if (rect.IsEmpty() || aMatrix.IsSingular()) {
    781    return false;
    782  }
    783  gfx::Matrix toRectSpace = aMatrix;
    784  toRectSpace.Invert();
    785  gfx::Point p = toRectSpace.TransformPoint(gfx::Point(aX, aY));
    786  return rect.x <= p.x && p.x <= rect.XMost() && rect.y <= p.y &&
    787         p.y <= rect.YMost();
    788 }
    789 
    790 gfxRect SVGUtils::GetClipRectForFrame(const nsIFrame* aFrame, float aX,
    791                                      float aY, float aWidth, float aHeight) {
    792  const nsStyleDisplay* disp = aFrame->StyleDisplay();
    793  const nsStyleEffects* effects = aFrame->StyleEffects();
    794 
    795  bool clipApplies = disp->mOverflowX == StyleOverflow::Hidden ||
    796                     disp->mOverflowY == StyleOverflow::Hidden;
    797 
    798  if (!clipApplies || effects->mClip.IsAuto()) {
    799    return gfxRect(aX, aY, aWidth, aHeight);
    800  }
    801 
    802  const auto& rect = effects->mClip.AsRect();
    803  nsRect coordClipRect = rect.ToLayoutRect();
    804  nsIntRect clipPxRect = coordClipRect.ToOutsidePixels(AppUnitsPerCSSPixel());
    805  gfxRect clipRect =
    806      gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height);
    807  if (rect.right.IsAuto()) {
    808    clipRect.width = std::max(aWidth - clipRect.X(), 0.0);
    809  }
    810  if (rect.bottom.IsAuto()) {
    811    clipRect.height = std::max(aHeight - clipRect.Y(), 0.0);
    812  }
    813  if (disp->mOverflowX != StyleOverflow::Hidden) {
    814    clipRect.x = aX;
    815    clipRect.width = aWidth;
    816  }
    817  if (disp->mOverflowY != StyleOverflow::Hidden) {
    818    clipRect.y = aY;
    819    clipRect.height = aHeight;
    820  }
    821  return clipRect;
    822 }
    823 
    824 gfxRect SVGUtils::GetBBox(nsIFrame* aFrame, uint32_t aFlags,
    825                          const gfxMatrix* aToBoundsSpace) {
    826  if (aFrame->IsTextFrame()) {
    827    aFrame = aFrame->GetParent();
    828  }
    829 
    830  if (aFrame->IsInSVGTextSubtree()) {
    831    // It is possible to apply a gradient, pattern, clipping path, mask or
    832    // filter to text. When one of these facilities is applied to text
    833    // the bounding box is the entire text element in all cases.
    834    aFrame =
    835        nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
    836  }
    837 
    838  ISVGDisplayableFrame* svg = do_QueryFrame(aFrame);
    839  const bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
    840  if (hasSVGLayout && !svg) {
    841    // An SVG frame, but not one that can be displayed directly (for
    842    // example, nsGradientFrame). These can't contribute to the bbox.
    843    return gfxRect();
    844  }
    845 
    846  const bool isOuterSVG = svg && !hasSVGLayout;
    847  MOZ_ASSERT(!isOuterSVG || aFrame->IsSVGOuterSVGFrame());
    848  if (!svg || (isOuterSVG && (aFlags & eUseFrameBoundsForOuterSVG))) {
    849    // An HTML element or an SVG outer frame.
    850    MOZ_ASSERT(!hasSVGLayout);
    851    bool onlyCurrentFrame = aFlags & eIncludeOnlyCurrentFrameForNonSVGElement;
    852    return SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
    853        aFrame,
    854        /* aUnionContinuations = */ !onlyCurrentFrame);
    855  }
    856 
    857  MOZ_ASSERT(svg);
    858 
    859  if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) {
    860    if (!element->HasValidDimensions()) {
    861      return gfxRect();
    862    }
    863  }
    864 
    865  // Clean out flags which have no effects on returning bbox from now, so that
    866  // we can cache and reuse ObjectBoundingBoxProperty() in the code below.
    867  aFlags &=
    868      ~(eIncludeOnlyCurrentFrameForNonSVGElement | eUseFrameBoundsForOuterSVG);
    869  if (!aFrame->IsSVGUseFrame()) {
    870    aFlags &= ~eUseUserSpaceOfUseElement;
    871  }
    872 
    873  if (aFlags == eBBoxIncludeFillGeometry &&
    874      // We only cache bbox in element's own user space
    875      !aToBoundsSpace) {
    876    gfxRect* prop = aFrame->GetProperty(ObjectBoundingBoxProperty());
    877    if (prop) {
    878      return *prop;
    879    }
    880  }
    881 
    882  gfxMatrix matrix;
    883  if (aToBoundsSpace) {
    884    matrix = *aToBoundsSpace;
    885  }
    886 
    887  if (aFrame->IsSVGForeignObjectFrame() ||
    888      aFlags & SVGUtils::eUseUserSpaceOfUseElement) {
    889    // The spec says getBBox "Returns the tight bounding box in *current user
    890    // space*". So we should really be doing this for all elements, but that
    891    // needs investigation to check that we won't break too much content.
    892    // NOTE: When changing this to apply to other frame types, make sure to
    893    // also update SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset.
    894    MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
    895    auto* element = static_cast<SVGElement*>(aFrame->GetContent());
    896    matrix = element->ChildToUserSpaceTransform() * matrix;
    897  }
    898  gfxRect bbox =
    899      svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect();
    900  // Account for 'clipped'.
    901  if (aFlags & SVGUtils::eBBoxIncludeClipped) {
    902    gfxRect clipRect;
    903    gfxRect fillBBox =
    904        svg->GetBBoxContribution({}, SVGUtils::eBBoxIncludeFill).ToThebesRect();
    905    // XXX Should probably check for overflow: clip too.
    906    bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow();
    907    if (hasClip) {
    908      clipRect = SVGUtils::GetClipRectForFrame(aFrame, 0.0f, 0.0f,
    909                                               fillBBox.width, fillBBox.height);
    910      clipRect.MoveBy(fillBBox.TopLeft());
    911      if (aFrame->IsSVGForeignObjectFrame() || aFrame->IsSVGUseFrame()) {
    912        clipRect = matrix.TransformBounds(clipRect);
    913      }
    914    }
    915    SVGClipPathFrame* clipPathFrame;
    916    if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) ==
    917        SVGObserverUtils::eHasRefsSomeInvalid) {
    918      bbox = gfxRect();
    919    } else {
    920      if (clipPathFrame) {
    921        SVGClipPathElement* clipContent =
    922            static_cast<SVGClipPathElement*>(clipPathFrame->GetContent());
    923        if (clipContent->IsUnitsObjectBoundingBox()) {
    924          matrix.PreTranslate(fillBBox.TopLeft());
    925          matrix.PreScale(fillBBox.width, fillBBox.height);
    926        } else if (aFrame->IsSVGForeignObjectFrame()) {
    927          matrix = gfxMatrix();
    928        }
    929        matrix *= SVGUtils::GetTransformMatrixInUserSpace(clipPathFrame);
    930 
    931        bbox = clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags)
    932                   .ToThebesRect();
    933      }
    934 
    935      if (hasClip && !(aFlags & eDoNotClipToBBoxOfContentInsideClipPath)) {
    936        bbox = bbox.Intersect(clipRect);
    937      }
    938 
    939      if (bbox.IsEmpty()) {
    940        bbox = gfxRect();
    941      }
    942    }
    943  }
    944 
    945  if (aFlags == eBBoxIncludeFillGeometry &&
    946      // We only cache bbox in element's own user space
    947      !aToBoundsSpace) {
    948    // Obtaining the bbox for objectBoundingBox calculations is common so we
    949    // cache the result for future calls, since calculation can be expensive:
    950    aFrame->SetProperty(ObjectBoundingBoxProperty(), new gfxRect(bbox));
    951  }
    952 
    953  return bbox;
    954 }
    955 
    956 gfxPoint SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(const nsIFrame* aFrame) {
    957  if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
    958    // The user space for non-SVG frames is defined as the bounding box of the
    959    // frame's border-box rects over all continuations.
    960    return gfxPoint();
    961  }
    962 
    963  // Leaf frames apply their own offset inside their user space.
    964  if (FrameDoesNotIncludePositionInTM(aFrame)) {
    965    return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(),
    966                                        AppUnitsPerCSSPixel())
    967        .TopLeft();
    968  }
    969 
    970  // For foreignObject frames, SVGUtils::GetBBox applies their local
    971  // transform, so we need to do the same here.
    972  if (aFrame->IsSVGForeignObjectFrame()) {
    973    gfxMatrix transform = static_cast<SVGElement*>(aFrame->GetContent())
    974                              ->ChildToUserSpaceTransform();
    975    NS_ASSERTION(!transform.HasNonTranslation(),
    976                 "we're relying on this being an offset-only transform");
    977    return transform.GetTranslation();
    978  }
    979 
    980  return gfxPoint();
    981 }
    982 
    983 static gfxRect GetBoundingBoxRelativeRect(const SVGAnimatedLength* aXYWH,
    984                                          const SVGElement* aElement,
    985                                          const gfxRect& aBBox) {
    986  SVGElementMetrics metrics(aElement);
    987  return gfxRect(aBBox.x + SVGUtils::ObjectSpace(aBBox, metrics, &aXYWH[0]),
    988                 aBBox.y + SVGUtils::ObjectSpace(aBBox, metrics, &aXYWH[1]),
    989                 SVGUtils::ObjectSpace(aBBox, metrics, &aXYWH[2]),
    990                 SVGUtils::ObjectSpace(aBBox, metrics, &aXYWH[3]));
    991 }
    992 
    993 gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits,
    994                                  const SVGAnimatedLength* aXYWH,
    995                                  const gfxRect& aBBox,
    996                                  const SVGElement* aElement,
    997                                  const UserSpaceMetrics& aMetrics) {
    998  if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    999    return GetBoundingBoxRelativeRect(aXYWH, aElement, aBBox);
   1000  }
   1001  return gfxRect(UserSpace(aMetrics, &aXYWH[0]), UserSpace(aMetrics, &aXYWH[1]),
   1002                 UserSpace(aMetrics, &aXYWH[2]),
   1003                 UserSpace(aMetrics, &aXYWH[3]));
   1004 }
   1005 
   1006 gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits,
   1007                                  const SVGAnimatedLength* aXYWH,
   1008                                  const gfxRect& aBBox, nsIFrame* aFrame) {
   1009  auto* svgElement = SVGElement::FromNode(aFrame->GetContent());
   1010  if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
   1011    return GetBoundingBoxRelativeRect(aXYWH, svgElement, aBBox);
   1012  }
   1013  if (svgElement) {
   1014    return GetRelativeRect(aUnits, aXYWH, aBBox, svgElement,
   1015                           SVGElementMetrics(svgElement));
   1016  }
   1017  return GetRelativeRect(aUnits, aXYWH, aBBox, svgElement,
   1018                         NonSVGFrameUserSpaceMetrics(aFrame));
   1019 }
   1020 
   1021 bool SVGUtils::CanOptimizeOpacity(const nsIFrame* aFrame) {
   1022  if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
   1023    return false;
   1024  }
   1025  auto* content = aFrame->GetContent();
   1026  if (!content->IsSVGGeometryElement() &&
   1027      !content->IsSVGElement(nsGkAtoms::image)) {
   1028    return false;
   1029  }
   1030  if (aFrame->StyleEffects()->HasFilters()) {
   1031    return false;
   1032  }
   1033  // XXX The SVG WG is intending to allow fill, stroke and markers on <image>
   1034  if (content->IsSVGElement(nsGkAtoms::image)) {
   1035    return true;
   1036  }
   1037  const nsStyleSVG* style = aFrame->StyleSVG();
   1038  if (style->HasMarker() &&
   1039      static_cast<SVGGeometryElement*>(content)->IsMarkable()) {
   1040    return false;
   1041  }
   1042 
   1043  if (nsLayoutUtils::HasAnimationOfPropertySet(
   1044          aFrame, nsCSSPropertyIDSet::OpacityProperties())) {
   1045    return false;
   1046  }
   1047 
   1048  return !style->HasFill() || !HasStroke(aFrame);
   1049 }
   1050 
   1051 gfxMatrix SVGUtils::AdjustMatrixForUnits(const gfxMatrix& aMatrix,
   1052                                         const SVGAnimatedEnumeration* aUnits,
   1053                                         nsIFrame* aFrame, uint32_t aFlags) {
   1054  if (aFrame && aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
   1055    gfxRect bbox = GetBBox(aFrame, aFlags);
   1056    gfxMatrix tm = aMatrix;
   1057    tm.PreTranslate(gfxPoint(bbox.X(), bbox.Y()));
   1058    tm.PreScale(bbox.Width(), bbox.Height());
   1059    return tm;
   1060  }
   1061  return aMatrix;
   1062 }
   1063 
   1064 bool SVGUtils::GetNonScalingStrokeTransform(const nsIFrame* aFrame,
   1065                                            gfxMatrix* aUserToOuterSVG) {
   1066  if (aFrame->GetContent()->IsText()) {
   1067    aFrame = aFrame->GetParent();
   1068  }
   1069 
   1070  if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) {
   1071    return false;
   1072  }
   1073 
   1074  MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "should be an SVG element");
   1075 
   1076  SVGElement* content = static_cast<SVGElement*>(aFrame->GetContent());
   1077  *aUserToOuterSVG =
   1078      ThebesMatrix(SVGContentUtils::GetNonScalingStrokeCTM(content));
   1079 
   1080  return aUserToOuterSVG->HasNonTranslation() && !aUserToOuterSVG->IsSingular();
   1081 }
   1082 
   1083 void SVGUtils::UpdateNonScalingStrokeStateBit(nsIFrame* aFrame) {
   1084  MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
   1085             "Called on invalid frame type");
   1086  MOZ_ASSERT(aFrame->StyleSVGReset()->HasNonScalingStroke(),
   1087             "Expecting initial frame to have non-scaling-stroke style");
   1088 
   1089  do {
   1090    MOZ_ASSERT(aFrame->IsSVGFrame(), "Unexpected frame type");
   1091    aFrame->AddStateBits(NS_STATE_SVG_MAY_CONTAIN_NON_SCALING_STROKE);
   1092    if (aFrame->IsSVGOuterSVGFrame()) {
   1093      return;
   1094    }
   1095  } while ((aFrame = aFrame->GetParent()));
   1096 }
   1097 
   1098 // The logic here comes from _cairo_stroke_style_max_distance_from_path
   1099 static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
   1100                                             const nsIFrame* aFrame,
   1101                                             double aStyleExpansionFactor,
   1102                                             const gfxMatrix& aMatrix) {
   1103  double style_expansion =
   1104      aStyleExpansionFactor * SVGUtils::GetStrokeWidth(aFrame);
   1105 
   1106  gfxMatrix matrix = aMatrix;
   1107 
   1108  gfxMatrix outerSVGToUser;
   1109  if (SVGUtils::GetNonScalingStrokeTransform(aFrame, &outerSVGToUser)) {
   1110    outerSVGToUser.Invert();
   1111    matrix.PreMultiply(outerSVGToUser);
   1112  }
   1113 
   1114  double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21));
   1115  double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12));
   1116 
   1117  gfxRect strokeExtents = aPathExtents;
   1118  strokeExtents.Inflate(dx, dy);
   1119  return strokeExtents;
   1120 }
   1121 
   1122 /*static*/
   1123 gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
   1124                                                const nsTextFrame* aFrame,
   1125                                                const gfxMatrix& aMatrix) {
   1126  NS_ASSERTION(aFrame->IsInSVGTextSubtree(),
   1127               "expected an nsTextFrame for SVG text");
   1128  return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5,
   1129                                                aMatrix);
   1130 }
   1131 
   1132 /*static*/
   1133 gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
   1134                                                const SVGGeometryFrame* aFrame,
   1135                                                const gfxMatrix& aMatrix) {
   1136  bool strokeMayHaveCorners =
   1137      !SVGContentUtils::ShapeTypeHasNoCorners(aFrame->GetContent());
   1138 
   1139  // For a shape without corners the stroke can only extend half the stroke
   1140  // width from the path in the x/y-axis directions. For shapes with corners
   1141  // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line
   1142  // with stroke-linecaps="square").
   1143  double styleExpansionFactor = strokeMayHaveCorners ? M_SQRT1_2 : 0.5;
   1144 
   1145  // The stroke can extend even further for paths that can be affected by
   1146  // stroke-miterlimit.
   1147  // We only need to do this if the limit is greater than 1, but it's probably
   1148  // not worth optimizing for that.
   1149  bool affectedByMiterlimit = aFrame->GetContent()->IsAnyOfSVGElements(
   1150      nsGkAtoms::path, nsGkAtoms::polyline, nsGkAtoms::polygon);
   1151 
   1152  if (affectedByMiterlimit) {
   1153    const nsStyleSVG* style = aFrame->StyleSVG();
   1154    if (style->mStrokeLinejoin == StyleStrokeLinejoin::Miter &&
   1155        styleExpansionFactor < style->mStrokeMiterlimit / 2.0) {
   1156      styleExpansionFactor = style->mStrokeMiterlimit / 2.0;
   1157    }
   1158  }
   1159 
   1160  return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame,
   1161                                                styleExpansionFactor, aMatrix);
   1162 }
   1163 
   1164 // ----------------------------------------------------------------------
   1165 
   1166 /* static */
   1167 nscolor SVGUtils::GetFallbackOrPaintColor(
   1168    const ComputedStyle& aStyle, StyleSVGPaint nsStyleSVG::* aFillOrStroke,
   1169    nscolor aDefaultContextFallbackColor) {
   1170  const auto& paint = aStyle.StyleSVG()->*aFillOrStroke;
   1171  nscolor color;
   1172  switch (paint.kind.tag) {
   1173    case StyleSVGPaintKind::Tag::PaintServer:
   1174      color = paint.fallback.IsColor()
   1175                  ? paint.fallback.AsColor().CalcColor(aStyle)
   1176                  : NS_RGBA(0, 0, 0, 0);
   1177      break;
   1178    case StyleSVGPaintKind::Tag::ContextStroke:
   1179    case StyleSVGPaintKind::Tag::ContextFill:
   1180      color = paint.fallback.IsColor()
   1181                  ? paint.fallback.AsColor().CalcColor(aStyle)
   1182                  : aDefaultContextFallbackColor;
   1183      break;
   1184    default:
   1185      color = paint.kind.AsColor().CalcColor(aStyle);
   1186      break;
   1187  }
   1188  if (const auto* styleIfVisited = aStyle.GetStyleIfVisited()) {
   1189    const auto& paintIfVisited = styleIfVisited->StyleSVG()->*aFillOrStroke;
   1190    // To prevent Web content from detecting if a user has visited a URL
   1191    // (via URL loading triggered by paint servers or performance
   1192    // differences between paint servers or between a paint server and a
   1193    // color), we do not allow whether links are visited to change which
   1194    // paint server is used or switch between paint servers and simple
   1195    // colors.  A :visited style may only override a simple color with
   1196    // another simple color.
   1197    if (paintIfVisited.kind.IsColor() && paint.kind.IsColor()) {
   1198      nscolor colors[2] = {
   1199          color, paintIfVisited.kind.AsColor().CalcColor(*styleIfVisited)};
   1200      return ComputedStyle::CombineVisitedColors(colors,
   1201                                                 aStyle.RelevantLinkVisited());
   1202    }
   1203  }
   1204  return color;
   1205 }
   1206 
   1207 /* static */
   1208 void SVGUtils::MakeFillPatternFor(nsIFrame* aFrame, gfxContext* aContext,
   1209                                  GeneralPattern* aOutPattern,
   1210                                  imgDrawingParams& aImgParams,
   1211                                  SVGContextPaint* aContextPaint) {
   1212  const nsStyleSVG* style = aFrame->StyleSVG();
   1213  if (style->mFill.kind.IsNone()) {
   1214    return;
   1215  }
   1216 
   1217  const auto* styleEffects = aFrame->StyleEffects();
   1218 
   1219  float fillOpacity = GetOpacity(style->mFillOpacity, aContextPaint);
   1220  if (!styleEffects->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame)) {
   1221    // Combine the group opacity into the fill opacity (we will have skipped
   1222    // creating an offscreen surface to apply the group opacity).
   1223    fillOpacity *= styleEffects->mOpacity;
   1224  }
   1225 
   1226  const DrawTarget* dt = aContext->GetDrawTarget();
   1227 
   1228  SVGPaintServerFrame* ps =
   1229      SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mFill);
   1230 
   1231  if (ps) {
   1232    RefPtr<gfxPattern> pattern =
   1233        ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(),
   1234                                  &nsStyleSVG::mFill, fillOpacity, aImgParams);
   1235    if (pattern) {
   1236      pattern->CacheColorStops(dt);
   1237      aOutPattern->Init(*pattern->GetPattern(dt));
   1238      return;
   1239    }
   1240  }
   1241 
   1242  if (aContextPaint) {
   1243    RefPtr<gfxPattern> pattern;
   1244    switch (style->mFill.kind.tag) {
   1245      case StyleSVGPaintKind::Tag::ContextFill:
   1246        pattern = aContextPaint->GetFillPattern(
   1247            dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams);
   1248        break;
   1249      case StyleSVGPaintKind::Tag::ContextStroke:
   1250        pattern = aContextPaint->GetStrokePattern(
   1251            dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams);
   1252        break;
   1253      default:;
   1254    }
   1255    if (pattern) {
   1256      aOutPattern->Init(*pattern->GetPattern(dt));
   1257      return;
   1258    }
   1259  }
   1260 
   1261  if (style->mFill.fallback.IsNone()) {
   1262    return;
   1263  }
   1264 
   1265  // On failure, use the fallback colour in case we have an
   1266  // objectBoundingBox where the width or height of the object is zero.
   1267  // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
   1268  sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor(
   1269      *aFrame->Style(), &nsStyleSVG::mFill, NS_RGB(0, 0, 0))));
   1270  color.a *= fillOpacity;
   1271  aOutPattern->InitColorPattern(ToDeviceColor(color));
   1272 }
   1273 
   1274 /* static */
   1275 void SVGUtils::MakeStrokePatternFor(nsIFrame* aFrame, gfxContext* aContext,
   1276                                    GeneralPattern* aOutPattern,
   1277                                    imgDrawingParams& aImgParams,
   1278                                    SVGContextPaint* aContextPaint) {
   1279  const nsStyleSVG* style = aFrame->StyleSVG();
   1280  if (style->mStroke.kind.IsNone()) {
   1281    return;
   1282  }
   1283 
   1284  const auto* styleEffects = aFrame->StyleEffects();
   1285 
   1286  float strokeOpacity = GetOpacity(style->mStrokeOpacity, aContextPaint);
   1287  if (!styleEffects->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame)) {
   1288    // Combine the group opacity into the stroke opacity (we will have skipped
   1289    // creating an offscreen surface to apply the group opacity).
   1290    strokeOpacity *= styleEffects->mOpacity;
   1291  }
   1292 
   1293  const DrawTarget* dt = aContext->GetDrawTarget();
   1294 
   1295  SVGPaintServerFrame* ps =
   1296      SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mStroke);
   1297 
   1298  if (ps) {
   1299    RefPtr<gfxPattern> pattern = ps->GetPaintServerPattern(
   1300        aFrame, dt, aContext->CurrentMatrixDouble(), &nsStyleSVG::mStroke,
   1301        strokeOpacity, aImgParams);
   1302    if (pattern) {
   1303      pattern->CacheColorStops(dt);
   1304      aOutPattern->Init(*pattern->GetPattern(dt));
   1305      return;
   1306    }
   1307  }
   1308 
   1309  if (aContextPaint) {
   1310    RefPtr<gfxPattern> pattern;
   1311    switch (style->mStroke.kind.tag) {
   1312      case StyleSVGPaintKind::Tag::ContextFill:
   1313        pattern = aContextPaint->GetFillPattern(
   1314            dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams);
   1315        break;
   1316      case StyleSVGPaintKind::Tag::ContextStroke:
   1317        pattern = aContextPaint->GetStrokePattern(
   1318            dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams);
   1319        break;
   1320      default:;
   1321    }
   1322    if (pattern) {
   1323      aOutPattern->Init(*pattern->GetPattern(dt));
   1324      return;
   1325    }
   1326  }
   1327 
   1328  if (style->mStroke.fallback.IsNone()) {
   1329    return;
   1330  }
   1331 
   1332  // On failure, use the fallback colour in case we have an
   1333  // objectBoundingBox where the width or height of the object is zero.
   1334  // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
   1335  sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor(
   1336      *aFrame->Style(), &nsStyleSVG::mStroke, NS_RGBA(0, 0, 0, 0))));
   1337  color.a *= strokeOpacity;
   1338  aOutPattern->InitColorPattern(ToDeviceColor(color));
   1339 }
   1340 
   1341 /* static */
   1342 float SVGUtils::GetOpacity(const StyleSVGOpacity& aOpacity,
   1343                           const SVGContextPaint* aContextPaint) {
   1344  float opacity = 1.0f;
   1345  switch (aOpacity.tag) {
   1346    case StyleSVGOpacity::Tag::Opacity:
   1347      return aOpacity.AsOpacity();
   1348    case StyleSVGOpacity::Tag::ContextFillOpacity:
   1349      if (aContextPaint) {
   1350        opacity = aContextPaint->GetFillOpacity();
   1351      }
   1352      break;
   1353    case StyleSVGOpacity::Tag::ContextStrokeOpacity:
   1354      if (aContextPaint) {
   1355        opacity = aContextPaint->GetStrokeOpacity();
   1356      }
   1357      break;
   1358  }
   1359  return opacity;
   1360 }
   1361 
   1362 bool SVGUtils::HasStroke(const nsIFrame* aFrame,
   1363                         const SVGContextPaint* aContextPaint) {
   1364  const nsStyleSVG* style = aFrame->StyleSVG();
   1365  return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0;
   1366 }
   1367 
   1368 float SVGUtils::GetStrokeWidth(const nsIFrame* aFrame,
   1369                               const SVGContextPaint* aContextPaint) {
   1370  nsIContent* content = aFrame->GetContent();
   1371  if (content->IsText()) {
   1372    content = content->GetParent();
   1373  }
   1374 
   1375  auto* ctx = SVGElement::FromNode(content);
   1376  return SVGContentUtils::GetStrokeWidth(ctx, aFrame->Style(), aContextPaint);
   1377 }
   1378 
   1379 void SVGUtils::SetupStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext,
   1380                                   SVGContextPaint* aContextPaint) {
   1381  MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
   1382  SVGContentUtils::AutoStrokeOptions strokeOptions;
   1383  SVGContentUtils::GetStrokeOptions(&strokeOptions,
   1384                                    SVGElement::FromNode(aFrame->GetContent()),
   1385                                    aFrame->Style(), aContextPaint);
   1386 
   1387  if (strokeOptions.mLineWidth <= 0) {
   1388    return;
   1389  }
   1390 
   1391  // SVGContentUtils::GetStrokeOptions gets the stroke options in CSS px;
   1392  // convert to device pixels for gfxContext.
   1393  float devPxPerCSSPx = aFrame->PresContext()->CSSToDevPixelScale().scale;
   1394 
   1395  aContext->SetLineWidth(strokeOptions.mLineWidth * devPxPerCSSPx);
   1396  aContext->SetLineCap(strokeOptions.mLineCap);
   1397  aContext->SetMiterLimit(strokeOptions.mMiterLimit);
   1398  aContext->SetLineJoin(strokeOptions.mLineJoin);
   1399  aContext->SetDash(strokeOptions.mDashPattern, strokeOptions.mDashLength,
   1400                    strokeOptions.mDashOffset, devPxPerCSSPx);
   1401 }
   1402 
   1403 uint16_t SVGUtils::GetGeometryHitTestFlags(const nsIFrame* aFrame) {
   1404  uint16_t flags = 0;
   1405 
   1406  switch (aFrame->Style()->PointerEvents()) {
   1407    case StylePointerEvents::None:
   1408      break;
   1409    case StylePointerEvents::Auto:
   1410    case StylePointerEvents::Visiblepainted:
   1411      if (aFrame->StyleVisibility()->IsVisible()) {
   1412        if (!aFrame->StyleSVG()->mFill.kind.IsNone()) {
   1413          flags = SVG_HIT_TEST_FILL;
   1414        }
   1415        if (!aFrame->StyleSVG()->mStroke.kind.IsNone()) {
   1416          flags |= SVG_HIT_TEST_STROKE;
   1417        }
   1418      }
   1419      break;
   1420    case StylePointerEvents::Visiblefill:
   1421      if (aFrame->StyleVisibility()->IsVisible()) {
   1422        flags = SVG_HIT_TEST_FILL;
   1423      }
   1424      break;
   1425    case StylePointerEvents::Visiblestroke:
   1426      if (aFrame->StyleVisibility()->IsVisible()) {
   1427        flags = SVG_HIT_TEST_STROKE;
   1428      }
   1429      break;
   1430    case StylePointerEvents::Visible:
   1431      if (aFrame->StyleVisibility()->IsVisible()) {
   1432        flags = SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
   1433      }
   1434      break;
   1435    case StylePointerEvents::Painted:
   1436      if (!aFrame->StyleSVG()->mFill.kind.IsNone()) {
   1437        flags = SVG_HIT_TEST_FILL;
   1438      }
   1439      if (!aFrame->StyleSVG()->mStroke.kind.IsNone()) {
   1440        flags |= SVG_HIT_TEST_STROKE;
   1441      }
   1442      break;
   1443    case StylePointerEvents::Fill:
   1444      flags = SVG_HIT_TEST_FILL;
   1445      break;
   1446    case StylePointerEvents::Stroke:
   1447      flags = SVG_HIT_TEST_STROKE;
   1448      break;
   1449    case StylePointerEvents::All:
   1450      flags = SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
   1451      break;
   1452    default:
   1453      NS_ERROR("not reached");
   1454      break;
   1455  }
   1456 
   1457  return flags;
   1458 }
   1459 
   1460 void SVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) {
   1461  nsIFrame* frame = aElement->GetPrimaryFrame();
   1462  ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame);
   1463  if (!svgFrame) {
   1464    return;
   1465  }
   1466  gfxMatrix m;
   1467  if (frame->GetContent()->IsSVGElement()) {
   1468    // PaintSVG() expects the passed transform to be the transform to its own
   1469    // SVG user space, so we need to account for any 'transform' attribute:
   1470    m = SVGUtils::GetTransformMatrixInUserSpace(frame);
   1471  }
   1472 
   1473  // SVG-in-OpenType is not allowed to paint external resources, so we can
   1474  // just pass a dummy params into PatintSVG.
   1475  imgDrawingParams dummy;
   1476  svgFrame->PaintSVG(*aContext, m, dummy);
   1477 }
   1478 
   1479 bool SVGUtils::GetSVGGlyphExtents(const Element* aElement,
   1480                                  const gfxMatrix& aSVGToAppSpace,
   1481                                  gfxRect* aResult) {
   1482  nsIFrame* frame = aElement->GetPrimaryFrame();
   1483  ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame);
   1484  if (!svgFrame) {
   1485    return false;
   1486  }
   1487 
   1488  gfxMatrix transform = aSVGToAppSpace;
   1489  if (auto* svg = SVGElement::FromNode(frame->GetContent())) {
   1490    transform = svg->ChildToUserSpaceTransform() * transform;
   1491  }
   1492 
   1493  *aResult =
   1494      svgFrame
   1495          ->GetBBoxContribution(gfx::ToMatrix(transform),
   1496                                SVGUtils::eBBoxIncludeFill |
   1497                                    SVGUtils::eBBoxIncludeFillGeometry |
   1498                                    SVGUtils::eBBoxIncludeStroke |
   1499                                    SVGUtils::eBBoxIncludeStrokeGeometry |
   1500                                    SVGUtils::eBBoxIncludeMarkers)
   1501          .ToThebesRect();
   1502  return true;
   1503 }
   1504 
   1505 nsRect SVGUtils::ToCanvasBounds(const gfxRect& aUserspaceRect,
   1506                                const gfxMatrix& aToCanvas,
   1507                                const nsPresContext* presContext) {
   1508  return nsLayoutUtils::RoundGfxRectToAppRect(
   1509      aToCanvas.TransformBounds(aUserspaceRect),
   1510      presContext->AppUnitsPerDevPixel());
   1511 }
   1512 
   1513 gfxMatrix SVGUtils::GetCSSPxToDevPxMatrix(const nsIFrame* aNonSVGFrame) {
   1514  float devPxPerCSSPx = aNonSVGFrame->PresContext()->CSSToDevPixelScale().scale;
   1515 
   1516  return gfxMatrix(devPxPerCSSPx, 0.0, 0.0, devPxPerCSSPx, 0.0, 0.0);
   1517 }
   1518 
   1519 gfxMatrix SVGUtils::GetTransformMatrixInUserSpace(const nsIFrame* aFrame) {
   1520  // We check element instead of aFrame directly because SVG element
   1521  // may have non-SVG frame, <tspan> for example.
   1522  MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsSVGElement(),
   1523             "Only use this wrapper for SVG elements");
   1524 
   1525  if (!aFrame->IsTransformed()) {
   1526    return {};
   1527  }
   1528 
   1529  nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
   1530  nsDisplayTransform::FrameTransformProperties properties{
   1531      aFrame, refBox, AppUnitsPerCSSPixel()};
   1532 
   1533  // SVG elements can have x/y offset, their default transform origin
   1534  // is the origin of user space, not the top left point of the frame.
   1535  Point3D svgTransformOrigin{
   1536      properties.mToTransformOrigin.x - CSSPixel::FromAppUnits(refBox.X()),
   1537      properties.mToTransformOrigin.y - CSSPixel::FromAppUnits(refBox.Y()),
   1538      properties.mToTransformOrigin.z};
   1539 
   1540  Matrix svgTransform;
   1541  Matrix4x4 trans;
   1542  if (properties.HasTransform()) {
   1543    trans = nsStyleTransformMatrix::ReadTransforms(
   1544        properties.mTranslate, properties.mRotate, properties.mScale,
   1545        properties.mMotion.ptrOr(nullptr), properties.mTransform, refBox,
   1546        AppUnitsPerCSSPixel());
   1547  }
   1548 
   1549  trans.ChangeBasis(svgTransformOrigin);
   1550 
   1551  Matrix mm;
   1552  trans.ProjectTo2D();
   1553  (void)trans.CanDraw2D(&mm);
   1554 
   1555  return ThebesMatrix(mm);
   1556 }
   1557 
   1558 }  // namespace mozilla