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