1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "SVGGeometryElement.h"
9 #include "DOMSVGPoint.h"
10 #include "gfxPlatform.h"
12 #include "SVGAnimatedLength.h"
13 #include "SVGCircleElement.h"
14 #include "SVGEllipseElement.h"
15 #include "SVGGeometryProperty.h"
16 #include "SVGRectElement.h"
17 #include "mozilla/dom/DOMPointBinding.h"
18 #include "mozilla/dom/SVGLengthBinding.h"
19 #include "mozilla/gfx/2D.h"
20 #include "mozilla/RefPtr.h"
21 #include "mozilla/SVGContentUtils.h"
23 using namespace mozilla::gfx
;
28 SVGElement::NumberInfo
SVGGeometryElement::sNumberInfo
= {nsGkAtoms::pathLength
,
31 //----------------------------------------------------------------------
34 SVGGeometryElement::SVGGeometryElement(
35 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
36 : SVGGeometryElementBase(std::move(aNodeInfo
)) {}
38 SVGElement::NumberAttributesInfo
SVGGeometryElement::GetNumberInfo() {
39 return NumberAttributesInfo(&mPathLength
, &sNumberInfo
, 1);
42 nsresult
SVGGeometryElement::AfterSetAttr(int32_t aNamespaceID
, nsAtom
* aName
,
43 const nsAttrValue
* aValue
,
44 const nsAttrValue
* aOldValue
,
45 nsIPrincipal
* aSubjectPrincipal
,
47 if (mCachedPath
&& aNamespaceID
== kNameSpaceID_None
&&
48 AttributeDefinesGeometry(aName
)) {
49 mCachedPath
= nullptr;
51 return SVGGeometryElementBase::AfterSetAttr(
52 aNamespaceID
, aName
, aValue
, aOldValue
, aSubjectPrincipal
, aNotify
);
55 bool SVGGeometryElement::IsNodeOfType(uint32_t aFlags
) const {
56 return !(aFlags
& ~(eSHAPE
| eUSE_TARGET
));
59 bool SVGGeometryElement::AttributeDefinesGeometry(const nsAtom
* aName
) {
60 if (aName
== nsGkAtoms::pathLength
) {
64 // Check for SVGAnimatedLength attribute
65 LengthAttributesInfo info
= GetLengthInfo();
66 for (uint32_t i
= 0; i
< info
.mLengthCount
; i
++) {
67 if (aName
== info
.mLengthInfo
[i
].mName
) {
75 bool SVGGeometryElement::GeometryDependsOnCoordCtx() {
76 // Check the SVGAnimatedLength attribute
77 LengthAttributesInfo info
=
78 const_cast<SVGGeometryElement
*>(this)->GetLengthInfo();
79 for (uint32_t i
= 0; i
< info
.mLengthCount
; i
++) {
80 if (info
.mLengths
[i
].GetSpecifiedUnitType() ==
81 SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE
) {
88 bool SVGGeometryElement::IsMarkable() { return false; }
90 void SVGGeometryElement::GetMarkPoints(nsTArray
<SVGMark
>* aMarks
) {}
92 already_AddRefed
<Path
> SVGGeometryElement::GetOrBuildPath(
93 const DrawTarget
* aDrawTarget
, FillRule aFillRule
) {
94 // We only cache the path if it matches the backend used for screen painting,
95 // and it's not a capturing drawtarget. A capturing DT might start using the
96 // the Path object on a different thread (OMTP), and we might have a data race
97 // if we keep a handle to it.
98 bool cacheable
= (aDrawTarget
->GetBackendType() ==
99 gfxPlatform::GetPlatform()->GetDefaultContentBackend()) &&
100 !aDrawTarget
->IsCaptureDT();
102 if (cacheable
&& mCachedPath
&& mCachedPath
->GetFillRule() == aFillRule
&&
103 aDrawTarget
->GetBackendType() == mCachedPath
->GetBackendType()) {
104 RefPtr
<Path
> path(mCachedPath
);
105 return path
.forget();
107 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder(aFillRule
);
108 RefPtr
<Path
> path
= BuildPath(builder
);
112 return path
.forget();
115 already_AddRefed
<Path
> SVGGeometryElement::GetOrBuildPathForMeasuring() {
116 RefPtr
<DrawTarget
> drawTarget
=
117 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
118 FillRule fillRule
= mCachedPath
? mCachedPath
->GetFillRule() : GetFillRule();
119 return GetOrBuildPath(drawTarget
, fillRule
);
122 // This helper is currently identical to GetOrBuildPathForMeasuring.
123 // We keep it a separate method because for length measuring purpose,
124 // fillRule isn't really needed. Derived class (e.g. SVGPathElement)
125 // may override GetOrBuildPathForMeasuring() to ignore fillRule. And
126 // GetOrBuildPathForMeasuring() itself may be modified in the future.
127 already_AddRefed
<Path
> SVGGeometryElement::GetOrBuildPathForHitTest() {
128 RefPtr
<DrawTarget
> drawTarget
=
129 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
130 FillRule fillRule
= mCachedPath
? mCachedPath
->GetFillRule() : GetFillRule();
131 return GetOrBuildPath(drawTarget
, fillRule
);
134 bool SVGGeometryElement::IsGeometryChangedViaCSS(
135 ComputedStyle
const& aNewStyle
, ComputedStyle
const& aOldStyle
) const {
136 nsAtom
* name
= NodeInfo()->NameAtom();
137 if (name
== nsGkAtoms::rect
) {
138 return SVGRectElement::IsLengthChangedViaCSS(aNewStyle
, aOldStyle
);
140 if (name
== nsGkAtoms::circle
) {
141 return SVGCircleElement::IsLengthChangedViaCSS(aNewStyle
, aOldStyle
);
143 if (name
== nsGkAtoms::ellipse
) {
144 return SVGEllipseElement::IsLengthChangedViaCSS(aNewStyle
, aOldStyle
);
149 FillRule
SVGGeometryElement::GetFillRule() {
151 FillRule::FILL_WINDING
; // Equivalent to StyleFillRule::Nonzero
153 bool res
= SVGGeometryProperty::DoForComputedStyle(
154 this, [&](const ComputedStyle
* s
) {
155 const auto* styleSVG
= s
->StyleSVG();
157 MOZ_ASSERT(styleSVG
->mFillRule
== StyleFillRule::Nonzero
||
158 styleSVG
->mFillRule
== StyleFillRule::Evenodd
);
160 if (styleSVG
->mFillRule
== StyleFillRule::Evenodd
) {
161 fillRule
= FillRule::FILL_EVEN_ODD
;
166 NS_WARNING("Couldn't get ComputedStyle for content in GetFillRule");
172 static Point
GetPointFrom(const DOMPointInit
& aPoint
) {
173 return Point(aPoint
.mX
, aPoint
.mY
);
176 bool SVGGeometryElement::IsPointInFill(const DOMPointInit
& aPoint
) {
177 auto point
= GetPointFrom(aPoint
);
179 RefPtr
<Path
> path
= GetOrBuildPathForHitTest();
184 return path
->ContainsPoint(point
, {});
187 bool SVGGeometryElement::IsPointInStroke(const DOMPointInit
& aPoint
) {
188 auto point
= GetPointFrom(aPoint
);
190 RefPtr
<Path
> path
= GetOrBuildPathForHitTest();
194 if (nsCOMPtr
<Document
> doc
= GetComposedDoc()) {
195 doc
->FlushPendingNotifications(FlushType::Layout
);
199 SVGGeometryProperty::DoForComputedStyle(this, [&](const ComputedStyle
* s
) {
200 // Per spec, we should take vector-effect into account.
201 if (s
->StyleSVGReset()->HasNonScalingStroke()) {
202 auto mat
= SVGContentUtils::GetCTM(this, true);
203 if (mat
.HasNonTranslation()) {
204 // We have non-scaling-stroke as well as a non-translation transform.
205 // We should transform the path first then apply the stroke on the
206 // transformed path to preserve the stroke-width.
207 RefPtr
<PathBuilder
> builder
= path
->TransformedCopyToBuilder(mat
);
209 path
= builder
->Finish();
210 point
= mat
.TransformPoint(point
);
214 SVGContentUtils::AutoStrokeOptions strokeOptions
;
215 SVGContentUtils::GetStrokeOptions(&strokeOptions
, this, s
, nullptr);
217 res
= path
->StrokeContainsPoint(strokeOptions
, point
, {});
223 float SVGGeometryElement::GetTotalLength() {
224 RefPtr
<Path
> flat
= GetOrBuildPathForMeasuring();
225 return flat
? flat
->ComputeLength() : 0.f
;
228 already_AddRefed
<DOMSVGPoint
> SVGGeometryElement::GetPointAtLength(
229 float distance
, ErrorResult
& rv
) {
230 RefPtr
<Path
> path
= GetOrBuildPathForMeasuring();
232 rv
.ThrowInvalidStateError("No path available for measuring");
236 RefPtr
<DOMSVGPoint
> point
= new DOMSVGPoint(path
->ComputePointAtLength(
237 clamped(distance
, 0.f
, path
->ComputeLength())));
238 return point
.forget();
241 float SVGGeometryElement::GetPathLengthScale(PathLengthScaleForType aFor
) {
242 MOZ_ASSERT(aFor
== eForTextPath
|| aFor
== eForStroking
, "Unknown enum");
243 if (mPathLength
.IsExplicitlySet()) {
244 float authorsPathLengthEstimate
= mPathLength
.GetAnimValue();
245 if (authorsPathLengthEstimate
> 0) {
246 RefPtr
<Path
> path
= GetOrBuildPathForMeasuring();
248 // The path is empty or invalid so its length must be zero and
249 // we know that 0 / authorsPathLengthEstimate = 0.
252 if (aFor
== eForTextPath
) {
253 // For textPath, a transform on the referenced path affects the
254 // textPath layout, so when calculating the actual path length
255 // we need to take that into account.
256 gfxMatrix matrix
= PrependLocalTransformsTo(gfxMatrix());
257 if (!matrix
.IsIdentity()) {
258 RefPtr
<PathBuilder
> builder
=
259 path
->TransformedCopyToBuilder(ToMatrix(matrix
));
260 path
= builder
->Finish();
263 return path
->ComputeLength() / authorsPathLengthEstimate
;
269 already_AddRefed
<DOMSVGAnimatedNumber
> SVGGeometryElement::PathLength() {
270 return mPathLength
.ToDOMAnimatedNumber(this);
274 } // namespace mozilla