summaryrefslogtreecommitdiffstats
path: root/layout/svg/SVGPatternFrame.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /layout/svg/SVGPatternFrame.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/svg/SVGPatternFrame.cpp')
-rw-r--r--layout/svg/SVGPatternFrame.cpp713
1 files changed, 713 insertions, 0 deletions
diff --git a/layout/svg/SVGPatternFrame.cpp b/layout/svg/SVGPatternFrame.cpp
new file mode 100644
index 0000000000..ea93daef41
--- /dev/null
+++ b/layout/svg/SVGPatternFrame.cpp
@@ -0,0 +1,713 @@
+/* -*- 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:
+#include "SVGPatternFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "AutoReferenceChainGuard.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxMatrix.h"
+#include "gfxPattern.h"
+#include "gfxPlatform.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/ISVGDisplayableFrame.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGContentUtils.h"
+#include "mozilla/SVGGeometryFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/SVGPatternElement.h"
+#include "mozilla/dom/SVGUnitTypesBinding.h"
+#include "mozilla/gfx/2D.h"
+#include "nsGkAtoms.h"
+#include "nsIFrameInlines.h"
+#include "SVGAnimatedTransformList.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::SVGUnitTypes_Binding;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+namespace mozilla {
+
+//----------------------------------------------------------------------
+// Implementation
+
+SVGPatternFrame::SVGPatternFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : SVGPaintServerFrame(aStyle, aPresContext, kClassID),
+ mSource(nullptr),
+ mLoopFlag(false),
+ mNoHRefURI(false) {}
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGPatternFrame)
+
+NS_QUERYFRAME_HEAD(SVGPatternFrame)
+ NS_QUERYFRAME_ENTRY(SVGPatternFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SVGPaintServerFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+nsresult SVGPatternFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::patternUnits ||
+ aAttribute == nsGkAtoms::patternContentUnits ||
+ aAttribute == nsGkAtoms::patternTransform ||
+ aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y ||
+ aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
+ aAttribute == nsGkAtoms::preserveAspectRatio ||
+ aAttribute == nsGkAtoms::viewBox)) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ }
+
+ if ((aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None) &&
+ aAttribute == nsGkAtoms::href) {
+ // Blow away our reference, if any
+ SVGObserverUtils::RemoveTemplateObserver(this);
+ mNoHRefURI = false;
+ // And update whoever references us
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ }
+
+ return SVGPaintServerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+#ifdef DEBUG
+void SVGPatternFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::pattern),
+ "Content is not an SVG pattern");
+
+ SVGPaintServerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+//----------------------------------------------------------------------
+// SVGContainerFrame methods:
+
+// If our GetCanvasTM is getting called, we
+// need to return *our current* transformation
+// matrix, which depends on our units parameters
+// and X, Y, Width, and Height
+gfxMatrix SVGPatternFrame::GetCanvasTM() {
+ if (mCTM) {
+ return *mCTM;
+ }
+
+ // Do we know our rendering parent?
+ if (mSource) {
+ // Yes, use it!
+ return mSource->GetCanvasTM();
+ }
+
+ // We get here when geometry in the <pattern> container is updated
+ return gfxMatrix();
+}
+
+// -------------------------------------------------------------------------
+// Helper functions
+// -------------------------------------------------------------------------
+
+/** Calculate the maximum expansion of a matrix */
+static float MaxExpansion(const Matrix& aMatrix) {
+ // maximum expansion derivation from
+ // http://lists.cairographics.org/archives/cairo/2004-October/001980.html
+ // and also implemented in cairo_matrix_transformed_circle_major_axis
+ double a = aMatrix._11;
+ double b = aMatrix._12;
+ double c = aMatrix._21;
+ double d = aMatrix._22;
+ double f = (a * a + b * b + c * c + d * d) / 2;
+ double g = (a * a + b * b - c * c - d * d) / 2;
+ double h = a * c + b * d;
+ return sqrt(f + sqrt(g * g + h * h));
+}
+
+// The SVG specification says that the 'patternContentUnits' attribute "has no
+// effect if attribute ‘viewBox’ is specified". We still need to include a bbox
+// scale if the viewBox is specified and _patternUnits_ is set to or defaults to
+// objectBoundingBox though, since in that case the viewBox is relative to the
+// bbox
+static bool IncludeBBoxScale(const SVGAnimatedViewBox& aViewBox,
+ uint32_t aPatternContentUnits,
+ uint32_t aPatternUnits) {
+ return (!aViewBox.IsExplicitlySet() &&
+ aPatternContentUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) ||
+ (aViewBox.IsExplicitlySet() &&
+ aPatternUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX);
+}
+
+// Given the matrix for the pattern element's own transform, this returns a
+// combined matrix including the transforms applicable to its target.
+static Matrix GetPatternMatrix(nsIFrame* aSource,
+ const StyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ uint16_t aPatternUnits,
+ const gfxMatrix& patternTransform,
+ const gfxRect& bbox, const gfxRect& callerBBox,
+ const Matrix& callerCTM) {
+ // We really want the pattern matrix to handle translations
+ gfxFloat minx = bbox.X();
+ gfxFloat miny = bbox.Y();
+
+ if (aPatternUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ minx += callerBBox.X();
+ miny += callerBBox.Y();
+ }
+
+ double scale = 1.0 / MaxExpansion(callerCTM);
+ auto patternMatrix = patternTransform;
+ patternMatrix.PreScale(scale, scale);
+ patternMatrix.PreTranslate(minx, miny);
+
+ // revert the vector effect transform so that the pattern appears unchanged
+ if (aFillOrStroke == &nsStyleSVG::mStroke) {
+ gfxMatrix userToOuterSVG;
+ if (SVGUtils::GetNonScalingStrokeTransform(aSource, &userToOuterSVG)) {
+ patternMatrix *= userToOuterSVG;
+ }
+ }
+
+ return ToMatrix(patternMatrix);
+}
+
+static nsresult GetTargetGeometry(gfxRect* aBBox,
+ const SVGAnimatedViewBox& aViewBox,
+ uint16_t aPatternContentUnits,
+ uint16_t aPatternUnits, nsIFrame* aTarget,
+ const Matrix& aContextMatrix,
+ const gfxRect* aOverrideBounds) {
+ *aBBox =
+ aOverrideBounds
+ ? *aOverrideBounds
+ : SVGUtils::GetBBox(aTarget, SVGUtils::eUseFrameBoundsForOuterSVG |
+ SVGUtils::eBBoxIncludeFillGeometry);
+
+ // Sanity check
+ if (IncludeBBoxScale(aViewBox, aPatternContentUnits, aPatternUnits) &&
+ (aBBox->Width() <= 0 || aBBox->Height() <= 0)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // OK, now fix up the bounding box to reflect user coordinates
+ // We handle device unit scaling in pattern matrix
+ float scale = MaxExpansion(aContextMatrix);
+ if (scale <= 0) {
+ return NS_ERROR_FAILURE;
+ }
+ aBBox->Scale(scale);
+ return NS_OK;
+}
+
+void SVGPatternFrame::PaintChildren(DrawTarget* aDrawTarget,
+ SVGPatternFrame* aPatternWithChildren,
+ nsIFrame* aSource, float aGraphicOpacity,
+ imgDrawingParams& aImgParams) {
+ nsIFrame* firstKid = aPatternWithChildren->mFrames.FirstChild();
+
+ gfxContext ctx(aDrawTarget);
+ gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&ctx);
+
+ if (aGraphicOpacity != 1.0f) {
+ autoGroupForBlend.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
+ aGraphicOpacity);
+ }
+
+ // OK, now render -- note that we use "firstKid", which
+ // we got at the beginning because it takes care of the
+ // referenced pattern situation for us
+
+ if (aSource->IsSVGGeometryFrame()) {
+ // Set the geometrical parent of the pattern we are rendering
+ aPatternWithChildren->mSource = static_cast<SVGGeometryFrame*>(aSource);
+ }
+
+ // Delay checking NS_FRAME_DRAWING_AS_PAINTSERVER bit until here so we can
+ // give back a clear surface if there's a loop
+ if (!aPatternWithChildren->HasAnyStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER)) {
+ AutoSetRestorePaintServerState paintServer(aPatternWithChildren);
+ for (nsIFrame* kid = firstKid; kid; kid = kid->GetNextSibling()) {
+ gfxMatrix tm = *(aPatternWithChildren->mCTM);
+
+ // The CTM of each frame referencing us can be different
+ ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ SVGFrame->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED);
+ tm = SVGUtils::GetTransformMatrixInUserSpace(kid) * tm;
+ }
+
+ SVGUtils::PaintFrameWithEffects(kid, ctx, tm, aImgParams);
+ }
+ }
+
+ aPatternWithChildren->mSource = nullptr;
+}
+
+already_AddRefed<SourceSurface> SVGPatternFrame::PaintPattern(
+ const DrawTarget* aDrawTarget, Matrix* patternMatrix,
+ const Matrix& aContextMatrix, nsIFrame* aSource,
+ StyleSVGPaint nsStyleSVG::*aFillOrStroke, float aGraphicOpacity,
+ const gfxRect* aOverrideBounds, imgDrawingParams& aImgParams) {
+ /*
+ * General approach:
+ * Set the content geometry stuff
+ * Calculate our bbox (using x,y,width,height & patternUnits &
+ * patternTransform)
+ * Create the surface
+ * Calculate the content transformation matrix
+ * Get our children (we may need to get them from another Pattern)
+ * Call SVGPaint on all of our children
+ * Return
+ */
+
+ SVGPatternFrame* patternWithChildren = GetPatternWithChildren();
+ if (!patternWithChildren) {
+ // Either no kids or a bad reference
+ return nullptr;
+ }
+
+ const SVGAnimatedViewBox& viewBox = GetViewBox();
+
+ uint16_t patternContentUnits =
+ GetEnumValue(SVGPatternElement::PATTERNCONTENTUNITS);
+ uint16_t patternUnits = GetEnumValue(SVGPatternElement::PATTERNUNITS);
+
+ /*
+ * Get the content geometry information. This is a little tricky --
+ * our parent is probably a <defs>, but we are rendering in the context
+ * of some geometry source. Our content geometry information needs to
+ * come from our rendering parent as opposed to our content parent. We
+ * get that information from aSource, which is passed to us from the
+ * backend renderer.
+ *
+ * There are three "geometries" that we need:
+ * 1) The bounding box for the pattern. We use this to get the
+ * width and height for the surface, and as the return to
+ * GetBBox.
+ * 2) The transformation matrix for the pattern. This is not *quite*
+ * the same as the canvas transformation matrix that we will
+ * provide to our rendering children since we "fudge" it a little
+ * to get the renderer to handle the translations correctly for us.
+ * 3) The CTM that we return to our children who make up the pattern.
+ */
+
+ // Get all of the information we need from our "caller" -- i.e.
+ // the geometry that is being rendered with a pattern
+ gfxRect callerBBox;
+ if (NS_FAILED(GetTargetGeometry(&callerBBox, viewBox, patternContentUnits,
+ patternUnits, aSource, aContextMatrix,
+ aOverrideBounds))) {
+ return nullptr;
+ }
+
+ // Construct the CTM that we will provide to our children when we
+ // render them into the tile.
+ gfxMatrix ctm = ConstructCTM(viewBox, patternContentUnits, patternUnits,
+ callerBBox, aContextMatrix, aSource);
+ if (ctm.IsSingular()) {
+ return nullptr;
+ }
+
+ if (patternWithChildren->mCTM) {
+ *patternWithChildren->mCTM = ctm;
+ } else {
+ patternWithChildren->mCTM = MakeUnique<gfxMatrix>(ctm);
+ }
+
+ // Get the bounding box of the pattern. This will be used to determine
+ // the size of the surface, and will also be used to define the bounding
+ // box for the pattern tile.
+ gfxRect bbox =
+ GetPatternRect(patternUnits, callerBBox, aContextMatrix, aSource);
+ if (bbox.Width() <= 0.0 || bbox.Height() <= 0.0) {
+ return nullptr;
+ }
+
+ // Get the pattern transform
+ auto patternTransform = GetPatternTransform();
+
+ // Get the transformation matrix that we will hand to the renderer's pattern
+ // routine.
+ *patternMatrix =
+ GetPatternMatrix(aSource, aFillOrStroke, patternUnits, patternTransform,
+ bbox, callerBBox, aContextMatrix);
+ if (patternMatrix->IsSingular()) {
+ return nullptr;
+ }
+
+ // Now that we have all of the necessary geometries, we can
+ // create our surface.
+ gfxSize scaledSize = bbox.Size() * MaxExpansion(ToMatrix(patternTransform));
+
+ bool resultOverflows;
+ IntSize surfaceSize =
+ SVGUtils::ConvertToSurfaceSize(scaledSize, &resultOverflows);
+
+ // 0 disables rendering, < 0 is an error
+ if (surfaceSize.width <= 0 || surfaceSize.height <= 0) {
+ return nullptr;
+ }
+
+ gfxFloat patternWidth = bbox.Width();
+ gfxFloat patternHeight = bbox.Height();
+
+ if (resultOverflows || patternWidth != surfaceSize.width ||
+ patternHeight != surfaceSize.height) {
+ // scale drawing to pattern surface size
+ patternWithChildren->mCTM->PostScale(surfaceSize.width / patternWidth,
+ surfaceSize.height / patternHeight);
+
+ // and rescale pattern to compensate
+ patternMatrix->PreScale(patternWidth / surfaceSize.width,
+ patternHeight / surfaceSize.height);
+ }
+
+ RefPtr<DrawTarget> dt = aDrawTarget->CreateSimilarDrawTargetWithBacking(
+ surfaceSize, SurfaceFormat::B8G8R8A8);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ dt->ClearRect(Rect(0, 0, surfaceSize.width, surfaceSize.height));
+
+ PaintChildren(dt, patternWithChildren, aSource, aGraphicOpacity, aImgParams);
+
+ // caller now owns the surface
+ return dt->GetBackingSurface();
+}
+
+/* Will probably need something like this... */
+// How do we handle the insertion of a new frame?
+// We really don't want to rerender this every time,
+// do we?
+SVGPatternFrame* SVGPatternFrame::GetPatternWithChildren() {
+ // Do we have any children ourselves?
+ if (!mFrames.IsEmpty()) {
+ return this;
+ }
+
+ // No, see if we chain to someone who does
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return nullptr;
+ }
+
+ SVGPatternFrame* next = GetReferencedPattern();
+ if (!next) {
+ return nullptr;
+ }
+
+ return next->GetPatternWithChildren();
+}
+
+uint16_t SVGPatternFrame::GetEnumValue(uint32_t aIndex, nsIContent* aDefault) {
+ SVGAnimatedEnumeration& thisEnum =
+ static_cast<SVGPatternElement*>(GetContent())->mEnumAttributes[aIndex];
+
+ if (thisEnum.IsExplicitlySet()) {
+ return thisEnum.GetAnimValue();
+ }
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return static_cast<SVGPatternElement*>(aDefault)
+ ->mEnumAttributes[aIndex]
+ .GetAnimValue();
+ }
+
+ SVGPatternFrame* next = GetReferencedPattern();
+ return next ? next->GetEnumValue(aIndex, aDefault)
+ : static_cast<SVGPatternElement*>(aDefault)
+ ->mEnumAttributes[aIndex]
+ .GetAnimValue();
+}
+
+SVGAnimatedTransformList* SVGPatternFrame::GetPatternTransformList(
+ nsIContent* aDefault) {
+ SVGAnimatedTransformList* thisTransformList =
+ static_cast<SVGPatternElement*>(GetContent())->GetAnimatedTransformList();
+
+ if (thisTransformList && thisTransformList->IsExplicitlySet())
+ return thisTransformList;
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return static_cast<SVGPatternElement*>(aDefault)->mPatternTransform.get();
+ }
+
+ SVGPatternFrame* next = GetReferencedPattern();
+ return next ? next->GetPatternTransformList(aDefault)
+ : static_cast<SVGPatternElement*>(aDefault)
+ ->mPatternTransform.get();
+}
+
+gfxMatrix SVGPatternFrame::GetPatternTransform() {
+ SVGAnimatedTransformList* animTransformList =
+ GetPatternTransformList(GetContent());
+ if (!animTransformList) {
+ return gfxMatrix();
+ }
+
+ return animTransformList->GetAnimValue().GetConsolidationMatrix();
+}
+
+const SVGAnimatedViewBox& SVGPatternFrame::GetViewBox(nsIContent* aDefault) {
+ const SVGAnimatedViewBox& thisViewBox =
+ static_cast<SVGPatternElement*>(GetContent())->mViewBox;
+
+ if (thisViewBox.IsExplicitlySet()) {
+ return thisViewBox;
+ }
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return static_cast<SVGPatternElement*>(aDefault)->mViewBox;
+ }
+
+ SVGPatternFrame* next = GetReferencedPattern();
+ return next ? next->GetViewBox(aDefault)
+ : static_cast<SVGPatternElement*>(aDefault)->mViewBox;
+}
+
+const SVGAnimatedPreserveAspectRatio& SVGPatternFrame::GetPreserveAspectRatio(
+ nsIContent* aDefault) {
+ const SVGAnimatedPreserveAspectRatio& thisPar =
+ static_cast<SVGPatternElement*>(GetContent())->mPreserveAspectRatio;
+
+ if (thisPar.IsExplicitlySet()) {
+ return thisPar;
+ }
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return static_cast<SVGPatternElement*>(aDefault)->mPreserveAspectRatio;
+ }
+
+ SVGPatternFrame* next = GetReferencedPattern();
+ return next ? next->GetPreserveAspectRatio(aDefault)
+ : static_cast<SVGPatternElement*>(aDefault)->mPreserveAspectRatio;
+}
+
+const SVGAnimatedLength* SVGPatternFrame::GetLengthValue(uint32_t aIndex,
+ nsIContent* aDefault) {
+ const SVGAnimatedLength* thisLength =
+ &static_cast<SVGPatternElement*>(GetContent())->mLengthAttributes[aIndex];
+
+ if (thisLength->IsExplicitlySet()) {
+ return thisLength;
+ }
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return &static_cast<SVGPatternElement*>(aDefault)
+ ->mLengthAttributes[aIndex];
+ }
+
+ SVGPatternFrame* next = GetReferencedPattern();
+ return next ? next->GetLengthValue(aIndex, aDefault)
+ : &static_cast<SVGPatternElement*>(aDefault)
+ ->mLengthAttributes[aIndex];
+}
+
+// Private (helper) methods
+
+SVGPatternFrame* SVGPatternFrame::GetReferencedPattern() {
+ if (mNoHRefURI) {
+ return nullptr;
+ }
+
+ auto GetHref = [this](nsAString& aHref) {
+ SVGPatternElement* pattern =
+ static_cast<SVGPatternElement*>(this->GetContent());
+ if (pattern->mStringAttributes[SVGPatternElement::HREF].IsExplicitlySet()) {
+ pattern->mStringAttributes[SVGPatternElement::HREF].GetAnimValue(aHref,
+ pattern);
+ } else {
+ pattern->mStringAttributes[SVGPatternElement::XLINK_HREF].GetAnimValue(
+ aHref, pattern);
+ }
+ this->mNoHRefURI = aHref.IsEmpty();
+ };
+
+ // We don't call SVGObserverUtils::RemoveTemplateObserver and set
+ // `mNoHRefURI = false` on failure since we want to be invalidated if the ID
+ // specified by our href starts resolving to a different/valid element.
+
+ return do_QueryFrame(SVGObserverUtils::GetAndObserveTemplate(this, GetHref));
+}
+
+gfxRect SVGPatternFrame::GetPatternRect(uint16_t aPatternUnits,
+ const gfxRect& aTargetBBox,
+ const Matrix& aTargetCTM,
+ nsIFrame* aTarget) {
+ // We need to initialize our box
+ float x, y, width, height;
+
+ // Get the pattern x,y,width, and height
+ const SVGAnimatedLength *tmpX, *tmpY, *tmpHeight, *tmpWidth;
+ tmpX = GetLengthValue(SVGPatternElement::ATTR_X);
+ tmpY = GetLengthValue(SVGPatternElement::ATTR_Y);
+ tmpHeight = GetLengthValue(SVGPatternElement::ATTR_HEIGHT);
+ tmpWidth = GetLengthValue(SVGPatternElement::ATTR_WIDTH);
+
+ if (aPatternUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ x = SVGUtils::ObjectSpace(aTargetBBox, tmpX);
+ y = SVGUtils::ObjectSpace(aTargetBBox, tmpY);
+ width = SVGUtils::ObjectSpace(aTargetBBox, tmpWidth);
+ height = SVGUtils::ObjectSpace(aTargetBBox, tmpHeight);
+ } else {
+ if (aTarget->IsTextFrame()) {
+ aTarget = aTarget->GetParent();
+ }
+ float scale = MaxExpansion(aTargetCTM);
+ x = SVGUtils::UserSpace(aTarget, tmpX) * scale;
+ y = SVGUtils::UserSpace(aTarget, tmpY) * scale;
+ width = SVGUtils::UserSpace(aTarget, tmpWidth) * scale;
+ height = SVGUtils::UserSpace(aTarget, tmpHeight) * scale;
+ }
+
+ return gfxRect(x, y, width, height);
+}
+
+gfxMatrix SVGPatternFrame::ConstructCTM(const SVGAnimatedViewBox& aViewBox,
+ uint16_t aPatternContentUnits,
+ uint16_t aPatternUnits,
+ const gfxRect& callerBBox,
+ const Matrix& callerCTM,
+ nsIFrame* aTarget) {
+ if (aTarget->IsTextFrame()) {
+ aTarget = aTarget->GetParent();
+ }
+ nsIContent* targetContent = aTarget->GetContent();
+ SVGViewportElement* ctx = nullptr;
+ gfxFloat scaleX, scaleY;
+
+ // The objectBoundingBox conversion must be handled in the CTM:
+ if (IncludeBBoxScale(aViewBox, aPatternContentUnits, aPatternUnits)) {
+ scaleX = callerBBox.Width();
+ scaleY = callerBBox.Height();
+ } else {
+ if (targetContent->IsSVGElement()) {
+ ctx = static_cast<SVGElement*>(targetContent)->GetCtx();
+ }
+ scaleX = scaleY = MaxExpansion(callerCTM);
+ }
+
+ if (!aViewBox.IsExplicitlySet()) {
+ return gfxMatrix(scaleX, 0.0, 0.0, scaleY, 0.0, 0.0);
+ }
+ const SVGViewBox& viewBox = aViewBox.GetAnimValue();
+
+ if (viewBox.height <= 0.0f || viewBox.width <= 0.0f) {
+ return gfxMatrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
+ }
+
+ float viewportWidth, viewportHeight;
+ if (targetContent->IsSVGElement()) {
+ // If we're dealing with an SVG target only retrieve the context once.
+ // Calling the nsIFrame* variant of GetAnimValue would look it up on
+ // every call.
+ viewportWidth =
+ GetLengthValue(SVGPatternElement::ATTR_WIDTH)->GetAnimValue(ctx);
+ viewportHeight =
+ GetLengthValue(SVGPatternElement::ATTR_HEIGHT)->GetAnimValue(ctx);
+ } else {
+ // No SVG target, call the nsIFrame* variant of GetAnimValue.
+ viewportWidth =
+ GetLengthValue(SVGPatternElement::ATTR_WIDTH)->GetAnimValue(aTarget);
+ viewportHeight =
+ GetLengthValue(SVGPatternElement::ATTR_HEIGHT)->GetAnimValue(aTarget);
+ }
+
+ if (viewportWidth <= 0.0f || viewportHeight <= 0.0f) {
+ return gfxMatrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
+ }
+
+ Matrix tm = SVGContentUtils::GetViewBoxTransform(
+ viewportWidth * scaleX, viewportHeight * scaleY, viewBox.x, viewBox.y,
+ viewBox.width, viewBox.height, GetPreserveAspectRatio());
+
+ return ThebesMatrix(tm);
+}
+
+//----------------------------------------------------------------------
+// SVGPaintServerFrame methods:
+already_AddRefed<gfxPattern> SVGPatternFrame::GetPaintServerPattern(
+ nsIFrame* aSource, const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aGraphicOpacity, imgDrawingParams& aImgParams,
+ const gfxRect* aOverrideBounds) {
+ if (aGraphicOpacity == 0.0f) {
+ return do_AddRef(new gfxPattern(DeviceColor()));
+ }
+
+ // Paint it!
+ Matrix pMatrix;
+ RefPtr<SourceSurface> surface =
+ PaintPattern(aDrawTarget, &pMatrix, ToMatrix(aContextMatrix), aSource,
+ aFillOrStroke, aGraphicOpacity, aOverrideBounds, aImgParams);
+
+ if (!surface) {
+ return nullptr;
+ }
+
+ RefPtr<gfxPattern> pattern = new gfxPattern(surface, pMatrix);
+
+ if (!pattern) {
+ return nullptr;
+ }
+
+ pattern->SetExtend(ExtendMode::REPEAT);
+ return pattern.forget();
+}
+
+} // namespace mozilla
+
+// -------------------------------------------------------------------------
+// Public functions
+// -------------------------------------------------------------------------
+
+nsIFrame* NS_NewSVGPatternFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGPatternFrame(aStyle, aPresShell->GetPresContext());
+}