/* -*- 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 <algorithm>
// 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);
}
// 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<SVGFilterFrame*> filterFrames; if (!aFrame->StyleEffects()->HasFilters() ||
SVGObserverUtils::GetAndObserveFilters(aFrame, &filterFrames) ==
SVGObserverUtils::eHasRefsSomeInvalid) { return aPreFilterRect;
}
bool SVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) {
SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); do { if (outer->IsCallingReflowSVG()) { returntrue;
}
outer = GetOuterSVGFrame(outer->GetParent());
} while (outer); returnfalse;
}
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-<svg> // 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<SVGOuterSVGFrame*>(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!");
}
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;
}
// The matrix that GetBBox accepts should operate on "user space", // i.e. with CSS pixel unit.
m.PreScale(devPixelPerCSSPixel, devPixelPerCSSPixel);
auto initPosition = gfxPoint(
NSAppUnitsToFloatPixels(aFrame->GetPosition().x, AppUnitsPerCSSPixel()),
NSAppUnitsToFloatPixels(aFrame->GetPosition().y, AppUnitsPerCSSPixel()));
// Both SVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor // will count this displacement, we should remove it here to avoid // double-counting.
m.PreTranslate(-initPosition);
bool SVGUtils::GetParentSVGTransforms(const nsIFrame* aFrame,
gfx::Matrix* aFromParentTransform) {
MOZ_ASSERT(aFrame->HasAllStateBits(NS_FRAME_SVG_LAYOUT |
NS_FRAME_MAY_BE_TRANSFORMED), "Expecting an SVG frame that can be transformed"); if (SVGContainerFrame* parent = do_QueryFrame(aFrame->GetParent())) { return parent->HasChildrenOnlyTransform(aFromParentTransform);
} returnfalse;
}
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. <clipPath>, <mask> // in case they have child frames with transformation matrices if (kid->IsSVGFrame()) {
NotifyChildrenOfSVGChange(kid, aFlags);
}
}
}
}
// 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;
}
gfxContextAutoSaveRestore save(mSourceCtx);
mSourceCtx->SetMatrix(Matrix()); // This will be restored right after. auto pattern = MakeRefPtr<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();
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;
} if (aFrame->IsSVGSymbolFrame() && !svg->IsInSVGUseShadowTree()) { 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<SVGMaskFrame*> maskFrames;
nsTArray<SVGFilterFrame*> filterFrames; constbool hasInvalidFilter =
SVGObserverUtils::GetAndObserveFilters(aFrame, &filterFrames) ==
SVGObserverUtils::eHasRefsSomeInvalid;
SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame);
SVGObserverUtils::GetAndObserveMasks(aFrame, &maskFrames);
/* 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<SourceSurface> 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);
if (!maskSurface) { // Either entire surface is clipped out, or gfx buffer allocation // failure in SVGMaskFrame::GetMaskForMaskedFrame. return;
}
shouldPushMask = true;
}
if (maskUsage.ShouldGenerateClipMaskLayer()) {
RefPtr<SourceSurface> 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<bool> 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()) { returntrue;
} 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);
}
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); constbool 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();
}
constbool 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"); auto* element = static_cast<SVGElement*>(aFrame->GetContent());
matrix = element->ChildToUserSpaceTransform() * matrix;
}
gfxRect bbox =
svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect(); // Account for 'clipped'. if (aFlags & SVGUtils::eBBoxIncludeClipped) {
gfxRect clipRect;
gfxRect fillBBox =
svg->GetBBoxContribution({}, SVGUtils::eBBoxIncludeFill).ToThebesRect(); // XXX Should probably check for overflow: clip too. bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow(); if (hasClip) {
clipRect = SVGUtils::GetClipRectForFrame(aFrame, 0.0f, 0.0f,
fillBBox.width, fillBBox.height);
clipRect.MoveBy(fillBBox.TopLeft()); if (aFrame->IsSVGForeignObjectFrame() || aFrame->IsSVGUseFrame()) {
clipRect = matrix.TransformBounds(clipRect);
}
}
SVGClipPathFrame* clipPathFrame; if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) ==
SVGObserverUtils::eHasRefsSomeInvalid) {
bbox = gfxRect();
} else { if (clipPathFrame) {
SVGClipPathElement* clipContent = static_cast<SVGClipPathElement*>(clipPathFrame->GetContent()); if (clipContent->IsUnitsObjectBoundingBox()) {
matrix.PreTranslate(fillBBox.TopLeft());
matrix.PreScale(fillBBox.width, fillBBox.height);
} elseif (aFrame->IsSVGForeignObjectFrame()) {
matrix = gfxMatrix();
}
matrix *= SVGUtils::GetTransformMatrixInUserSpace(clipPathFrame);
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<SVGElement*>(aFrame->GetContent())
->ChildToUserSpaceTransform();
NS_ASSERTION(!transform.HasNonTranslation(), "we're relying on this being an offset-only transform"); return transform.GetTranslation();
}
// 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);
/* static */
nscolor SVGUtils::GetFallbackOrPaintColor( const ComputedStyle& aStyle, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
nscolor aDefaultContextFallbackColor) { constauto& 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 (constauto* styleIfVisited = aStyle.GetStyleIfVisited()) { constauto& 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;
}
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;
}
if (ps) {
RefPtr<gfxPattern> 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<gfxPattern> 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));
}
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;
}
if (aContextPaint) {
RefPtr<gfxPattern> 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;
}
// SVGContentUtils::GetStrokeOptions gets the stroke options in CSS px; // convert to device pixels for gfxContext. float devPxPerCSSPx = aFrame->PresContext()->CSSToDevPixelScale().scale;
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);
}
gfxMatrix SVGUtils::GetTransformMatrixInUserSpace(const nsIFrame* aFrame) { // We check element instead of aFrame directly because SVG element // may have non-SVG frame, <tspan> for example.
MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsSVGElement(), "Only use this wrapper for SVG elements");
// 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; if (properties.HasTransform()) {
trans = nsStyleTransformMatrix::ReadTransforms(
properties.mTranslate, properties.mRotate, properties.mScale,
properties.mMotion.ptrOr(nullptr), properties.mTransform, refBox,
AppUnitsPerCSSPixel());
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.