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/. */
6
7 #include "SVGGeometryElement.h"
8
9 #include "DOMSVGPoint.h"
10 #include "gfxPlatform.h"
11 #include "nsCOMPtr.h"
12 #include "SVGAnimatedLength.h"
13 #include "SVGCircleElement.h"
14 #include "SVGEllipseElement.h"
15 #include "SVGGeometryProperty.h"
16 #include "SVGPathElement.h"
17 #include "SVGRectElement.h"
18 #include "mozilla/dom/DOMPointBinding.h"
19 #include "mozilla/dom/SVGLengthBinding.h"
20 #include "mozilla/gfx/2D.h"
21 #include "mozilla/RefPtr.h"
22 #include "mozilla/SVGContentUtils.h"
23
24 using namespace mozilla::gfx;
25
26 namespace mozilla {
27 namespace dom {
28
29 SVGElement::NumberInfo SVGGeometryElement::sNumberInfo = {nsGkAtoms::pathLength,
30 0, false};
31
32 //----------------------------------------------------------------------
33 // Implementation
34
SVGGeometryElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo)35 SVGGeometryElement::SVGGeometryElement(
36 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
37 : SVGGeometryElementBase(std::move(aNodeInfo)) {}
38
GetNumberInfo()39 SVGElement::NumberAttributesInfo SVGGeometryElement::GetNumberInfo() {
40 return NumberAttributesInfo(&mPathLength, &sNumberInfo, 1);
41 }
42
AfterSetAttr(int32_t aNamespaceID,nsAtom * aName,const nsAttrValue * aValue,const nsAttrValue * aOldValue,nsIPrincipal * aSubjectPrincipal,bool aNotify)43 nsresult SVGGeometryElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
44 const nsAttrValue* aValue,
45 const nsAttrValue* aOldValue,
46 nsIPrincipal* aSubjectPrincipal,
47 bool aNotify) {
48 if (mCachedPath && aNamespaceID == kNameSpaceID_None &&
49 AttributeDefinesGeometry(aName)) {
50 mCachedPath = nullptr;
51 }
52 return SVGGeometryElementBase::AfterSetAttr(
53 aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
54 }
55
IsNodeOfType(uint32_t aFlags) const56 bool SVGGeometryElement::IsNodeOfType(uint32_t aFlags) const {
57 return !(aFlags & ~(eSHAPE | eUSE_TARGET));
58 }
59
AttributeDefinesGeometry(const nsAtom * aName)60 bool SVGGeometryElement::AttributeDefinesGeometry(const nsAtom* aName) {
61 if (aName == nsGkAtoms::pathLength) {
62 return true;
63 }
64
65 // Check for SVGAnimatedLength attribute
66 LengthAttributesInfo info = GetLengthInfo();
67 for (uint32_t i = 0; i < info.mCount; i++) {
68 if (aName == info.mInfos[i].mName) {
69 return true;
70 }
71 }
72
73 return false;
74 }
75
GeometryDependsOnCoordCtx()76 bool SVGGeometryElement::GeometryDependsOnCoordCtx() {
77 // Check the SVGAnimatedLength attribute
78 LengthAttributesInfo info =
79 const_cast<SVGGeometryElement*>(this)->GetLengthInfo();
80 for (uint32_t i = 0; i < info.mCount; i++) {
81 if (info.mValues[i].GetSpecifiedUnitType() ==
82 SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE) {
83 return true;
84 }
85 }
86 return false;
87 }
88
IsMarkable()89 bool SVGGeometryElement::IsMarkable() { return false; }
90
GetMarkPoints(nsTArray<SVGMark> * aMarks)91 void SVGGeometryElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) {}
92
GetOrBuildPath(const DrawTarget * aDrawTarget,FillRule aFillRule)93 already_AddRefed<Path> SVGGeometryElement::GetOrBuildPath(
94 const DrawTarget* aDrawTarget, FillRule aFillRule) {
95 // We only cache the path if it matches the backend used for screen painting,
96 // and it's not a capturing drawtarget. A capturing DT might start using the
97 // the Path object on a different thread (OMTP), and we might have a data race
98 // if we keep a handle to it.
99 bool cacheable = aDrawTarget->GetBackendType() ==
100 gfxPlatform::GetPlatform()->GetDefaultContentBackend();
101
102 if (cacheable && mCachedPath && mCachedPath->GetFillRule() == aFillRule &&
103 aDrawTarget->GetBackendType() == mCachedPath->GetBackendType()) {
104 RefPtr<Path> path(mCachedPath);
105 return path.forget();
106 }
107 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(aFillRule);
108 RefPtr<Path> path = BuildPath(builder);
109 if (cacheable) {
110 mCachedPath = path;
111 }
112 return path.forget();
113 }
114
GetOrBuildPathForMeasuring()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);
120 }
121
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.
GetOrBuildPathForHitTest()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);
132 }
133
IsGeometryChangedViaCSS(ComputedStyle const & aNewStyle,ComputedStyle const & aOldStyle) const134 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);
139 }
140 if (name == nsGkAtoms::circle) {
141 return SVGCircleElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle);
142 }
143 if (name == nsGkAtoms::ellipse) {
144 return SVGEllipseElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle);
145 }
146 if (name == nsGkAtoms::path) {
147 return StaticPrefs::layout_css_d_property_enabled() &&
148 SVGPathElement::IsDPropertyChangedViaCSS(aNewStyle, aOldStyle);
149 }
150 return false;
151 }
152
GetFillRule()153 FillRule SVGGeometryElement::GetFillRule() {
154 FillRule fillRule =
155 FillRule::FILL_WINDING; // Equivalent to StyleFillRule::Nonzero
156
157 bool res = SVGGeometryProperty::DoForComputedStyle(
158 this, [&](const ComputedStyle* s) {
159 const auto* styleSVG = s->StyleSVG();
160
161 MOZ_ASSERT(styleSVG->mFillRule == StyleFillRule::Nonzero ||
162 styleSVG->mFillRule == StyleFillRule::Evenodd);
163
164 if (styleSVG->mFillRule == StyleFillRule::Evenodd) {
165 fillRule = FillRule::FILL_EVEN_ODD;
166 }
167 });
168
169 if (!res) {
170 NS_WARNING("Couldn't get ComputedStyle for content in GetFillRule");
171 }
172
173 return fillRule;
174 }
175
GetPointFrom(const DOMPointInit & aPoint)176 static Point GetPointFrom(const DOMPointInit& aPoint) {
177 return Point(aPoint.mX, aPoint.mY);
178 }
179
IsPointInFill(const DOMPointInit & aPoint)180 bool SVGGeometryElement::IsPointInFill(const DOMPointInit& aPoint) {
181 // d is a presentation attribute, so make sure style is up to date:
182 FlushStyleIfNeeded();
183
184 RefPtr<Path> path = GetOrBuildPathForHitTest();
185 if (!path) {
186 return false;
187 }
188
189 auto point = GetPointFrom(aPoint);
190 return path->ContainsPoint(point, {});
191 }
192
IsPointInStroke(const DOMPointInit & aPoint)193 bool SVGGeometryElement::IsPointInStroke(const DOMPointInit& aPoint) {
194 // stroke-* attributes and the d attribute are presentation attributes, so we
195 // flush the layout before building the path.
196 if (nsCOMPtr<Document> doc = GetComposedDoc()) {
197 doc->FlushPendingNotifications(FlushType::Layout);
198 }
199
200 RefPtr<Path> path = GetOrBuildPathForHitTest();
201 if (!path) {
202 return false;
203 }
204
205 auto point = GetPointFrom(aPoint);
206 bool res = false;
207 SVGGeometryProperty::DoForComputedStyle(this, [&](const ComputedStyle* s) {
208 // Per spec, we should take vector-effect into account.
209 if (s->StyleSVGReset()->HasNonScalingStroke()) {
210 auto mat = SVGContentUtils::GetCTM(this, true);
211 if (mat.HasNonTranslation()) {
212 // We have non-scaling-stroke as well as a non-translation transform.
213 // We should transform the path first then apply the stroke on the
214 // transformed path to preserve the stroke-width.
215 RefPtr<PathBuilder> builder = path->TransformedCopyToBuilder(mat);
216
217 path = builder->Finish();
218 point = mat.TransformPoint(point);
219 }
220 }
221
222 SVGContentUtils::AutoStrokeOptions strokeOptions;
223 SVGContentUtils::GetStrokeOptions(&strokeOptions, this, s, nullptr);
224
225 res = path->StrokeContainsPoint(strokeOptions, point, {});
226 });
227
228 return res;
229 }
230
GetTotalLengthForBinding()231 float SVGGeometryElement::GetTotalLengthForBinding() {
232 // d is a presentation attribute, so make sure style is up to date:
233 FlushStyleIfNeeded();
234 return GetTotalLength();
235 }
236
GetPointAtLength(float distance,ErrorResult & rv)237 already_AddRefed<DOMSVGPoint> SVGGeometryElement::GetPointAtLength(
238 float distance, ErrorResult& rv) {
239 // d is a presentation attribute, so make sure style is up to date:
240 FlushStyleIfNeeded();
241 RefPtr<Path> path = GetOrBuildPathForMeasuring();
242 if (!path) {
243 rv.ThrowInvalidStateError("No path available for measuring");
244 return nullptr;
245 }
246
247 RefPtr<DOMSVGPoint> point = new DOMSVGPoint(path->ComputePointAtLength(
248 clamped(distance, 0.f, path->ComputeLength())));
249 return point.forget();
250 }
251
GetPathLengthScale(PathLengthScaleForType aFor)252 float SVGGeometryElement::GetPathLengthScale(PathLengthScaleForType aFor) {
253 MOZ_ASSERT(aFor == eForTextPath || aFor == eForStroking, "Unknown enum");
254 if (mPathLength.IsExplicitlySet()) {
255 float authorsPathLengthEstimate = mPathLength.GetAnimValue();
256 if (authorsPathLengthEstimate > 0) {
257 RefPtr<Path> path = GetOrBuildPathForMeasuring();
258 if (!path) {
259 // The path is empty or invalid so its length must be zero and
260 // we know that 0 / authorsPathLengthEstimate = 0.
261 return 0.0;
262 }
263 if (aFor == eForTextPath) {
264 // For textPath, a transform on the referenced path affects the
265 // textPath layout, so when calculating the actual path length
266 // we need to take that into account.
267 gfxMatrix matrix = PrependLocalTransformsTo(gfxMatrix());
268 if (!matrix.IsIdentity()) {
269 RefPtr<PathBuilder> builder =
270 path->TransformedCopyToBuilder(ToMatrix(matrix));
271 path = builder->Finish();
272 }
273 }
274 return path->ComputeLength() / authorsPathLengthEstimate;
275 }
276 }
277 return 1.0;
278 }
279
PathLength()280 already_AddRefed<DOMSVGAnimatedNumber> SVGGeometryElement::PathLength() {
281 return mPathLength.ToDOMAnimatedNumber(this);
282 }
283
GetTotalLength()284 float SVGGeometryElement::GetTotalLength() {
285 RefPtr<Path> flat = GetOrBuildPathForMeasuring();
286 return flat ? flat->ComputeLength() : 0.f;
287 }
288
FlushStyleIfNeeded()289 void SVGGeometryElement::FlushStyleIfNeeded() {
290 // Note: we still can set d property on other elements which don't have d
291 // attribute, but we don't look at the d property on them, so here we only
292 // care about the element with d attribute, i.e. SVG path element.
293 if (GetPathDataAttrName() != nsGkAtoms::d ||
294 !StaticPrefs::layout_css_d_property_enabled()) {
295 return;
296 }
297
298 RefPtr<Document> doc = GetComposedDoc();
299 if (!doc) {
300 return;
301 }
302
303 doc->FlushPendingNotifications(FlushType::Style);
304 }
305
306 } // namespace dom
307 } // namespace mozilla
308