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