/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Main header first: // This is also necessary to ensure our definition of M_SQRT1_2 is picked up #include "SVGUtils.h" #include // Keep others in (case-insensitive) order: #include "gfx2DGlue.h" #include "gfxContext.h" #include "gfxMatrix.h" #include "gfxPlatform.h" #include "gfxRect.h" #include "gfxUtils.h" #include "nsCSSFrameConstructor.h" #include "nsDisplayList.h" #include "nsFrameList.h" #include "nsGkAtoms.h" #include "nsIContent.h" #include "nsIFrame.h" #include "nsIFrameInlines.h" #include "nsLayoutUtils.h" #include "nsPresContext.h" #include "nsStyleStruct.h" #include "nsStyleTransformMatrix.h" #include "SVGAnimatedLength.h" #include "SVGPaintServerFrame.h" #include "nsTextFrame.h" #include "mozilla/CSSClipPathInstance.h" #include "mozilla/FilterInstance.h" #include "mozilla/ISVGDisplayableFrame.h" #include "mozilla/Preferences.h" #include "mozilla/StaticPrefs_svg.h" #include "mozilla/SVGClipPathFrame.h" #include "mozilla/SVGContainerFrame.h" #include "mozilla/SVGContentUtils.h" #include "mozilla/SVGContextPaint.h" #include "mozilla/SVGForeignObjectFrame.h" #include "mozilla/SVGIntegrationUtils.h" #include "mozilla/SVGGeometryFrame.h" #include "mozilla/SVGMaskFrame.h" #include "mozilla/SVGObserverUtils.h" #include "mozilla/SVGOuterSVGFrame.h" #include "mozilla/SVGTextFrame.h" #include "mozilla/Unused.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/PatternHelpers.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/SVGClipPathElement.h" #include "mozilla/dom/SVGGeometryElement.h" #include "mozilla/dom/SVGPathElement.h" #include "mozilla/dom/SVGUnitTypesBinding.h" #include "mozilla/dom/SVGViewportElement.h" using namespace mozilla::dom; using namespace mozilla::dom::SVGUnitTypes_Binding; using namespace mozilla::gfx; using namespace mozilla::image; bool NS_SVGNewGetBBoxEnabled() { return mozilla::StaticPrefs::svg_new_getBBox_enabled(); } namespace mozilla { // we only take the address of this: static gfx::UserDataKey sSVGAutoRenderStateKey; SVGAutoRenderState::SVGAutoRenderState(DrawTarget* aDrawTarget) : mDrawTarget(aDrawTarget), mOriginalRenderState(nullptr), mPaintingToWindow(false) { mOriginalRenderState = aDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey); // We always remove ourselves from aContext before it dies, so // passing nullptr as the destroy function is okay. aDrawTarget->AddUserData(&sSVGAutoRenderStateKey, this, nullptr); } SVGAutoRenderState::~SVGAutoRenderState() { mDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey); if (mOriginalRenderState) { mDrawTarget->AddUserData(&sSVGAutoRenderStateKey, mOriginalRenderState, nullptr); } } void SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) { mPaintingToWindow = aPaintingToWindow; } /* static */ bool SVGAutoRenderState::IsPaintingToWindow(DrawTarget* aDrawTarget) { void* state = aDrawTarget->GetUserData(&sSVGAutoRenderStateKey); if (state) { return static_cast(state)->mPaintingToWindow; } return false; } // Unlike containers, leaf frames do not include GetPosition() in // GetCanvasTM(). static bool FrameDoesNotIncludePositionInTM(const nsIFrame* aFrame) { return aFrame->IsSVGGeometryFrame() || aFrame->IsSVGImageFrame() || aFrame->IsInSVGTextSubtree(); } nsRect SVGUtils::GetPostFilterInkOverflowRect(nsIFrame* aFrame, const nsRect& aPreFilterRect) { MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT), "Called on invalid frame type"); // Note: we do not return here for eHasNoRefs since we must still handle any // CSS filter functions. // in that case we disable painting of the element. nsTArray filterFrames; if (!aFrame->StyleEffects()->HasFilters() || SVGObserverUtils::GetAndObserveFilters(aFrame, &filterFrames) == SVGObserverUtils::eHasRefsSomeInvalid) { return aPreFilterRect; } return FilterInstance::GetPostFilterBounds(aFrame, filterFrames, nullptr, &aPreFilterRect) .valueOr(aPreFilterRect); } bool SVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame* aFrame) { return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG(); } bool SVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) { SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); do { if (outer->IsCallingReflowSVG()) { return true; } outer = GetOuterSVGFrame(outer->GetParent()); } while (outer); return false; } void SVGUtils::ScheduleReflowSVG(nsIFrame* aFrame) { MOZ_ASSERT(aFrame->IsSVGFrame(), "Passed bad frame!"); // If this is triggered, the callers should be fixed to call us before // ReflowSVG is called. If we try to mark dirty bits on frames while we're // in the process of removing them, things will get messed up. MOZ_ASSERT(!OuterSVGIsCallingReflowSVG(aFrame), "Do not call under ISVGDisplayableFrame::ReflowSVG!"); // We don't call SVGObserverUtils::InvalidateRenderingObservers here because // we should only be called under InvalidateAndScheduleReflowSVG (which // calls InvalidateBounds) or SVGDisplayContainerFrame::InsertFrames // (at which point the frame has no observers). if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { return; } if (aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) { // Nothing to do if we're already dirty, or if the outer- // hasn't yet had its initial reflow. return; } SVGOuterSVGFrame* outerSVGFrame = nullptr; // We must not add dirty bits to the SVGOuterSVGFrame or else // PresShell::FrameNeedsReflow won't work when we pass it in below. if (aFrame->IsSVGOuterSVGFrame()) { outerSVGFrame = static_cast(aFrame); } else { aFrame->MarkSubtreeDirty(); nsIFrame* f = aFrame->GetParent(); while (f && !f->IsSVGOuterSVGFrame()) { if (f->HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) { return; } f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); f = f->GetParent(); MOZ_ASSERT(f->IsSVGFrame(), "IsSVGOuterSVGFrame check above not valid!"); } outerSVGFrame = static_cast(f); MOZ_ASSERT(outerSVGFrame && outerSVGFrame->IsSVGOuterSVGFrame(), "Did not find SVGOuterSVGFrame!"); } if (outerSVGFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) { // We're currently under an SVGOuterSVGFrame::Reflow call so there is no // need to call PresShell::FrameNeedsReflow, since we have an // SVGOuterSVGFrame::DidReflow call pending. return; } nsFrameState dirtyBit = (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY : NS_FRAME_HAS_DIRTY_CHILDREN); aFrame->PresShell()->FrameNeedsReflow(outerSVGFrame, IntrinsicDirty::None, dirtyBit); } bool SVGUtils::NeedsReflowSVG(const nsIFrame* aFrame) { MOZ_ASSERT(aFrame->IsSVGFrame(), "SVG uses bits differently!"); // The flags we test here may change, hence why we have this separate // function. return aFrame->IsSubtreeDirty(); } Size SVGUtils::GetContextSize(const nsIFrame* aFrame) { Size size; MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast"); const SVGElement* element = static_cast(aFrame->GetContent()); SVGViewportElement* ctx = element->GetCtx(); if (ctx) { size.width = ctx->GetLength(SVGContentUtils::X); size.height = ctx->GetLength(SVGContentUtils::Y); } return size; } float SVGUtils::ObjectSpace(const gfxRect& aRect, const SVGAnimatedLength* aLength) { float axis; switch (aLength->GetCtxType()) { case SVGContentUtils::X: axis = aRect.Width(); break; case SVGContentUtils::Y: axis = aRect.Height(); break; case SVGContentUtils::XY: axis = float(SVGContentUtils::ComputeNormalizedHypotenuse( aRect.Width(), aRect.Height())); break; default: MOZ_ASSERT_UNREACHABLE("unexpected ctx type"); axis = 0.0f; break; } if (aLength->IsPercentage()) { // Multiply first to avoid precision errors: return axis * aLength->GetAnimValInSpecifiedUnits() / 100; } return aLength->GetAnimValue(static_cast(nullptr)) * axis; } float SVGUtils::UserSpace(nsIFrame* aNonSVGContext, const SVGAnimatedLength* aLength) { MOZ_ASSERT(!aNonSVGContext->IsTextFrame(), "Not expecting text content"); return aLength->GetAnimValue(aNonSVGContext); } float SVGUtils::UserSpace(const UserSpaceMetrics& aMetrics, const SVGAnimatedLength* aLength) { return aLength->GetAnimValue(aMetrics); } SVGOuterSVGFrame* SVGUtils::GetOuterSVGFrame(nsIFrame* aFrame) { return static_cast(nsLayoutUtils::GetClosestFrameOfType( aFrame, LayoutFrameType::SVGOuterSVG)); } nsIFrame* SVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, nsRect* aRect) { ISVGDisplayableFrame* svg = do_QueryFrame(aFrame); if (!svg) { return nullptr; } SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); if (outer == svg) { return nullptr; } if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { *aRect = nsRect(0, 0, 0, 0); } else { uint32_t flags = SVGUtils::eForGetClientRects | SVGUtils::eBBoxIncludeFill | SVGUtils::eBBoxIncludeStroke | SVGUtils::eBBoxIncludeMarkers | SVGUtils::eUseUserSpaceOfUseElement; auto ctm = nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame}, RelativeTo{outer}); float initPositionX = NSAppUnitsToFloatPixels(aFrame->GetPosition().x, AppUnitsPerCSSPixel()), initPositionY = NSAppUnitsToFloatPixels(aFrame->GetPosition().y, AppUnitsPerCSSPixel()); Matrix mm; ctm.ProjectTo2D(); ctm.CanDraw2D(&mm); gfxMatrix m = ThebesMatrix(mm); float appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); float devPixelPerCSSPixel = float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel; // The matrix that GetBBox accepts should operate on "user space", // i.e. with CSS pixel unit. m = m.PreScale(devPixelPerCSSPixel, devPixelPerCSSPixel); // Both SVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor // will count this displacement, we should remove it here to avoid // double-counting. m = m.PreTranslate(-initPositionX, -initPositionY); gfxRect bbox = SVGUtils::GetBBox(aFrame, flags, &m); *aRect = nsLayoutUtils::RoundGfxRectToAppRect(bbox, appUnitsPerDevPixel); } return outer; } gfxMatrix SVGUtils::GetCanvasTM(nsIFrame* aFrame) { // XXX yuck, we really need a common interface for GetCanvasTM if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { return GetCSSPxToDevPxMatrix(aFrame); } if (aFrame->IsSVGForeignObjectFrame()) { return static_cast(aFrame)->GetCanvasTM(); } if (SVGContainerFrame* containerFrame = do_QueryFrame(aFrame)) { return containerFrame->GetCanvasTM(); } MOZ_ASSERT(aFrame->GetParent()->IsSVGContainerFrame()); auto* parent = static_cast(aFrame->GetParent()); auto* content = static_cast(aFrame->GetContent()); return content->PrependLocalTransformsTo(parent->GetCanvasTM()); } bool SVGUtils::IsSVGTransformed(const nsIFrame* aFrame, gfx::Matrix* aOwnTransform, gfx::Matrix* aFromParentTransform) { MOZ_ASSERT(aFrame->HasAllStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_MAY_BE_TRANSFORMED), "Expecting an SVG frame that can be transformed"); bool foundTransform = false; // Check if our parent has children-only transforms: if (SVGContainerFrame* parent = do_QueryFrame(aFrame->GetParent())) { foundTransform = parent->HasChildrenOnlyTransform(aFromParentTransform); } if (auto* content = SVGElement::FromNode(aFrame->GetContent())) { auto* transformList = content->GetAnimatedTransformList(); if ((transformList && transformList->HasTransform()) || content->GetAnimateMotionTransform()) { if (aOwnTransform) { *aOwnTransform = gfx::ToMatrix( content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent)); } foundTransform = true; } } return foundTransform; } void SVGUtils::NotifyChildrenOfSVGChange(nsIFrame* aFrame, uint32_t aFlags) { for (nsIFrame* kid : aFrame->PrincipalChildList()) { ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); if (SVGFrame) { SVGFrame->NotifySVGChanged(aFlags); } else { NS_ASSERTION(kid->IsSVGFrame() || kid->IsInSVGTextSubtree(), "SVG frame expected"); // recurse into the children of container frames e.g. , // in case they have child frames with transformation matrices if (kid->IsSVGFrame()) { NotifyChildrenOfSVGChange(kid, aFlags); } } } } // ************************************************************ float SVGUtils::ComputeOpacity(const nsIFrame* aFrame, bool aHandleOpacity) { const auto* styleEffects = aFrame->StyleEffects(); if (!styleEffects->IsOpaque() && (SVGUtils::CanOptimizeOpacity(aFrame) || !aHandleOpacity)) { return 1.0f; } return styleEffects->mOpacity; } SVGUtils::MaskUsage SVGUtils::DetermineMaskUsage(const nsIFrame* aFrame, bool aHandleOpacity) { MaskUsage usage; using ClipPathType = StyleClipPath::Tag; usage.mOpacity = ComputeOpacity(aFrame, aHandleOpacity); nsIFrame* firstFrame = nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset(); if (SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) != SVGObserverUtils::eHasNoRefs) { usage.mShouldGenerateMaskLayer = true; } SVGClipPathFrame* clipPathFrame; // XXX check return value? SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame); MOZ_ASSERT(!clipPathFrame || svgReset->mClipPath.IsUrl()); switch (svgReset->mClipPath.tag) { case ClipPathType::Url: if (clipPathFrame) { if (clipPathFrame->IsTrivial()) { usage.mShouldApplyClipPath = true; } else { usage.mShouldGenerateClipMaskLayer = true; } } break; case ClipPathType::Shape: { usage.mShouldApplyBasicShapeOrPath = true; const auto& shape = svgReset->mClipPath.AsShape()._0; usage.mIsSimpleClipShape = !usage.mShouldGenerateMaskLayer && (shape->IsRect() || shape->IsCircle() || shape->IsEllipse()); break; } case ClipPathType::Box: usage.mShouldApplyBasicShapeOrPath = true; break; case ClipPathType::None: MOZ_ASSERT(!usage.mShouldGenerateClipMaskLayer && !usage.mShouldApplyClipPath && !usage.mShouldApplyBasicShapeOrPath); break; default: MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type."); break; } return usage; } class MixModeBlender { public: using Factory = gfx::Factory; MixModeBlender(nsIFrame* aFrame, gfxContext* aContext) : mFrame(aFrame), mSourceCtx(aContext) { MOZ_ASSERT(mFrame && mSourceCtx); } bool ShouldCreateDrawTargetForBlend() const { return mFrame->StyleEffects()->HasMixBlendMode(); } gfxContext* CreateBlendTarget(const gfxMatrix& aTransform) { MOZ_ASSERT(ShouldCreateDrawTargetForBlend()); // Create a temporary context to draw to so we can blend it back with // another operator. IntRect drawRect = ComputeClipExtsInDeviceSpace(aTransform); if (drawRect.IsEmpty()) { return nullptr; } RefPtr targetDT = mSourceCtx->GetDrawTarget()->CreateSimilarDrawTarget( drawRect.Size(), SurfaceFormat::B8G8R8A8); if (!targetDT || !targetDT->IsValid()) { return nullptr; } MOZ_ASSERT(!mTargetCtx, "CreateBlendTarget is designed to be used once only."); mTargetCtx = gfxContext::CreateOrNull(targetDT); MOZ_ASSERT(mTargetCtx); // already checked the draw target above mTargetCtx->SetMatrix(mSourceCtx->CurrentMatrix() * Matrix::Translation(-drawRect.TopLeft())); mTargetOffset = drawRect.TopLeft(); return mTargetCtx.get(); } void BlendToTarget() { MOZ_ASSERT(ShouldCreateDrawTargetForBlend()); MOZ_ASSERT(mTargetCtx, "BlendToTarget should be used after CreateBlendTarget."); RefPtr targetSurf = mTargetCtx->GetDrawTarget()->Snapshot(); gfxContextAutoSaveRestore save(mSourceCtx); mSourceCtx->SetMatrix(Matrix()); // This will be restored right after. RefPtr pattern = new gfxPattern( targetSurf, Matrix::Translation(mTargetOffset.x, mTargetOffset.y)); mSourceCtx->SetPattern(pattern); mSourceCtx->Paint(); } private: MixModeBlender() = delete; IntRect ComputeClipExtsInDeviceSpace(const gfxMatrix& aTransform) { // These are used if we require a temporary surface for a custom blend // mode. Clip the source context first, so that we can generate a smaller // temporary surface. (Since we will clip this context in // SetupContextMatrix, a pair of save/restore is needed.) gfxContextAutoSaveRestore saver; if (!mFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { saver.SetContext(mSourceCtx); // aFrame has a valid ink overflow rect, so clip to it before calling // PushGroup() to minimize the size of the surfaces we'll composite: gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(mSourceCtx); mSourceCtx->Multiply(aTransform); nsRect overflowRect = mFrame->InkOverflowRectRelativeToSelf(); if (FrameDoesNotIncludePositionInTM(mFrame)) { overflowRect = overflowRect + mFrame->GetPosition(); } mSourceCtx->Clip(NSRectToSnappedRect( overflowRect, mFrame->PresContext()->AppUnitsPerDevPixel(), *mSourceCtx->GetDrawTarget())); } // Get the clip extents in device space. gfxRect clippedFrameSurfaceRect = mSourceCtx->GetClipExtents(gfxContext::eDeviceSpace); clippedFrameSurfaceRect.RoundOut(); IntRect result; ToRect(clippedFrameSurfaceRect).ToIntRect(&result); return Factory::CheckSurfaceSize(result.Size()) ? result : IntRect(); } nsIFrame* mFrame; gfxContext* mSourceCtx; UniquePtr mTargetCtx; IntPoint mTargetOffset; }; void SVGUtils::PaintFrameWithEffects(nsIFrame* aFrame, gfxContext& aContext, const gfxMatrix& aTransform, imgDrawingParams& aImgParams) { NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || aFrame->PresContext()->Document()->IsSVGGlyphsDocument(), "Only painting of non-display SVG should take this code path"); ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame); if (!svgFrame) { return; } MaskUsage maskUsage = DetermineMaskUsage(aFrame, true); if (maskUsage.IsTransparent()) { return; } if (auto* svg = SVGElement::FromNode(aFrame->GetContent())) { if (!svg->HasValidDimensions()) { return; } } /* SVG defines the following rendering model: * * 1. Render fill * 2. Render stroke * 3. Render markers * 4. Apply filter * 5. Apply clipping, masking, group opacity * * We follow this, but perform a couple of optimizations: * * + Use cairo's clipPath when representable natively (single object * clip region). * * + Merge opacity and masking if both used together. */ /* Properties are added lazily and may have been removed by a restyle, so make sure all applicable ones are set again. */ SVGClipPathFrame* clipPathFrame; nsTArray maskFrames; nsTArray filterFrames; const bool hasInvalidFilter = SVGObserverUtils::GetAndObserveFilters(aFrame, &filterFrames) == SVGObserverUtils::eHasRefsSomeInvalid; SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame); SVGObserverUtils::GetAndObserveMasks(aFrame, &maskFrames); SVGMaskFrame* maskFrame = maskFrames.IsEmpty() ? nullptr : maskFrames[0]; MixModeBlender blender(aFrame, &aContext); gfxContext* target = blender.ShouldCreateDrawTargetForBlend() ? blender.CreateBlendTarget(aTransform) : &aContext; if (!target) { return; } /* Check if we need to do additional operations on this child's * rendering, which necessitates rendering into another surface. */ bool shouldPushMask = false; if (maskUsage.ShouldGenerateMask()) { RefPtr maskSurface; // maskFrame can be nullptr even if maskUsage.ShouldGenerateMaskLayer() is // true. That happens when a user gives an unresolvable mask-id, such as // mask:url() // mask:url(#id-which-does-not-exist) // Since we only uses SVGUtils with SVG elements, not like mask on an // HTML element, we should treat an unresolvable mask as no-mask here. if (maskUsage.ShouldGenerateMaskLayer() && maskFrame) { StyleMaskMode maskMode = aFrame->StyleSVGReset()->mMask.mLayers[0].mMaskMode; SVGMaskFrame::MaskParams params(aContext.GetDrawTarget(), aFrame, aTransform, maskUsage.Opacity(), maskMode, aImgParams); maskSurface = maskFrame->GetMaskForMaskedFrame(params); if (!maskSurface) { // Either entire surface is clipped out, or gfx buffer allocation // failure in SVGMaskFrame::GetMaskForMaskedFrame. return; } shouldPushMask = true; } if (maskUsage.ShouldGenerateClipMaskLayer()) { RefPtr clipMaskSurface = clipPathFrame->GetClipMask(aContext, aFrame, aTransform, maskSurface); if (clipMaskSurface) { maskSurface = clipMaskSurface; } else { // Either entire surface is clipped out, or gfx buffer allocation // failure in SVGClipPathFrame::GetClipMask. return; } shouldPushMask = true; } if (!maskUsage.ShouldGenerateLayer()) { shouldPushMask = true; } // SVG mask multiply opacity into maskSurface already, so we do not bother // to apply opacity again. if (shouldPushMask) { // We want the mask to be untransformed so use the inverse of the // current transform as the maskTransform to compensate. Matrix maskTransform = aContext.CurrentMatrix(); maskTransform.Invert(); target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, maskFrame ? 1.0f : maskUsage.Opacity(), maskSurface, maskTransform); } } /* If this frame has only a trivial clipPath, set up cairo's clipping now so * we can just do normal painting and get it clipped appropriately. */ if (maskUsage.ShouldApplyClipPath() || maskUsage.ShouldApplyBasicShapeOrPath()) { if (maskUsage.ShouldApplyClipPath()) { clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform); } else { CSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame, aTransform); } } /* Paint the child */ // Invalid filters should render the unfiltered contents per spec. if (aFrame->StyleEffects()->HasFilters() && !hasInvalidFilter) { gfxContextMatrixAutoSaveRestore autoSR(target); // 'target' is currently scaled such that its user space units are CSS // pixels (SVG user space units). But PaintFilteredFrame expects it to be // scaled in such a way that its user space units are device pixels. So we // have to adjust the scale. gfxMatrix reverseScaleMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aFrame); DebugOnly invertible = reverseScaleMatrix.Invert(); target->SetMatrixDouble(reverseScaleMatrix * aTransform * target->CurrentMatrixDouble()); auto callback = [&](gfxContext& aContext, imgDrawingParams& aImgParams, const gfxMatrix* aFilterTransform, const nsIntRect* aDirtyRect) { svgFrame->PaintSVG(aContext, aFilterTransform ? SVGUtils::GetCSSPxToDevPxMatrix(aFrame) : aTransform, aImgParams); }; // If we're masking a userSpaceOnUse mask we may need to include the // stroke too. Err on the side of caution and include it always. gfxRect bbox = GetBBox(aFrame, SVGUtils::eUseFrameBoundsForOuterSVG | SVGUtils::eBBoxIncludeFillGeometry | SVGUtils::eBBoxIncludeStroke); FilterInstance::PaintFilteredFrame( aFrame, aFrame->StyleEffects()->mFilters.AsSpan(), filterFrames, target, callback, nullptr, aImgParams, 1.0f, &bbox); } else { svgFrame->PaintSVG(*target, aTransform, aImgParams); } if (maskUsage.ShouldApplyClipPath() || maskUsage.ShouldApplyBasicShapeOrPath()) { aContext.PopClip(); } if (shouldPushMask) { target->PopGroupAndBlend(); } if (blender.ShouldCreateDrawTargetForBlend()) { MOZ_ASSERT(target != &aContext); blender.BlendToTarget(); } } bool SVGUtils::HitTestClip(nsIFrame* aFrame, const gfxPoint& aPoint) { const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset(); if (!svgReset->HasClipPath()) { return true; } if (svgReset->mClipPath.IsUrl()) { // If the clip-path property references non-existent or invalid clipPath // element(s) we ignore it. SVGClipPathFrame* clipPathFrame; SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame); return !clipPathFrame || clipPathFrame->PointIsInsideClipPath(aFrame, aPoint); } return CSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame, aPoint); } IntSize SVGUtils::ConvertToSurfaceSize(const gfxSize& aSize, bool* aResultOverflows) { IntSize surfaceSize(ClampToInt(ceil(aSize.width)), ClampToInt(ceil(aSize.height))); *aResultOverflows = surfaceSize.width != ceil(aSize.width) || surfaceSize.height != ceil(aSize.height); if (!Factory::AllowedSurfaceSize(surfaceSize)) { surfaceSize.width = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.width); surfaceSize.height = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.height); *aResultOverflows = true; } return surfaceSize; } bool SVGUtils::HitTestRect(const gfx::Matrix& aMatrix, float aRX, float aRY, float aRWidth, float aRHeight, float aX, float aY) { gfx::Rect rect(aRX, aRY, aRWidth, aRHeight); if (rect.IsEmpty() || aMatrix.IsSingular()) { return false; } gfx::Matrix toRectSpace = aMatrix; toRectSpace.Invert(); gfx::Point p = toRectSpace.TransformPoint(gfx::Point(aX, aY)); return rect.x <= p.x && p.x <= rect.XMost() && rect.y <= p.y && p.y <= rect.YMost(); } gfxRect SVGUtils::GetClipRectForFrame(const nsIFrame* aFrame, float aX, float aY, float aWidth, float aHeight) { const nsStyleDisplay* disp = aFrame->StyleDisplay(); const nsStyleEffects* effects = aFrame->StyleEffects(); bool clipApplies = disp->mOverflowX == StyleOverflow::Hidden || disp->mOverflowY == StyleOverflow::Hidden; if (!clipApplies || effects->mClip.IsAuto()) { return gfxRect(aX, aY, aWidth, aHeight); } const auto& rect = effects->mClip.AsRect(); nsRect coordClipRect = rect.ToLayoutRect(); nsIntRect clipPxRect = coordClipRect.ToOutsidePixels( aFrame->PresContext()->AppUnitsPerDevPixel()); gfxRect clipRect = gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height); if (rect.right.IsAuto()) { clipRect.width = aWidth - clipRect.X(); } if (rect.bottom.IsAuto()) { clipRect.height = aHeight - clipRect.Y(); } if (disp->mOverflowX != StyleOverflow::Hidden) { clipRect.x = aX; clipRect.width = aWidth; } if (disp->mOverflowY != StyleOverflow::Hidden) { clipRect.y = aY; clipRect.height = aHeight; } return clipRect; } gfxRect SVGUtils::GetBBox(nsIFrame* aFrame, uint32_t aFlags, const gfxMatrix* aToBoundsSpace) { if (aFrame->IsTextFrame()) { aFrame = aFrame->GetParent(); } if (aFrame->IsInSVGTextSubtree()) { // It is possible to apply a gradient, pattern, clipping path, mask or // filter to text. When one of these facilities is applied to text // the bounding box is the entire text element in all cases. aFrame = nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText); } ISVGDisplayableFrame* svg = do_QueryFrame(aFrame); const bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT); if (hasSVGLayout && !svg) { // An SVG frame, but not one that can be displayed directly (for // example, nsGradientFrame). These can't contribute to the bbox. return gfxRect(); } const bool isOuterSVG = svg && !hasSVGLayout; MOZ_ASSERT(!isOuterSVG || aFrame->IsSVGOuterSVGFrame()); if (!svg || (isOuterSVG && (aFlags & eUseFrameBoundsForOuterSVG))) { // An HTML element or an SVG outer frame. MOZ_ASSERT(!hasSVGLayout); bool onlyCurrentFrame = aFlags & eIncludeOnlyCurrentFrameForNonSVGElement; return SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame( aFrame, /* aUnionContinuations = */ !onlyCurrentFrame); } MOZ_ASSERT(svg); if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) { if (!element->HasValidDimensions()) { return gfxRect(); } } // Clean out flags which have no effects on returning bbox from now, so that // we can cache and reuse ObjectBoundingBoxProperty() in the code below. aFlags &= ~(eIncludeOnlyCurrentFrameForNonSVGElement | eUseFrameBoundsForOuterSVG); if (!aFrame->IsSVGUseFrame()) { aFlags &= ~eUseUserSpaceOfUseElement; } if (aFlags == eBBoxIncludeFillGeometry && // We only cache bbox in element's own user space !aToBoundsSpace) { gfxRect* prop = aFrame->GetProperty(ObjectBoundingBoxProperty()); if (prop) { return *prop; } } gfxMatrix matrix; if (aToBoundsSpace) { matrix = *aToBoundsSpace; } if (aFrame->IsSVGForeignObjectFrame() || aFlags & SVGUtils::eUseUserSpaceOfUseElement) { // The spec says getBBox "Returns the tight bounding box in *current user // space*". So we should really be doing this for all elements, but that // needs investigation to check that we won't break too much content. // NOTE: When changing this to apply to other frame types, make sure to // also update SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset. MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast"); SVGElement* element = static_cast(aFrame->GetContent()); matrix = element->PrependLocalTransformsTo(matrix, eChildToUserSpace); } gfxRect bbox = svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect(); // Account for 'clipped'. if (aFlags & SVGUtils::eBBoxIncludeClipped) { gfxRect clipRect; float x, y, width, height; gfxRect fillBBox = svg->GetBBoxContribution({}, SVGUtils::eBBoxIncludeFill).ToThebesRect(); x = fillBBox.x; y = fillBBox.y; width = fillBBox.width; height = fillBBox.height; // XXX Should probably check for overflow: clip too. bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow(); if (hasClip) { clipRect = SVGUtils::GetClipRectForFrame(aFrame, x, y, width, height); if (aFrame->IsSVGForeignObjectFrame() || aFrame->IsSVGUseFrame()) { clipRect = matrix.TransformBounds(clipRect); } } SVGClipPathFrame* clipPathFrame; if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) == SVGObserverUtils::eHasRefsSomeInvalid) { bbox = gfxRect(0, 0, 0, 0); } else { if (clipPathFrame) { SVGClipPathElement* clipContent = static_cast(clipPathFrame->GetContent()); if (clipContent->IsUnitsObjectBoundingBox()) { matrix.PreTranslate(gfxPoint(x, y)); matrix.PreScale(width, height); } else if (aFrame->IsSVGForeignObjectFrame()) { matrix = gfxMatrix(); } matrix *= SVGUtils::GetTransformMatrixInUserSpace(clipPathFrame); bbox = clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags) .ToThebesRect(); } if (hasClip) { bbox = bbox.Intersect(clipRect); } if (bbox.IsEmpty()) { bbox = gfxRect(0, 0, 0, 0); } } } if (aFlags == eBBoxIncludeFillGeometry && // We only cache bbox in element's own user space !aToBoundsSpace) { // Obtaining the bbox for objectBoundingBox calculations is common so we // cache the result for future calls, since calculation can be expensive: aFrame->SetProperty(ObjectBoundingBoxProperty(), new gfxRect(bbox)); } return bbox; } gfxPoint SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(const nsIFrame* aFrame) { if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { // The user space for non-SVG frames is defined as the bounding box of the // frame's border-box rects over all continuations. return gfxPoint(); } // Leaf frames apply their own offset inside their user space. if (FrameDoesNotIncludePositionInTM(aFrame)) { return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(), AppUnitsPerCSSPixel()) .TopLeft(); } // For foreignObject frames, SVGUtils::GetBBox applies their local // transform, so we need to do the same here. if (aFrame->IsSVGForeignObjectFrame()) { gfxMatrix transform = static_cast(aFrame->GetContent()) ->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); NS_ASSERTION(!transform.HasNonTranslation(), "we're relying on this being an offset-only transform"); return transform.GetTranslation(); } return gfxPoint(); } static gfxRect GetBoundingBoxRelativeRect(const SVGAnimatedLength* aXYWH, const gfxRect& aBBox) { return gfxRect(aBBox.x + SVGUtils::ObjectSpace(aBBox, &aXYWH[0]), aBBox.y + SVGUtils::ObjectSpace(aBBox, &aXYWH[1]), SVGUtils::ObjectSpace(aBBox, &aXYWH[2]), SVGUtils::ObjectSpace(aBBox, &aXYWH[3])); } gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits, const SVGAnimatedLength* aXYWH, const gfxRect& aBBox, const UserSpaceMetrics& aMetrics) { if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { return GetBoundingBoxRelativeRect(aXYWH, aBBox); } return gfxRect(UserSpace(aMetrics, &aXYWH[0]), UserSpace(aMetrics, &aXYWH[1]), UserSpace(aMetrics, &aXYWH[2]), UserSpace(aMetrics, &aXYWH[3])); } gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits, const SVGAnimatedLength* aXYWH, const gfxRect& aBBox, nsIFrame* aFrame) { if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { return GetBoundingBoxRelativeRect(aXYWH, aBBox); } if (SVGElement* svgElement = SVGElement::FromNode(aFrame->GetContent())) { return GetRelativeRect(aUnits, aXYWH, aBBox, SVGElementMetrics(svgElement)); } return GetRelativeRect(aUnits, aXYWH, aBBox, NonSVGFrameUserSpaceMetrics(aFrame)); } bool SVGUtils::CanOptimizeOpacity(const nsIFrame* aFrame) { if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { return false; } auto* content = aFrame->GetContent(); if (!content->IsSVGGeometryElement() && !content->IsSVGElement(nsGkAtoms::image)) { return false; } if (aFrame->StyleEffects()->HasFilters()) { return false; } // XXX The SVG WG is intending to allow fill, stroke and markers on if (content->IsSVGElement(nsGkAtoms::image)) { return true; } const nsStyleSVG* style = aFrame->StyleSVG(); if (style->HasMarker() && static_cast(content)->IsMarkable()) { return false; } if (nsLayoutUtils::HasAnimationOfPropertySet( aFrame, nsCSSPropertyIDSet::OpacityProperties())) { return false; } return !style->HasFill() || !HasStroke(aFrame); } gfxMatrix SVGUtils::AdjustMatrixForUnits(const gfxMatrix& aMatrix, const SVGAnimatedEnumeration* aUnits, nsIFrame* aFrame, uint32_t aFlags) { if (aFrame && aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { gfxRect bbox = GetBBox(aFrame, aFlags); gfxMatrix tm = aMatrix; tm.PreTranslate(gfxPoint(bbox.X(), bbox.Y())); tm.PreScale(bbox.Width(), bbox.Height()); return tm; } return aMatrix; } bool SVGUtils::GetNonScalingStrokeTransform(const nsIFrame* aFrame, gfxMatrix* aUserToOuterSVG) { if (aFrame->GetContent()->IsText()) { aFrame = aFrame->GetParent(); } if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) { return false; } MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "should be an SVG element"); *aUserToOuterSVG = ThebesMatrix(SVGContentUtils::GetCTM( static_cast(aFrame->GetContent()), true)); return aUserToOuterSVG->HasNonTranslation(); } // The logic here comes from _cairo_stroke_style_max_distance_from_path static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, const nsIFrame* aFrame, double aStyleExpansionFactor, const gfxMatrix& aMatrix) { double style_expansion = aStyleExpansionFactor * SVGUtils::GetStrokeWidth(aFrame); gfxMatrix matrix = aMatrix; gfxMatrix outerSVGToUser; if (SVGUtils::GetNonScalingStrokeTransform(aFrame, &outerSVGToUser)) { outerSVGToUser.Invert(); matrix.PreMultiply(outerSVGToUser); } double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21)); double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12)); gfxRect strokeExtents = aPathExtents; strokeExtents.Inflate(dx, dy); return strokeExtents; } /*static*/ gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, const nsTextFrame* aFrame, const gfxMatrix& aMatrix) { NS_ASSERTION(aFrame->IsInSVGTextSubtree(), "expected an nsTextFrame for SVG text"); return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, aMatrix); } /*static*/ gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, const SVGGeometryFrame* aFrame, const gfxMatrix& aMatrix) { bool strokeMayHaveCorners = !SVGContentUtils::ShapeTypeHasNoCorners(aFrame->GetContent()); // For a shape without corners the stroke can only extend half the stroke // width from the path in the x/y-axis directions. For shapes with corners // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line // with stroke-linecaps="square"). double styleExpansionFactor = strokeMayHaveCorners ? M_SQRT1_2 : 0.5; // The stroke can extend even further for paths that can be affected by // stroke-miterlimit. // We only need to do this if the limit is greater than 1, but it's probably // not worth optimizing for that. bool affectedByMiterlimit = aFrame->GetContent()->IsAnyOfSVGElements( nsGkAtoms::path, nsGkAtoms::polyline, nsGkAtoms::polygon); if (affectedByMiterlimit) { const nsStyleSVG* style = aFrame->StyleSVG(); if (style->mStrokeLinejoin == StyleStrokeLinejoin::Miter && styleExpansionFactor < style->mStrokeMiterlimit / 2.0) { styleExpansionFactor = style->mStrokeMiterlimit / 2.0; } } return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, styleExpansionFactor, aMatrix); } // ---------------------------------------------------------------------- /* static */ nscolor SVGUtils::GetFallbackOrPaintColor( const ComputedStyle& aStyle, StyleSVGPaint nsStyleSVG::*aFillOrStroke, nscolor aDefaultContextFallbackColor) { const auto& paint = aStyle.StyleSVG()->*aFillOrStroke; nscolor color; switch (paint.kind.tag) { case StyleSVGPaintKind::Tag::PaintServer: color = paint.fallback.IsColor() ? paint.fallback.AsColor().CalcColor(aStyle) : NS_RGBA(0, 0, 0, 0); break; case StyleSVGPaintKind::Tag::ContextStroke: case StyleSVGPaintKind::Tag::ContextFill: color = paint.fallback.IsColor() ? paint.fallback.AsColor().CalcColor(aStyle) : aDefaultContextFallbackColor; break; default: color = paint.kind.AsColor().CalcColor(aStyle); break; } if (const auto* styleIfVisited = aStyle.GetStyleIfVisited()) { const auto& paintIfVisited = styleIfVisited->StyleSVG()->*aFillOrStroke; // To prevent Web content from detecting if a user has visited a URL // (via URL loading triggered by paint servers or performance // differences between paint servers or between a paint server and a // color), we do not allow whether links are visited to change which // paint server is used or switch between paint servers and simple // colors. A :visited style may only override a simple color with // another simple color. if (paintIfVisited.kind.IsColor() && paint.kind.IsColor()) { nscolor colors[2] = { color, paintIfVisited.kind.AsColor().CalcColor(*styleIfVisited)}; return ComputedStyle::CombineVisitedColors(colors, aStyle.RelevantLinkVisited()); } } return color; } /* static */ void SVGUtils::MakeFillPatternFor(nsIFrame* aFrame, gfxContext* aContext, GeneralPattern* aOutPattern, imgDrawingParams& aImgParams, SVGContextPaint* aContextPaint) { const nsStyleSVG* style = aFrame->StyleSVG(); if (style->mFill.kind.IsNone()) { return; } const auto* styleEffects = aFrame->StyleEffects(); float fillOpacity = GetOpacity(style->mFillOpacity, aContextPaint); if (!styleEffects->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame)) { // Combine the group opacity into the fill opacity (we will have skipped // creating an offscreen surface to apply the group opacity). fillOpacity *= styleEffects->mOpacity; } const DrawTarget* dt = aContext->GetDrawTarget(); SVGPaintServerFrame* ps = SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mFill); if (ps) { RefPtr pattern = ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(), &nsStyleSVG::mFill, fillOpacity, aImgParams); if (pattern) { pattern->CacheColorStops(dt); aOutPattern->Init(*pattern->GetPattern(dt)); return; } } if (aContextPaint) { RefPtr pattern; switch (style->mFill.kind.tag) { case StyleSVGPaintKind::Tag::ContextFill: pattern = aContextPaint->GetFillPattern( dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams); break; case StyleSVGPaintKind::Tag::ContextStroke: pattern = aContextPaint->GetStrokePattern( dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams); break; default:; } if (pattern) { aOutPattern->Init(*pattern->GetPattern(dt)); return; } } if (style->mFill.fallback.IsNone()) { return; } // On failure, use the fallback colour in case we have an // objectBoundingBox where the width or height of the object is zero. // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor( *aFrame->Style(), &nsStyleSVG::mFill, NS_RGB(0, 0, 0)))); color.a *= fillOpacity; aOutPattern->InitColorPattern(ToDeviceColor(color)); } /* static */ void SVGUtils::MakeStrokePatternFor(nsIFrame* aFrame, gfxContext* aContext, GeneralPattern* aOutPattern, imgDrawingParams& aImgParams, SVGContextPaint* aContextPaint) { const nsStyleSVG* style = aFrame->StyleSVG(); if (style->mStroke.kind.IsNone()) { return; } const auto* styleEffects = aFrame->StyleEffects(); float strokeOpacity = GetOpacity(style->mStrokeOpacity, aContextPaint); if (!styleEffects->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame)) { // Combine the group opacity into the stroke opacity (we will have skipped // creating an offscreen surface to apply the group opacity). strokeOpacity *= styleEffects->mOpacity; } const DrawTarget* dt = aContext->GetDrawTarget(); SVGPaintServerFrame* ps = SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mStroke); if (ps) { RefPtr pattern = ps->GetPaintServerPattern( aFrame, dt, aContext->CurrentMatrixDouble(), &nsStyleSVG::mStroke, strokeOpacity, aImgParams); if (pattern) { pattern->CacheColorStops(dt); aOutPattern->Init(*pattern->GetPattern(dt)); return; } } if (aContextPaint) { RefPtr pattern; switch (style->mStroke.kind.tag) { case StyleSVGPaintKind::Tag::ContextFill: pattern = aContextPaint->GetFillPattern( dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams); break; case StyleSVGPaintKind::Tag::ContextStroke: pattern = aContextPaint->GetStrokePattern( dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams); break; default:; } if (pattern) { aOutPattern->Init(*pattern->GetPattern(dt)); return; } } if (style->mStroke.fallback.IsNone()) { return; } // On failure, use the fallback colour in case we have an // objectBoundingBox where the width or height of the object is zero. // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor( *aFrame->Style(), &nsStyleSVG::mStroke, NS_RGBA(0, 0, 0, 0)))); color.a *= strokeOpacity; aOutPattern->InitColorPattern(ToDeviceColor(color)); } /* static */ float SVGUtils::GetOpacity(const StyleSVGOpacity& aOpacity, const SVGContextPaint* aContextPaint) { float opacity = 1.0f; switch (aOpacity.tag) { case StyleSVGOpacity::Tag::Opacity: return aOpacity.AsOpacity(); case StyleSVGOpacity::Tag::ContextFillOpacity: if (aContextPaint) { opacity = aContextPaint->GetFillOpacity(); } break; case StyleSVGOpacity::Tag::ContextStrokeOpacity: if (aContextPaint) { opacity = aContextPaint->GetStrokeOpacity(); } break; } return opacity; } bool SVGUtils::HasStroke(const nsIFrame* aFrame, const SVGContextPaint* aContextPaint) { const nsStyleSVG* style = aFrame->StyleSVG(); return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0; } float SVGUtils::GetStrokeWidth(const nsIFrame* aFrame, const SVGContextPaint* aContextPaint) { nsIContent* content = aFrame->GetContent(); if (content->IsText()) { content = content->GetParent(); } auto* ctx = SVGElement::FromNode(content); return SVGContentUtils::GetStrokeWidth(ctx, aFrame->Style(), aContextPaint); } void SVGUtils::SetupStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext, SVGContextPaint* aContextPaint) { MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast"); SVGContentUtils::AutoStrokeOptions strokeOptions; SVGContentUtils::GetStrokeOptions(&strokeOptions, SVGElement::FromNode(aFrame->GetContent()), aFrame->Style(), aContextPaint); if (strokeOptions.mLineWidth <= 0) { return; } // SVGContentUtils::GetStrokeOptions gets the stroke options in CSS px; // convert to device pixels for gfxContext. float devPxPerCSSPx = aFrame->PresContext()->CSSToDevPixelScale().scale; aContext->SetLineWidth(strokeOptions.mLineWidth * devPxPerCSSPx); aContext->SetLineCap(strokeOptions.mLineCap); aContext->SetMiterLimit(strokeOptions.mMiterLimit); aContext->SetLineJoin(strokeOptions.mLineJoin); aContext->SetDash(strokeOptions.mDashPattern, strokeOptions.mDashLength, strokeOptions.mDashOffset, devPxPerCSSPx); } uint16_t SVGUtils::GetGeometryHitTestFlags(const nsIFrame* aFrame) { uint16_t flags = 0; switch (aFrame->Style()->PointerEvents()) { case StylePointerEvents::None: break; case StylePointerEvents::Auto: case StylePointerEvents::Visiblepainted: if (aFrame->StyleVisibility()->IsVisible()) { if (!aFrame->StyleSVG()->mFill.kind.IsNone()) { flags = SVG_HIT_TEST_FILL; } if (!aFrame->StyleSVG()->mStroke.kind.IsNone()) { flags |= SVG_HIT_TEST_STROKE; } } break; case StylePointerEvents::Visiblefill: if (aFrame->StyleVisibility()->IsVisible()) { flags = SVG_HIT_TEST_FILL; } break; case StylePointerEvents::Visiblestroke: if (aFrame->StyleVisibility()->IsVisible()) { flags = SVG_HIT_TEST_STROKE; } break; case StylePointerEvents::Visible: if (aFrame->StyleVisibility()->IsVisible()) { flags = SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; } break; case StylePointerEvents::Painted: if (!aFrame->StyleSVG()->mFill.kind.IsNone()) { flags = SVG_HIT_TEST_FILL; } if (!aFrame->StyleSVG()->mStroke.kind.IsNone()) { flags |= SVG_HIT_TEST_STROKE; } break; case StylePointerEvents::Fill: flags = SVG_HIT_TEST_FILL; break; case StylePointerEvents::Stroke: flags = SVG_HIT_TEST_STROKE; break; case StylePointerEvents::All: flags = SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; break; default: NS_ERROR("not reached"); break; } return flags; } void SVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) { nsIFrame* frame = aElement->GetPrimaryFrame(); ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame); if (!svgFrame) { return; } gfxMatrix m; if (frame->GetContent()->IsSVGElement()) { // PaintSVG() expects the passed transform to be the transform to its own // SVG user space, so we need to account for any 'transform' attribute: m = SVGUtils::GetTransformMatrixInUserSpace(frame); } // SVG-in-OpenType is not allowed to paint external resources, so we can // just pass a dummy params into PatintSVG. imgDrawingParams dummy; svgFrame->PaintSVG(*aContext, m, dummy); } bool SVGUtils::GetSVGGlyphExtents(const Element* aElement, const gfxMatrix& aSVGToAppSpace, gfxRect* aResult) { nsIFrame* frame = aElement->GetPrimaryFrame(); ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame); if (!svgFrame) { return false; } gfxMatrix transform(aSVGToAppSpace); if (auto* svg = SVGElement::FromNode(frame->GetContent())) { transform = svg->PrependLocalTransformsTo(aSVGToAppSpace); } *aResult = svgFrame ->GetBBoxContribution(gfx::ToMatrix(transform), SVGUtils::eBBoxIncludeFill | SVGUtils::eBBoxIncludeFillGeometry | SVGUtils::eBBoxIncludeStroke | SVGUtils::eBBoxIncludeStrokeGeometry | SVGUtils::eBBoxIncludeMarkers) .ToThebesRect(); return true; } nsRect SVGUtils::ToCanvasBounds(const gfxRect& aUserspaceRect, const gfxMatrix& aToCanvas, const nsPresContext* presContext) { return nsLayoutUtils::RoundGfxRectToAppRect( aToCanvas.TransformBounds(aUserspaceRect), presContext->AppUnitsPerDevPixel()); } gfxMatrix SVGUtils::GetCSSPxToDevPxMatrix(const nsIFrame* aNonSVGFrame) { float devPxPerCSSPx = aNonSVGFrame->PresContext()->CSSToDevPixelScale().scale; return gfxMatrix(devPxPerCSSPx, 0.0, 0.0, devPxPerCSSPx, 0.0, 0.0); } gfxMatrix SVGUtils::GetTransformMatrixInUserSpace(const nsIFrame* aFrame) { // We check element instead of aFrame directly because SVG element // may have non-SVG frame, for example. MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsSVGElement(), "Only use this wrapper for SVG elements"); if (!aFrame->IsTransformed()) { return {}; } nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame); nsDisplayTransform::FrameTransformProperties properties{ aFrame, refBox, AppUnitsPerCSSPixel()}; // SVG elements can have x/y offset, their default transform origin // is the origin of user space, not the top left point of the frame. Point3D svgTransformOrigin{ properties.mToTransformOrigin.x - CSSPixel::FromAppUnits(refBox.X()), properties.mToTransformOrigin.y - CSSPixel::FromAppUnits(refBox.Y()), properties.mToTransformOrigin.z}; Matrix svgTransform; Matrix4x4 trans; (void)aFrame->IsSVGTransformed(&svgTransform); if (properties.HasTransform()) { trans = nsStyleTransformMatrix::ReadTransforms( properties.mTranslate, properties.mRotate, properties.mScale, properties.mMotion.ptrOr(nullptr), properties.mTransform, refBox, AppUnitsPerCSSPixel()); } else { trans = Matrix4x4::From2D(svgTransform); } trans.ChangeBasis(svgTransformOrigin); Matrix mm; trans.ProjectTo2D(); (void)trans.CanDraw2D(&mm); return ThebesMatrix(mm); } } // namespace mozilla