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 DOM_SVG_SVGPATHSEGUTILS_H_
8 #define DOM_SVG_SVGPATHSEGUTILS_H_
9 
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/dom/SVGPathSegBinding.h"
12 #include "mozilla/gfx/Point.h"
13 #include "mozilla/gfx/Rect.h"
14 #include "nsDebug.h"
15 
16 namespace mozilla {
17 
18 struct StylePathCommand;
19 
20 #define NS_SVG_PATH_SEG_MAX_ARGS 7
21 #define NS_SVG_PATH_SEG_FIRST_VALID_TYPE \
22   dom::SVGPathSeg_Binding::PATHSEG_CLOSEPATH
23 #define NS_SVG_PATH_SEG_LAST_VALID_TYPE \
24   dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
25 #define NS_SVG_PATH_SEG_TYPE_COUNT (NS_SVG_PATH_SEG_LAST_VALID_TYPE + 1)
26 
27 /**
28  * Code that works with path segments can use an instance of this class to
29  * store/provide information about the start of the current subpath and the
30  * last path segment (if any).
31  */
32 struct SVGPathTraversalState {
33   using Point = gfx::Point;
34 
35   enum TraversalMode { eUpdateAll, eUpdateOnlyStartAndCurrentPos };
36 
SVGPathTraversalStateSVGPathTraversalState37   SVGPathTraversalState()
38       : start(0.0, 0.0),
39         pos(0.0, 0.0),
40         cp1(0.0, 0.0),
41         cp2(0.0, 0.0),
42         length(0.0),
43         mode(eUpdateAll) {}
44 
ShouldUpdateLengthAndControlPointsSVGPathTraversalState45   bool ShouldUpdateLengthAndControlPoints() { return mode == eUpdateAll; }
46 
47   Point start;  // start point of current sub path (reset each moveto)
48 
49   Point pos;  // current position (end point of previous segment)
50 
51   Point cp1;  // quadratic control point - if the previous segment was a
52               // quadratic bezier curve then this is set to the absolute
53               // position of its control point, otherwise its set to pos
54 
55   Point cp2;  // cubic control point - if the previous segment was a cubic
56               // bezier curve then this is set to the absolute position of
57               // its second control point, otherwise it's set to pos
58 
59   float length;  // accumulated path length
60 
61   TraversalMode mode;  // indicates what to track while traversing a path
62 };
63 
64 /**
65  * This class is just a collection of static methods - it doesn't have any data
66  * members, and it's not possible to create instances of this class. This class
67  * exists purely as a convenient place to gather together a bunch of methods
68  * related to manipulating and answering questions about path segments.
69  * Internally we represent path segments purely as an array of floats. See the
70  * comment documenting SVGPathData for more info on that.
71  *
72  * The DOM wrapper classes for encoded path segments (data contained in
73  * instances of SVGPathData) is DOMSVGPathSeg and its sub-classes. Note that
74  * there are multiple different DOM classes for path segs - one for each of the
75  * 19 SVG 1.1 segment types.
76  */
77 class SVGPathSegUtils {
78  private:
79   SVGPathSegUtils() = default;  // private to prevent instances
80 
81  public:
82   static void GetValueAsString(const float* aSeg, nsAString& aValue);
83 
84   /**
85    * Encode a segment type enum to a float.
86    *
87    * At some point in the future we will likely want to encode other
88    * information into the float, such as whether the command was explicit or
89    * not. For now all this method does is save on int to float runtime
90    * conversion by requiring uint32_t and float to be of the same size so we
91    * can simply do a bitwise uint32_t<->float copy.
92    */
EncodeType(uint32_t aType)93   static float EncodeType(uint32_t aType) {
94     static_assert(sizeof(uint32_t) == sizeof(float),
95                   "sizeof uint32_t and float must be the same");
96     MOZ_ASSERT(IsValidType(aType), "Seg type not recognized");
97     return *(reinterpret_cast<float*>(&aType));
98   }
99 
DecodeType(float aType)100   static uint32_t DecodeType(float aType) {
101     static_assert(sizeof(uint32_t) == sizeof(float),
102                   "sizeof uint32_t and float must be the same");
103     uint32_t type = *(reinterpret_cast<uint32_t*>(&aType));
104     MOZ_ASSERT(IsValidType(type), "Seg type not recognized");
105     return type;
106   }
107 
GetPathSegTypeAsLetter(uint32_t aType)108   static char16_t GetPathSegTypeAsLetter(uint32_t aType) {
109     MOZ_ASSERT(IsValidType(aType), "Seg type not recognized");
110 
111     static const char16_t table[] = {
112         char16_t('x'),  //  0 == PATHSEG_UNKNOWN
113         char16_t('z'),  //  1 == PATHSEG_CLOSEPATH
114         char16_t('M'),  //  2 == PATHSEG_MOVETO_ABS
115         char16_t('m'),  //  3 == PATHSEG_MOVETO_REL
116         char16_t('L'),  //  4 == PATHSEG_LINETO_ABS
117         char16_t('l'),  //  5 == PATHSEG_LINETO_REL
118         char16_t('C'),  //  6 == PATHSEG_CURVETO_CUBIC_ABS
119         char16_t('c'),  //  7 == PATHSEG_CURVETO_CUBIC_REL
120         char16_t('Q'),  //  8 == PATHSEG_CURVETO_QUADRATIC_ABS
121         char16_t('q'),  //  9 == PATHSEG_CURVETO_QUADRATIC_REL
122         char16_t('A'),  // 10 == PATHSEG_ARC_ABS
123         char16_t('a'),  // 11 == PATHSEG_ARC_REL
124         char16_t('H'),  // 12 == PATHSEG_LINETO_HORIZONTAL_ABS
125         char16_t('h'),  // 13 == PATHSEG_LINETO_HORIZONTAL_REL
126         char16_t('V'),  // 14 == PATHSEG_LINETO_VERTICAL_ABS
127         char16_t('v'),  // 15 == PATHSEG_LINETO_VERTICAL_REL
128         char16_t('S'),  // 16 == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
129         char16_t('s'),  // 17 == PATHSEG_CURVETO_CUBIC_SMOOTH_REL
130         char16_t('T'),  // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
131         char16_t('t')   // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
132     };
133     static_assert(MOZ_ARRAY_LENGTH(table) == NS_SVG_PATH_SEG_TYPE_COUNT,
134                   "Unexpected table size");
135 
136     return table[aType];
137   }
138 
ArgCountForType(uint32_t aType)139   static uint32_t ArgCountForType(uint32_t aType) {
140     MOZ_ASSERT(IsValidType(aType), "Seg type not recognized");
141 
142     static const uint8_t table[] = {
143         0,  //  0 == PATHSEG_UNKNOWN
144         0,  //  1 == PATHSEG_CLOSEPATH
145         2,  //  2 == PATHSEG_MOVETO_ABS
146         2,  //  3 == PATHSEG_MOVETO_REL
147         2,  //  4 == PATHSEG_LINETO_ABS
148         2,  //  5 == PATHSEG_LINETO_REL
149         6,  //  6 == PATHSEG_CURVETO_CUBIC_ABS
150         6,  //  7 == PATHSEG_CURVETO_CUBIC_REL
151         4,  //  8 == PATHSEG_CURVETO_QUADRATIC_ABS
152         4,  //  9 == PATHSEG_CURVETO_QUADRATIC_REL
153         7,  // 10 == PATHSEG_ARC_ABS
154         7,  // 11 == PATHSEG_ARC_REL
155         1,  // 12 == PATHSEG_LINETO_HORIZONTAL_ABS
156         1,  // 13 == PATHSEG_LINETO_HORIZONTAL_REL
157         1,  // 14 == PATHSEG_LINETO_VERTICAL_ABS
158         1,  // 15 == PATHSEG_LINETO_VERTICAL_REL
159         4,  // 16 == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
160         4,  // 17 == PATHSEG_CURVETO_CUBIC_SMOOTH_REL
161         2,  // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
162         2   // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
163     };
164     static_assert(MOZ_ARRAY_LENGTH(table) == NS_SVG_PATH_SEG_TYPE_COUNT,
165                   "Unexpected table size");
166 
167     return table[aType];
168   }
169 
170   /**
171    * Convenience so that callers can pass a float containing an encoded type
172    * and have it decoded implicitly.
173    */
ArgCountForType(float aType)174   static uint32_t ArgCountForType(float aType) {
175     return ArgCountForType(DecodeType(aType));
176   }
177 
IsValidType(uint32_t aType)178   static bool IsValidType(uint32_t aType) {
179     return aType >= NS_SVG_PATH_SEG_FIRST_VALID_TYPE &&
180            aType <= NS_SVG_PATH_SEG_LAST_VALID_TYPE;
181   }
182 
IsCubicType(uint32_t aType)183   static bool IsCubicType(uint32_t aType) {
184     return aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_REL ||
185            aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_ABS ||
186            aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_SMOOTH_REL ||
187            aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
188   }
189 
IsQuadraticType(uint32_t aType)190   static bool IsQuadraticType(uint32_t aType) {
191     return aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_REL ||
192            aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_ABS ||
193            aType ==
194                dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL ||
195            aType ==
196                dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
197   }
198 
IsArcType(uint32_t aType)199   static bool IsArcType(uint32_t aType) {
200     return aType == dom::SVGPathSeg_Binding::PATHSEG_ARC_ABS ||
201            aType == dom::SVGPathSeg_Binding::PATHSEG_ARC_REL;
202   }
203 
IsRelativeOrAbsoluteType(uint32_t aType)204   static bool IsRelativeOrAbsoluteType(uint32_t aType) {
205     MOZ_ASSERT(IsValidType(aType), "Seg type not recognized");
206 
207     // When adding a new path segment type, ensure that the returned condition
208     // below is still correct.
209     static_assert(
210         NS_SVG_PATH_SEG_LAST_VALID_TYPE ==
211             dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,
212         "Unexpected type");
213 
214     return aType >= dom::SVGPathSeg_Binding::PATHSEG_MOVETO_ABS;
215   }
216 
IsRelativeType(uint32_t aType)217   static bool IsRelativeType(uint32_t aType) {
218     MOZ_ASSERT(IsRelativeOrAbsoluteType(aType),
219                "IsRelativeType called with segment type that does not come in "
220                "relative and absolute forms");
221 
222     // When adding a new path segment type, ensure that the returned condition
223     // below is still correct.
224     static_assert(
225         NS_SVG_PATH_SEG_LAST_VALID_TYPE ==
226             dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,
227         "Unexpected type");
228 
229     return aType & 1;
230   }
231 
RelativeVersionOfType(uint32_t aType)232   static uint32_t RelativeVersionOfType(uint32_t aType) {
233     MOZ_ASSERT(IsRelativeOrAbsoluteType(aType),
234                "RelativeVersionOfType called with segment type that does not "
235                "come in relative and absolute forms");
236 
237     // When adding a new path segment type, ensure that the returned condition
238     // below is still correct.
239     static_assert(
240         NS_SVG_PATH_SEG_LAST_VALID_TYPE ==
241             dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,
242         "Unexpected type");
243 
244     return aType | 1;
245   }
246 
SameTypeModuloRelativeness(uint32_t aType1,uint32_t aType2)247   static uint32_t SameTypeModuloRelativeness(uint32_t aType1, uint32_t aType2) {
248     if (!IsRelativeOrAbsoluteType(aType1)) {
249       return aType1 == aType2;
250     }
251 
252     return RelativeVersionOfType(aType1) == RelativeVersionOfType(aType2);
253   }
254 
255   /**
256    * Traverse the given path segment and update the SVGPathTraversalState
257    * object.
258    */
259   static void TraversePathSegment(const float* aData,
260                                   SVGPathTraversalState& aState);
261 
262   /**
263    * Traverse the given path segment and update the SVGPathTraversalState
264    * object. This is identical to the above one but accepts StylePathCommand.
265    */
266   static void TraversePathSegment(const StylePathCommand& aCommand,
267                                   SVGPathTraversalState& aState);
268 };
269 
270 /// Detect whether the path represents a rectangle (for both filling AND
271 /// stroking) and if so returns it.
272 ///
273 /// This is typically useful for google slides which has many of these rectangle
274 /// shaped paths. It handles the same scenarios as skia's
275 /// SkPathPriv::IsRectContour which it is inspried from, including zero-length
276 /// edges and multiple points on edges of the rectangle, and doesn't attempt to
277 /// detect flat curves (that could easily be added but the expectation is that
278 /// since skia doesn't fast path it we're not likely to run into it in
279 /// practice).
280 ///
281 /// We could implement something similar for polygons.
282 Maybe<gfx::Rect> SVGPathToAxisAlignedRect(Span<const StylePathCommand> aPath);
283 
284 }  // namespace mozilla
285 
286 #endif  // DOM_SVG_SVGPATHSEGUTILS_H_
287