Line data Source code
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 : #ifndef MOZILLA_SVGPATHDATA_H__
8 : #define MOZILLA_SVGPATHDATA_H__
9 :
10 : #include "nsCOMPtr.h"
11 : #include "nsDebug.h"
12 : #include "nsIContent.h"
13 : #include "nsINode.h"
14 : #include "nsIWeakReferenceUtils.h"
15 : #include "mozilla/gfx/2D.h"
16 : #include "mozilla/gfx/Types.h"
17 : #include "mozilla/MemoryReporting.h"
18 : #include "mozilla/RefPtr.h"
19 : #include "nsSVGElement.h"
20 : #include "nsTArray.h"
21 :
22 : #include <string.h>
23 :
24 : class nsSVGPathDataParser; // IWYU pragma: keep
25 :
26 : struct nsSVGMark;
27 :
28 : namespace mozilla {
29 :
30 : /**
31 : * ATTENTION! WARNING! WATCH OUT!!
32 : *
33 : * Consumers that modify objects of this type absolutely MUST keep the DOM
34 : * wrappers for those lists (if any) in sync!! That's why this class is so
35 : * locked down.
36 : *
37 : * The DOM wrapper class for this class is DOMSVGPathSegList.
38 : *
39 : * This class is not called |class SVGPathSegList| for one very good reason;
40 : * this class does not provide a list of "SVGPathSeg" items, it provides an
41 : * array of floats into which path segments are encoded. See the paragraphs
42 : * that follow for why. Note that the Length() method returns the number of
43 : * floats in our array, not the number of encoded segments, and the index
44 : * operator indexes floats in the array, not segments. If this class were
45 : * called SVGPathSegList the names of these methods would be very misleading.
46 : *
47 : * The reason this class is designed in this way is because there are many
48 : * different types of path segment, each taking a different numbers of
49 : * arguments. We want to store the segments in an nsTArray to avoid individual
50 : * allocations for each item, but the different size of segments means we can't
51 : * have one single segment type for the nsTArray (not without using a space
52 : * wasteful union or something similar). Since the internal code does not need
53 : * to index into the list (the DOM wrapper does, but it handles that itself)
54 : * the obvious solution is to have the items in this class take up variable
55 : * width and have the internal code iterate over these lists rather than index
56 : * into them.
57 : *
58 : * Implementing indexing to segments with O(1) performance would require us to
59 : * allocate and maintain a separate segment index table (keeping that table in
60 : * sync when items are inserted or removed from the list). So long as the
61 : * internal code doesn't require indexing to segments, we can avoid that
62 : * overhead and additional complexity.
63 : *
64 : * Segment encoding: the first float in the encoding of a segment contains the
65 : * segment's type. The segment's type is encoded to/decoded from this float
66 : * using the static methods SVGPathSegUtils::EncodeType(uint32_t)/
67 : * SVGPathSegUtils::DecodeType(float). If the path segment type in question
68 : * takes any arguments then these follow the first float, and are in the same
69 : * order as they are given in a <path> element's 'd' attribute (NOT in the
70 : * order of the createSVGPathSegXxx() methods' arguments from the SVG DOM
71 : * interface SVGPathElement, which are different...grr). Consumers can use
72 : * SVGPathSegUtils::ArgCountForType(type) to determine how many arguments
73 : * there are (if any), and thus where the current encoded segment ends, and
74 : * where the next segment (if any) begins.
75 : */
76 : class SVGPathData
77 : {
78 : friend class SVGAnimatedPathSegList;
79 : friend class DOMSVGPathSegList;
80 : friend class DOMSVGPathSeg;
81 : friend class ::nsSVGPathDataParser;
82 : // nsSVGPathDataParser will not keep wrappers in sync, so consumers
83 : // are responsible for that!
84 :
85 : typedef gfx::DrawTarget DrawTarget;
86 : typedef gfx::Path Path;
87 : typedef gfx::PathBuilder PathBuilder;
88 : typedef gfx::FillRule FillRule;
89 : typedef gfx::Float Float;
90 : typedef gfx::CapStyle CapStyle;
91 :
92 : public:
93 : typedef const float* const_iterator;
94 :
95 88 : SVGPathData(){}
96 44 : ~SVGPathData(){}
97 :
98 : // Only methods that don't make/permit modification to this list are public.
99 : // Only our friend classes can access methods that may change us.
100 :
101 : /// This may return an incomplete string on OOM, but that's acceptable.
102 : void GetValueAsString(nsAString& aValue) const;
103 :
104 14 : bool IsEmpty() const {
105 14 : return mData.IsEmpty();
106 : }
107 :
108 : #ifdef DEBUG
109 : /**
110 : * This method iterates over the encoded segment data and counts the number
111 : * of segments we currently have.
112 : */
113 : uint32_t CountItems() const;
114 : #endif
115 :
116 : /**
117 : * Returns the number of *floats* in the encoding array, and NOT the number
118 : * of segments encoded in this object. (For that, see CountItems() above.)
119 : */
120 0 : uint32_t Length() const {
121 0 : return mData.Length();
122 : }
123 :
124 : const float& operator[](uint32_t aIndex) const {
125 : return mData[aIndex];
126 : }
127 :
128 : // Used by nsSMILCompositor to check if the cached base val is out of date
129 0 : bool operator==(const SVGPathData& rhs) const {
130 : // We use memcmp so that we don't need to worry that the data encoded in
131 : // the first float may have the same bit pattern as a NaN.
132 0 : return mData.Length() == rhs.mData.Length() &&
133 0 : memcmp(mData.Elements(), rhs.mData.Elements(),
134 0 : mData.Length() * sizeof(float)) == 0;
135 : }
136 :
137 : bool SetCapacity(uint32_t aSize) {
138 : return mData.SetCapacity(aSize, fallible);
139 : }
140 :
141 : void Compact() {
142 : mData.Compact();
143 : }
144 :
145 :
146 : float GetPathLength() const;
147 :
148 : uint32_t GetPathSegAtLength(float aLength) const;
149 :
150 : void GetMarkerPositioningData(nsTArray<nsSVGMark> *aMarks) const;
151 :
152 : /**
153 : * Returns true, except on OOM, in which case returns false.
154 : */
155 : bool GetSegmentLengths(nsTArray<double> *aLengths) const;
156 :
157 : /**
158 : * Returns true, except on OOM, in which case returns false.
159 : */
160 : bool GetDistancesFromOriginToEndsOfVisibleSegments(FallibleTArray<double> *aArray) const;
161 :
162 : /**
163 : * This returns a path without the extra little line segments that
164 : * ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps.
165 : * See the comment for that function for more info on that.
166 : */
167 : already_AddRefed<Path> BuildPathForMeasuring() const;
168 :
169 : already_AddRefed<Path> BuildPath(PathBuilder* aBuilder,
170 : uint8_t aCapStyle,
171 : Float aStrokeWidth) const;
172 :
173 0 : const_iterator begin() const { return mData.Elements(); }
174 0 : const_iterator end() const { return mData.Elements() + mData.Length(); }
175 :
176 : // memory reporting methods
177 : size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
178 : size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
179 :
180 : // Access to methods that can modify objects of this type is deliberately
181 : // limited. This is to reduce the chances of someone modifying objects of
182 : // this type without taking the necessary steps to keep DOM wrappers in sync.
183 : // If you need wider access to these methods, consider adding a method to
184 : // SVGAnimatedPathSegList and having that class act as an intermediary so it
185 : // can take care of keeping DOM wrappers in sync.
186 :
187 : protected:
188 : typedef float* iterator;
189 :
190 : /**
191 : * This may fail on OOM if the internal capacity needs to be increased, in
192 : * which case the list will be left unmodified.
193 : */
194 : nsresult CopyFrom(const SVGPathData& rhs);
195 :
196 : float& operator[](uint32_t aIndex) {
197 : return mData[aIndex];
198 : }
199 :
200 : /**
201 : * This may fail (return false) on OOM if the internal capacity is being
202 : * increased, in which case the list will be left unmodified.
203 : */
204 0 : bool SetLength(uint32_t aLength) {
205 0 : return mData.SetLength(aLength, fallible);
206 : }
207 :
208 : nsresult SetValueFromString(const nsAString& aValue);
209 :
210 44 : void Clear() {
211 44 : mData.Clear();
212 44 : }
213 :
214 : // Our DOM wrappers have direct access to our mData, so they directly
215 : // manipulate it rather than us implementing:
216 : //
217 : // * InsertItem(uint32_t aDataIndex, uint32_t aType, const float *aArgs);
218 : // * ReplaceItem(uint32_t aDataIndex, uint32_t aType, const float *aArgs);
219 : // * RemoveItem(uint32_t aDataIndex);
220 : // * bool AppendItem(uint32_t aType, const float *aArgs);
221 :
222 : nsresult AppendSeg(uint32_t aType, ...); // variable number of float args
223 :
224 0 : iterator begin() { return mData.Elements(); }
225 0 : iterator end() { return mData.Elements() + mData.Length(); }
226 :
227 : FallibleTArray<float> mData;
228 : };
229 :
230 :
231 : /**
232 : * This SVGPathData subclass is for SVGPathSegListSMILType which needs to
233 : * have write access to the lists it works with.
234 : *
235 : * Instances of this class do not have DOM wrappers that need to be kept in
236 : * sync, so we can safely expose any protected base class methods required by
237 : * the SMIL code.
238 : */
239 0 : class SVGPathDataAndInfo final : public SVGPathData
240 : {
241 : public:
242 0 : explicit SVGPathDataAndInfo(nsSVGElement *aElement = nullptr)
243 0 : : mElement(do_GetWeakReference(static_cast<nsINode*>(aElement)))
244 0 : {}
245 :
246 0 : void SetElement(nsSVGElement *aElement) {
247 0 : mElement = do_GetWeakReference(static_cast<nsINode*>(aElement));
248 0 : }
249 :
250 0 : nsSVGElement* Element() const {
251 0 : nsCOMPtr<nsIContent> e = do_QueryReferent(mElement);
252 0 : return static_cast<nsSVGElement*>(e.get());
253 : }
254 :
255 0 : nsresult CopyFrom(const SVGPathDataAndInfo& rhs) {
256 0 : mElement = rhs.mElement;
257 0 : return SVGPathData::CopyFrom(rhs);
258 : }
259 :
260 : /**
261 : * Returns true if this object is an "identity" value, from the perspective
262 : * of SMIL. In other words, returns true until the initial value set up in
263 : * SVGPathSegListSMILType::Init() has been changed with a SetElement() call.
264 : */
265 0 : bool IsIdentity() const {
266 0 : if (!mElement) {
267 0 : MOZ_ASSERT(IsEmpty(), "target element propagation failure");
268 0 : return true;
269 : }
270 0 : return false;
271 : }
272 :
273 : /**
274 : * Exposed so that SVGPathData baseVals can be copied to
275 : * SVGPathDataAndInfo objects. Note that callers should also call
276 : * SetElement() when using this method!
277 : */
278 : using SVGPathData::CopyFrom;
279 :
280 : // Exposed since SVGPathData objects can be modified.
281 : using SVGPathData::iterator;
282 : using SVGPathData::operator[];
283 : using SVGPathData::SetLength;
284 : using SVGPathData::begin;
285 : using SVGPathData::end;
286 :
287 : private:
288 : // We must keep a weak reference to our element because we may belong to a
289 : // cached baseVal nsSMILValue. See the comments starting at:
290 : // https://bugzilla.mozilla.org/show_bug.cgi?id=515116#c15
291 : // See also https://bugzilla.mozilla.org/show_bug.cgi?id=653497
292 : nsWeakPtr mElement;
293 : };
294 :
295 : } // namespace mozilla
296 :
297 : #endif // MOZILLA_SVGPATHDATA_H__
|