no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / svg / SVGPathElement.cpp
blob61d2dab070ef6394370571e85b18cb596ecc6fb0
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"
9 #include <algorithm>
11 #include "DOMSVGPathSeg.h"
12 #include "DOMSVGPathSegList.h"
13 #include "SVGGeometryProperty.h"
14 #include "gfx2DGlue.h"
15 #include "gfxPlatform.h"
16 #include "nsGkAtoms.h"
17 #include "nsIFrame.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 //----------------------------------------------------------------------
39 // Implementation
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 //----------------------------------------------------------------------
55 // nsINode methods
57 NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPathElement)
59 uint32_t SVGPathElement::GetPathSegAtLength(float distance) {
60 uint32_t seg = 0;
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);
69 FlushStyleIfNeeded();
70 if (SVGGeometryProperty::DoForComputedStyle(this, callback)) {
71 return seg;
73 return mD.GetAnimValue().GetPathSegAtLength(distance);
76 already_AddRefed<DOMSVGPathSegClosePath>
77 SVGPathElement::CreateSVGPathSegClosePath() {
78 return do_AddRef(new DOMSVGPathSegClosePath());
81 already_AddRefed<DOMSVGPathSegMovetoAbs>
82 SVGPathElement::CreateSVGPathSegMovetoAbs(float x, float y) {
83 return do_AddRef(new DOMSVGPathSegMovetoAbs(x, y));
86 already_AddRefed<DOMSVGPathSegMovetoRel>
87 SVGPathElement::CreateSVGPathSegMovetoRel(float x, float y) {
88 return do_AddRef(new DOMSVGPathSegMovetoRel(x, y));
91 already_AddRefed<DOMSVGPathSegLinetoAbs>
92 SVGPathElement::CreateSVGPathSegLinetoAbs(float x, float y) {
93 return do_AddRef(new DOMSVGPathSegLinetoAbs(x, y));
96 already_AddRefed<DOMSVGPathSegLinetoRel>
97 SVGPathElement::CreateSVGPathSegLinetoRel(float x, float y) {
98 return do_AddRef(new DOMSVGPathSegLinetoRel(x, y));
101 already_AddRefed<DOMSVGPathSegCurvetoCubicAbs>
102 SVGPathElement::CreateSVGPathSegCurvetoCubicAbs(float x, float y, float x1,
103 float y1, float x2, float y2) {
104 // Note that we swap from DOM API argument order to the argument order used
105 // in the <path> element's 'd' attribute (i.e. we put the arguments for the
106 // end point of the segment last instead of first).
107 return do_AddRef(new DOMSVGPathSegCurvetoCubicAbs(x1, y1, x2, y2, x, y));
110 already_AddRefed<DOMSVGPathSegCurvetoCubicRel>
111 SVGPathElement::CreateSVGPathSegCurvetoCubicRel(float x, float y, float x1,
112 float y1, float x2, float y2) {
113 // See comment in CreateSVGPathSegCurvetoCubicAbs
114 return do_AddRef(new DOMSVGPathSegCurvetoCubicRel(x1, y1, x2, y2, x, y));
117 already_AddRefed<DOMSVGPathSegCurvetoQuadraticAbs>
118 SVGPathElement::CreateSVGPathSegCurvetoQuadraticAbs(float x, float y, float x1,
119 float y1) {
120 // See comment in CreateSVGPathSegCurvetoCubicAbs
121 return do_AddRef(new DOMSVGPathSegCurvetoQuadraticAbs(x1, y1, x, y));
124 already_AddRefed<DOMSVGPathSegCurvetoQuadraticRel>
125 SVGPathElement::CreateSVGPathSegCurvetoQuadraticRel(float x, float y, float x1,
126 float y1) {
127 // See comment in CreateSVGPathSegCurvetoCubicAbs
128 return do_AddRef(new DOMSVGPathSegCurvetoQuadraticRel(x1, y1, x, y));
131 already_AddRefed<DOMSVGPathSegArcAbs> SVGPathElement::CreateSVGPathSegArcAbs(
132 float x, float y, float r1, float r2, float angle, bool largeArcFlag,
133 bool sweepFlag) {
134 // See comment in CreateSVGPathSegCurvetoCubicAbs
135 return do_AddRef(
136 new DOMSVGPathSegArcAbs(r1, r2, angle, largeArcFlag, sweepFlag, x, y));
139 already_AddRefed<DOMSVGPathSegArcRel> SVGPathElement::CreateSVGPathSegArcRel(
140 float x, float y, float r1, float r2, float angle, bool largeArcFlag,
141 bool sweepFlag) {
142 // See comment in CreateSVGPathSegCurvetoCubicAbs
143 return do_AddRef(
144 new DOMSVGPathSegArcRel(r1, r2, angle, largeArcFlag, sweepFlag, x, y));
147 already_AddRefed<DOMSVGPathSegLinetoHorizontalAbs>
148 SVGPathElement::CreateSVGPathSegLinetoHorizontalAbs(float x) {
149 return do_AddRef(new DOMSVGPathSegLinetoHorizontalAbs(x));
152 already_AddRefed<DOMSVGPathSegLinetoHorizontalRel>
153 SVGPathElement::CreateSVGPathSegLinetoHorizontalRel(float x) {
154 return do_AddRef(new DOMSVGPathSegLinetoHorizontalRel(x));
157 already_AddRefed<DOMSVGPathSegLinetoVerticalAbs>
158 SVGPathElement::CreateSVGPathSegLinetoVerticalAbs(float y) {
159 return do_AddRef(new DOMSVGPathSegLinetoVerticalAbs(y));
162 already_AddRefed<DOMSVGPathSegLinetoVerticalRel>
163 SVGPathElement::CreateSVGPathSegLinetoVerticalRel(float y) {
164 return do_AddRef(new DOMSVGPathSegLinetoVerticalRel(y));
167 already_AddRefed<DOMSVGPathSegCurvetoCubicSmoothAbs>
168 SVGPathElement::CreateSVGPathSegCurvetoCubicSmoothAbs(float x, float y,
169 float x2, float y2) {
170 // See comment in CreateSVGPathSegCurvetoCubicAbs
171 return do_AddRef(new DOMSVGPathSegCurvetoCubicSmoothAbs(x2, y2, x, y));
174 already_AddRefed<DOMSVGPathSegCurvetoCubicSmoothRel>
175 SVGPathElement::CreateSVGPathSegCurvetoCubicSmoothRel(float x, float y,
176 float x2, float y2) {
177 // See comment in CreateSVGPathSegCurvetoCubicAbs
178 return do_AddRef(new DOMSVGPathSegCurvetoCubicSmoothRel(x2, y2, x, y));
181 already_AddRefed<DOMSVGPathSegCurvetoQuadraticSmoothAbs>
182 SVGPathElement::CreateSVGPathSegCurvetoQuadraticSmoothAbs(float x, float y) {
183 return do_AddRef(new DOMSVGPathSegCurvetoQuadraticSmoothAbs(x, y));
186 already_AddRefed<DOMSVGPathSegCurvetoQuadraticSmoothRel>
187 SVGPathElement::CreateSVGPathSegCurvetoQuadraticSmoothRel(float x, float y) {
188 return do_AddRef(new DOMSVGPathSegCurvetoQuadraticSmoothRel(x, y));
191 // FIXME: This API is enabled only if dom.svg.pathSeg.enabled is true. This
192 // preference is off by default in Bug 1388931, and will be dropped later.
193 // So we are not planning to map d property for this API.
194 already_AddRefed<DOMSVGPathSegList> SVGPathElement::PathSegList() {
195 return DOMSVGPathSegList::GetDOMWrapper(mD.GetBaseValKey(), this);
198 // FIXME: This API is enabled only if dom.svg.pathSeg.enabled is true. This
199 // preference is off by default in Bug 1388931, and will be dropped later.
200 // So we are not planning to map d property for this API.
201 already_AddRefed<DOMSVGPathSegList> SVGPathElement::AnimatedPathSegList() {
202 return DOMSVGPathSegList::GetDOMWrapper(mD.GetAnimValKey(), this);
205 //----------------------------------------------------------------------
206 // SVGElement methods
208 /* virtual */
209 bool SVGPathElement::HasValidDimensions() const {
210 bool hasPath = false;
211 auto callback = [&](const ComputedStyle* s) {
212 const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset();
213 hasPath =
214 styleSVGReset->mD.IsPath() && !styleSVGReset->mD.AsPath()._0.IsEmpty();
217 SVGGeometryProperty::DoForComputedStyle(this, callback);
218 // If hasPath is false, we may disable the pref of d property, so we fallback
219 // to check mD.
220 return hasPath || !mD.GetAnimValue().IsEmpty();
223 //----------------------------------------------------------------------
224 // nsIContent methods
226 NS_IMETHODIMP_(bool)
227 SVGPathElement::IsAttributeMapped(const nsAtom* name) const {
228 return name == nsGkAtoms::d || SVGPathElementBase::IsAttributeMapped(name);
231 already_AddRefed<Path> SVGPathElement::GetOrBuildPathForMeasuring() {
232 RefPtr<Path> path;
233 bool success = SVGGeometryProperty::DoForComputedStyle(
234 this, [&path](const ComputedStyle* s) {
235 const auto& d = s->StyleSVGReset()->mD;
236 if (d.IsNone()) {
237 return;
239 path = SVGPathData::BuildPathForMeasuring(d.AsPath()._0.AsSpan());
241 return success ? path.forget() : mD.GetAnimValue().BuildPathForMeasuring();
244 //----------------------------------------------------------------------
245 // SVGGeometryElement methods
247 bool SVGPathElement::AttributeDefinesGeometry(const nsAtom* aName) {
248 return aName == nsGkAtoms::d || aName == nsGkAtoms::pathLength;
251 bool SVGPathElement::IsMarkable() { return true; }
253 void SVGPathElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) {
254 auto callback = [aMarks](const ComputedStyle* s) {
255 const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset();
256 if (styleSVGReset->mD.IsPath()) {
257 Span<const StylePathCommand> path =
258 styleSVGReset->mD.AsPath()._0.AsSpan();
259 SVGPathData::GetMarkerPositioningData(path, aMarks);
263 if (SVGGeometryProperty::DoForComputedStyle(this, callback)) {
264 return;
267 mD.GetAnimValue().GetMarkerPositioningData(aMarks);
270 void SVGPathElement::GetAsSimplePath(SimplePath* aSimplePath) {
271 aSimplePath->Reset();
272 auto callback = [&](const ComputedStyle* s) {
273 const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset();
274 if (styleSVGReset->mD.IsPath()) {
275 auto pathData = styleSVGReset->mD.AsPath()._0.AsSpan();
276 auto maybeRect = SVGPathToAxisAlignedRect(pathData);
277 if (maybeRect.isSome()) {
278 Rect r = maybeRect.value();
279 aSimplePath->SetRect(r.x, r.y, r.width, r.height);
284 SVGGeometryProperty::DoForComputedStyle(this, callback);
287 already_AddRefed<Path> SVGPathElement::BuildPath(PathBuilder* aBuilder) {
288 // The Moz2D PathBuilder that our SVGPathData will be using only cares about
289 // the fill rule. However, in order to fulfill the requirements of the SVG
290 // spec regarding zero length sub-paths when square line caps are in use,
291 // SVGPathData needs to know our stroke-linecap style and, if "square", then
292 // also our stroke width. See the comment for
293 // ApproximateZeroLengthSubpathSquareCaps for more info.
295 auto strokeLineCap = StyleStrokeLinecap::Butt;
296 Float strokeWidth = 0;
297 RefPtr<Path> path;
299 auto callback = [&](const ComputedStyle* s) {
300 const nsStyleSVG* styleSVG = s->StyleSVG();
301 // Note: the path that we return may be used for hit-testing, and SVG
302 // exposes hit-testing of strokes that are not actually painted. For that
303 // reason we do not check for eStyleSVGPaintType_None or check the stroke
304 // opacity here.
305 if (styleSVG->mStrokeLinecap != StyleStrokeLinecap::Butt) {
306 strokeLineCap = styleSVG->mStrokeLinecap;
307 strokeWidth = SVGContentUtils::GetStrokeWidth(this, s, nullptr);
310 const auto& d = s->StyleSVGReset()->mD;
311 if (d.IsPath()) {
312 path = SVGPathData::BuildPath(d.AsPath()._0.AsSpan(), aBuilder,
313 strokeLineCap, strokeWidth);
317 bool success = SVGGeometryProperty::DoForComputedStyle(this, callback);
318 if (success) {
319 return path.forget();
322 // Fallback to use the d attribute if it exists.
323 return mD.GetAnimValue().BuildPath(aBuilder, strokeLineCap, strokeWidth);
326 bool SVGPathElement::GetDistancesFromOriginToEndsOfVisibleSegments(
327 FallibleTArray<double>* aOutput) {
328 bool ret = false;
329 auto callback = [&ret, aOutput](const ComputedStyle* s) {
330 const auto& d = s->StyleSVGReset()->mD;
331 ret = d.IsNone() ||
332 SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
333 d.AsPath()._0.AsSpan(), aOutput);
336 if (SVGGeometryProperty::DoForComputedStyle(this, callback)) {
337 return ret;
340 return mD.GetAnimValue().GetDistancesFromOriginToEndsOfVisibleSegments(
341 aOutput);
344 // Offset paths (including references to SVG Paths) are closed loops only if the
345 // final command in the path list is a closepath command ("z" or "Z"), otherwise
346 // they are unclosed intervals.
347 // https://drafts.fxtf.org/motion/#path-distance
348 bool SVGPathElement::IsClosedLoop() const {
349 bool isClosed = false;
350 auto callback = [&](const ComputedStyle* s) {
351 const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset();
352 if (styleSVGReset->mD.IsPath()) {
353 isClosed = !styleSVGReset->mD.AsPath()._0.IsEmpty() &&
354 styleSVGReset->mD.AsPath()._0.AsSpan().rbegin()->IsClosePath();
358 const bool success = SVGGeometryProperty::DoForComputedStyle(this, callback);
359 if (success) {
360 return isClosed;
363 const SVGPathData& data = mD.GetAnimValue();
364 // FIXME: Bug 1847621, we can cache this value, instead of walking through the
365 // entire path again and again.
366 uint32_t i = 0;
367 uint32_t segType = SVGPathSeg_Binding::PATHSEG_UNKNOWN;
368 while (i < data.Length()) {
369 segType = SVGPathSegUtils::DecodeType(data[i++]);
370 i += SVGPathSegUtils::ArgCountForType(segType);
372 return segType == SVGPathSeg_Binding::PATHSEG_CLOSEPATH;
375 /* static */
376 bool SVGPathElement::IsDPropertyChangedViaCSS(const ComputedStyle& aNewStyle,
377 const ComputedStyle& aOldStyle) {
378 return aNewStyle.StyleSVGReset()->mD != aOldStyle.StyleSVGReset()->mD;
381 } // namespace mozilla::dom