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 "mozilla/dom/SVGPathElement.h"
11 #include "DOMSVGPathSeg.h"
12 #include "DOMSVGPathSegList.h"
13 #include "SVGGeometryProperty.h"
14 #include "gfx2DGlue.h"
15 #include "gfxPlatform.h"
16 #include "nsGkAtoms.h"
18 #include "nsStyleConsts.h"
19 #include "nsStyleStruct.h"
20 #include "nsWindowSizes.h"
21 #include "mozilla/dom/SVGPathElementBinding.h"
22 #include "mozilla/gfx/2D.h"
23 #include "mozilla/RefPtr.h"
24 #include "mozilla/StaticPrefs_layout.h"
25 #include "mozilla/SVGContentUtils.h"
27 NS_IMPL_NS_NEW_SVG_ELEMENT(Path
)
29 using namespace mozilla::gfx
;
31 namespace mozilla::dom
{
33 JSObject
* SVGPathElement::WrapNode(JSContext
* aCx
,
34 JS::Handle
<JSObject
*> aGivenProto
) {
35 return SVGPathElement_Binding::Wrap(aCx
, this, aGivenProto
);
38 //----------------------------------------------------------------------
41 SVGPathElement::SVGPathElement(
42 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
43 : SVGPathElementBase(std::move(aNodeInfo
)) {}
45 //----------------------------------------------------------------------
46 // memory reporting methods
48 void SVGPathElement::AddSizeOfExcludingThis(nsWindowSizes
& aSizes
,
49 size_t* aNodeSize
) const {
50 SVGPathElementBase::AddSizeOfExcludingThis(aSizes
, aNodeSize
);
51 *aNodeSize
+= mD
.SizeOfExcludingThis(aSizes
.mState
.mMallocSizeOf
);
54 //----------------------------------------------------------------------
57 NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPathElement
)
59 uint32_t SVGPathElement::GetPathSegAtLength(float distance
) {
61 auto callback
= [&](const ComputedStyle
* s
) {
62 const nsStyleSVGReset
* styleSVGReset
= s
->StyleSVGReset();
63 if (styleSVGReset
->mD
.IsPath()) {
64 seg
= SVGPathData::GetPathSegAtLength(
65 styleSVGReset
->mD
.AsPath()._0
.AsSpan(), distance
);
70 if (SVGGeometryProperty::DoForComputedStyle(this, callback
)) {
73 return mD
.GetAnimValue().GetPathSegAtLength(distance
);
76 already_AddRefed
<DOMSVGPathSegClosePath
>
77 SVGPathElement::CreateSVGPathSegClosePath() {
78 RefPtr
<DOMSVGPathSegClosePath
> pathSeg
= new DOMSVGPathSegClosePath();
79 return pathSeg
.forget();
82 already_AddRefed
<DOMSVGPathSegMovetoAbs
>
83 SVGPathElement::CreateSVGPathSegMovetoAbs(float x
, float y
) {
84 RefPtr
<DOMSVGPathSegMovetoAbs
> pathSeg
= new DOMSVGPathSegMovetoAbs(x
, y
);
85 return pathSeg
.forget();
88 already_AddRefed
<DOMSVGPathSegMovetoRel
>
89 SVGPathElement::CreateSVGPathSegMovetoRel(float x
, float y
) {
90 RefPtr
<DOMSVGPathSegMovetoRel
> pathSeg
= new DOMSVGPathSegMovetoRel(x
, y
);
91 return pathSeg
.forget();
94 already_AddRefed
<DOMSVGPathSegLinetoAbs
>
95 SVGPathElement::CreateSVGPathSegLinetoAbs(float x
, float y
) {
96 RefPtr
<DOMSVGPathSegLinetoAbs
> pathSeg
= new DOMSVGPathSegLinetoAbs(x
, y
);
97 return pathSeg
.forget();
100 already_AddRefed
<DOMSVGPathSegLinetoRel
>
101 SVGPathElement::CreateSVGPathSegLinetoRel(float x
, float y
) {
102 RefPtr
<DOMSVGPathSegLinetoRel
> pathSeg
= new DOMSVGPathSegLinetoRel(x
, y
);
103 return pathSeg
.forget();
106 already_AddRefed
<DOMSVGPathSegCurvetoCubicAbs
>
107 SVGPathElement::CreateSVGPathSegCurvetoCubicAbs(float x
, float y
, float x1
,
108 float y1
, float x2
, float y2
) {
109 // Note that we swap from DOM API argument order to the argument order used
110 // in the <path> element's 'd' attribute (i.e. we put the arguments for the
111 // end point of the segment last instead of first).
112 RefPtr
<DOMSVGPathSegCurvetoCubicAbs
> pathSeg
=
113 new DOMSVGPathSegCurvetoCubicAbs(x1
, y1
, x2
, y2
, x
, y
);
114 return pathSeg
.forget();
117 already_AddRefed
<DOMSVGPathSegCurvetoCubicRel
>
118 SVGPathElement::CreateSVGPathSegCurvetoCubicRel(float x
, float y
, float x1
,
119 float y1
, float x2
, float y2
) {
120 // See comment in CreateSVGPathSegCurvetoCubicAbs
121 RefPtr
<DOMSVGPathSegCurvetoCubicRel
> pathSeg
=
122 new DOMSVGPathSegCurvetoCubicRel(x1
, y1
, x2
, y2
, x
, y
);
123 return pathSeg
.forget();
126 already_AddRefed
<DOMSVGPathSegCurvetoQuadraticAbs
>
127 SVGPathElement::CreateSVGPathSegCurvetoQuadraticAbs(float x
, float y
, float x1
,
129 // See comment in CreateSVGPathSegCurvetoCubicAbs
130 RefPtr
<DOMSVGPathSegCurvetoQuadraticAbs
> pathSeg
=
131 new DOMSVGPathSegCurvetoQuadraticAbs(x1
, y1
, x
, y
);
132 return pathSeg
.forget();
135 already_AddRefed
<DOMSVGPathSegCurvetoQuadraticRel
>
136 SVGPathElement::CreateSVGPathSegCurvetoQuadraticRel(float x
, float y
, float x1
,
138 // See comment in CreateSVGPathSegCurvetoCubicAbs
139 RefPtr
<DOMSVGPathSegCurvetoQuadraticRel
> pathSeg
=
140 new DOMSVGPathSegCurvetoQuadraticRel(x1
, y1
, x
, y
);
141 return pathSeg
.forget();
144 already_AddRefed
<DOMSVGPathSegArcAbs
> SVGPathElement::CreateSVGPathSegArcAbs(
145 float x
, float y
, float r1
, float r2
, float angle
, bool largeArcFlag
,
147 // See comment in CreateSVGPathSegCurvetoCubicAbs
148 RefPtr
<DOMSVGPathSegArcAbs
> pathSeg
=
149 new DOMSVGPathSegArcAbs(r1
, r2
, angle
, largeArcFlag
, sweepFlag
, x
, y
);
150 return pathSeg
.forget();
153 already_AddRefed
<DOMSVGPathSegArcRel
> SVGPathElement::CreateSVGPathSegArcRel(
154 float x
, float y
, float r1
, float r2
, float angle
, bool largeArcFlag
,
156 // See comment in CreateSVGPathSegCurvetoCubicAbs
157 RefPtr
<DOMSVGPathSegArcRel
> pathSeg
=
158 new DOMSVGPathSegArcRel(r1
, r2
, angle
, largeArcFlag
, sweepFlag
, x
, y
);
159 return pathSeg
.forget();
162 already_AddRefed
<DOMSVGPathSegLinetoHorizontalAbs
>
163 SVGPathElement::CreateSVGPathSegLinetoHorizontalAbs(float x
) {
164 RefPtr
<DOMSVGPathSegLinetoHorizontalAbs
> pathSeg
=
165 new DOMSVGPathSegLinetoHorizontalAbs(x
);
166 return pathSeg
.forget();
169 already_AddRefed
<DOMSVGPathSegLinetoHorizontalRel
>
170 SVGPathElement::CreateSVGPathSegLinetoHorizontalRel(float x
) {
171 RefPtr
<DOMSVGPathSegLinetoHorizontalRel
> pathSeg
=
172 new DOMSVGPathSegLinetoHorizontalRel(x
);
173 return pathSeg
.forget();
176 already_AddRefed
<DOMSVGPathSegLinetoVerticalAbs
>
177 SVGPathElement::CreateSVGPathSegLinetoVerticalAbs(float y
) {
178 RefPtr
<DOMSVGPathSegLinetoVerticalAbs
> pathSeg
=
179 new DOMSVGPathSegLinetoVerticalAbs(y
);
180 return pathSeg
.forget();
183 already_AddRefed
<DOMSVGPathSegLinetoVerticalRel
>
184 SVGPathElement::CreateSVGPathSegLinetoVerticalRel(float y
) {
185 RefPtr
<DOMSVGPathSegLinetoVerticalRel
> pathSeg
=
186 new DOMSVGPathSegLinetoVerticalRel(y
);
187 return pathSeg
.forget();
190 already_AddRefed
<DOMSVGPathSegCurvetoCubicSmoothAbs
>
191 SVGPathElement::CreateSVGPathSegCurvetoCubicSmoothAbs(float x
, float y
,
192 float x2
, float y2
) {
193 // See comment in CreateSVGPathSegCurvetoCubicAbs
194 RefPtr
<DOMSVGPathSegCurvetoCubicSmoothAbs
> pathSeg
=
195 new DOMSVGPathSegCurvetoCubicSmoothAbs(x2
, y2
, x
, y
);
196 return pathSeg
.forget();
199 already_AddRefed
<DOMSVGPathSegCurvetoCubicSmoothRel
>
200 SVGPathElement::CreateSVGPathSegCurvetoCubicSmoothRel(float x
, float y
,
201 float x2
, float y2
) {
202 // See comment in CreateSVGPathSegCurvetoCubicAbs
203 RefPtr
<DOMSVGPathSegCurvetoCubicSmoothRel
> pathSeg
=
204 new DOMSVGPathSegCurvetoCubicSmoothRel(x2
, y2
, x
, y
);
205 return pathSeg
.forget();
208 already_AddRefed
<DOMSVGPathSegCurvetoQuadraticSmoothAbs
>
209 SVGPathElement::CreateSVGPathSegCurvetoQuadraticSmoothAbs(float x
, float y
) {
210 RefPtr
<DOMSVGPathSegCurvetoQuadraticSmoothAbs
> pathSeg
=
211 new DOMSVGPathSegCurvetoQuadraticSmoothAbs(x
, y
);
212 return pathSeg
.forget();
215 already_AddRefed
<DOMSVGPathSegCurvetoQuadraticSmoothRel
>
216 SVGPathElement::CreateSVGPathSegCurvetoQuadraticSmoothRel(float x
, float y
) {
217 RefPtr
<DOMSVGPathSegCurvetoQuadraticSmoothRel
> pathSeg
=
218 new DOMSVGPathSegCurvetoQuadraticSmoothRel(x
, y
);
219 return pathSeg
.forget();
222 // FIXME: This API is enabled only if dom.svg.pathSeg.enabled is true. This
223 // preference is off by default in Bug 1388931, and will be dropped later.
224 // So we are not planning to map d property for this API.
225 already_AddRefed
<DOMSVGPathSegList
> SVGPathElement::PathSegList() {
226 return DOMSVGPathSegList::GetDOMWrapper(mD
.GetBaseValKey(), this, false);
229 // FIXME: This API is enabled only if dom.svg.pathSeg.enabled is true. This
230 // preference is off by default in Bug 1388931, and will be dropped later.
231 // So we are not planning to map d property for this API.
232 already_AddRefed
<DOMSVGPathSegList
> SVGPathElement::AnimatedPathSegList() {
233 return DOMSVGPathSegList::GetDOMWrapper(mD
.GetAnimValKey(), this, true);
236 //----------------------------------------------------------------------
237 // SVGElement methods
240 bool SVGPathElement::HasValidDimensions() const {
241 bool hasPath
= false;
242 auto callback
= [&](const ComputedStyle
* s
) {
243 const nsStyleSVGReset
* styleSVGReset
= s
->StyleSVGReset();
245 styleSVGReset
->mD
.IsPath() && !styleSVGReset
->mD
.AsPath()._0
.IsEmpty();
248 SVGGeometryProperty::DoForComputedStyle(this, callback
);
249 // If hasPath is false, we may disable the pref of d property, so we fallback
251 return hasPath
|| !mD
.GetAnimValue().IsEmpty();
254 //----------------------------------------------------------------------
255 // nsIContent methods
258 SVGPathElement::IsAttributeMapped(const nsAtom
* name
) const {
259 return name
== nsGkAtoms::d
|| SVGPathElementBase::IsAttributeMapped(name
);
262 already_AddRefed
<Path
> SVGPathElement::GetOrBuildPathForMeasuring() {
264 bool success
= SVGGeometryProperty::DoForComputedStyle(
265 this, [&path
](const ComputedStyle
* s
) {
266 const auto& d
= s
->StyleSVGReset()->mD
;
270 path
= SVGPathData::BuildPathForMeasuring(d
.AsPath()._0
.AsSpan());
272 return success
? path
.forget() : mD
.GetAnimValue().BuildPathForMeasuring();
275 //----------------------------------------------------------------------
276 // SVGGeometryElement methods
278 bool SVGPathElement::AttributeDefinesGeometry(const nsAtom
* aName
) {
279 return aName
== nsGkAtoms::d
|| aName
== nsGkAtoms::pathLength
;
282 bool SVGPathElement::IsMarkable() { return true; }
284 void SVGPathElement::GetMarkPoints(nsTArray
<SVGMark
>* aMarks
) {
285 auto callback
= [aMarks
](const ComputedStyle
* s
) {
286 const nsStyleSVGReset
* styleSVGReset
= s
->StyleSVGReset();
287 if (styleSVGReset
->mD
.IsPath()) {
288 Span
<const StylePathCommand
> path
=
289 styleSVGReset
->mD
.AsPath()._0
.AsSpan();
290 SVGPathData::GetMarkerPositioningData(path
, aMarks
);
294 if (SVGGeometryProperty::DoForComputedStyle(this, callback
)) {
298 mD
.GetAnimValue().GetMarkerPositioningData(aMarks
);
301 void SVGPathElement::GetAsSimplePath(SimplePath
* aSimplePath
) {
302 aSimplePath
->Reset();
303 auto callback
= [&](const ComputedStyle
* s
) {
304 const nsStyleSVGReset
* styleSVGReset
= s
->StyleSVGReset();
305 if (styleSVGReset
->mD
.IsPath()) {
306 auto pathData
= styleSVGReset
->mD
.AsPath()._0
.AsSpan();
307 auto maybeRect
= SVGPathToAxisAlignedRect(pathData
);
308 if (maybeRect
.isSome()) {
309 Rect r
= maybeRect
.value();
310 aSimplePath
->SetRect(r
.x
, r
.y
, r
.width
, r
.height
);
315 SVGGeometryProperty::DoForComputedStyle(this, callback
);
318 already_AddRefed
<Path
> SVGPathElement::BuildPath(PathBuilder
* aBuilder
) {
319 // The Moz2D PathBuilder that our SVGPathData will be using only cares about
320 // the fill rule. However, in order to fulfill the requirements of the SVG
321 // spec regarding zero length sub-paths when square line caps are in use,
322 // SVGPathData needs to know our stroke-linecap style and, if "square", then
323 // also our stroke width. See the comment for
324 // ApproximateZeroLengthSubpathSquareCaps for more info.
326 auto strokeLineCap
= StyleStrokeLinecap::Butt
;
327 Float strokeWidth
= 0;
330 auto callback
= [&](const ComputedStyle
* s
) {
331 const nsStyleSVG
* styleSVG
= s
->StyleSVG();
332 // Note: the path that we return may be used for hit-testing, and SVG
333 // exposes hit-testing of strokes that are not actually painted. For that
334 // reason we do not check for eStyleSVGPaintType_None or check the stroke
336 if (styleSVG
->mStrokeLinecap
!= StyleStrokeLinecap::Butt
) {
337 strokeLineCap
= styleSVG
->mStrokeLinecap
;
338 strokeWidth
= SVGContentUtils::GetStrokeWidth(this, s
, nullptr);
341 const auto& d
= s
->StyleSVGReset()->mD
;
343 path
= SVGPathData::BuildPath(d
.AsPath()._0
.AsSpan(), aBuilder
,
344 strokeLineCap
, strokeWidth
);
348 bool success
= SVGGeometryProperty::DoForComputedStyle(this, callback
);
350 return path
.forget();
353 // Fallback to use the d attribute if it exists.
354 return mD
.GetAnimValue().BuildPath(aBuilder
, strokeLineCap
, strokeWidth
);
357 bool SVGPathElement::GetDistancesFromOriginToEndsOfVisibleSegments(
358 FallibleTArray
<double>* aOutput
) {
360 auto callback
= [&ret
, aOutput
](const ComputedStyle
* s
) {
361 const auto& d
= s
->StyleSVGReset()->mD
;
363 SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
364 d
.AsPath()._0
.AsSpan(), aOutput
);
367 if (SVGGeometryProperty::DoForComputedStyle(this, callback
)) {
371 return mD
.GetAnimValue().GetDistancesFromOriginToEndsOfVisibleSegments(
376 bool SVGPathElement::IsDPropertyChangedViaCSS(const ComputedStyle
& aNewStyle
,
377 const ComputedStyle
& aOldStyle
) {
378 return aNewStyle
.StyleSVGReset()->mD
!= aOldStyle
.StyleSVGReset()->mD
;
381 } // namespace mozilla::dom