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_SVGCONTENTUTILS_H
8 #define MOZILLA_SVGCONTENTUTILS_H
9
10 // include math.h to pick up definition of M_ maths defines e.g. M_PI
11 #include <math.h>
12
13 #include "mozilla/gfx/2D.h" // for StrokeOptions
14 #include "mozilla/gfx/Matrix.h"
15 #include "mozilla/RangedPtr.h"
16 #include "nsError.h"
17 #include "nsStringFwd.h"
18 #include "gfx2DGlue.h"
19
20 class nsIContent;
21 class nsIDocument;
22 class nsIFrame;
23 class nsStyleContext;
24 class nsStyleCoord;
25 class nsSVGElement;
26
27 namespace mozilla {
28 class nsSVGAnimatedTransformList;
29 class SVGAnimatedPreserveAspectRatio;
30 class SVGContextPaint;
31 class SVGPreserveAspectRatio;
32 namespace dom {
33 class Element;
34 class SVGSVGElement;
35 class SVGViewportElement;
36 } // namespace dom
37
38 } // namespace mozilla
39
40 #define SVG_ZERO_LENGTH_PATH_FIX_FACTOR 512
41
42 /**
43 * SVGTransformTypes controls the transforms that PrependLocalTransformsTo
44 * applies.
45 *
46 * If aWhich is eAllTransforms, then all the transforms from the coordinate
47 * space established by this element for its children to the coordinate
48 * space established by this element's parent element for this element, are
49 * included.
50 *
51 * If aWhich is eUserSpaceToParent, then only the transforms from this
52 * element's userspace to the coordinate space established by its parent is
53 * included. This includes any transforms introduced by the 'transform'
54 * attribute, transform animations and animateMotion, but not any offsets
55 * due to e.g. 'x'/'y' attributes, or any transform due to a 'viewBox'
56 * attribute. (SVG userspace is defined to be the coordinate space in which
57 * coordinates on an element apply.)
58 *
59 * If aWhich is eChildToUserSpace, then only the transforms from the
60 * coordinate space established by this element for its childre to this
61 * elements userspace are included. This includes any offsets due to e.g.
62 * 'x'/'y' attributes, and any transform due to a 'viewBox' attribute, but
63 * does not include any transforms due to the 'transform' attribute.
64 */
65 enum SVGTransformTypes {
66 eAllTransforms,
67 eUserSpaceToParent,
68 eChildToUserSpace
69 };
70
IsSVGWhitespace(char aChar)71 inline bool IsSVGWhitespace(char aChar) {
72 return aChar == '\x20' || aChar == '\x9' || aChar == '\xD' || aChar == '\xA';
73 }
74
IsSVGWhitespace(char16_t aChar)75 inline bool IsSVGWhitespace(char16_t aChar) {
76 return aChar == char16_t('\x20') || aChar == char16_t('\x9') ||
77 aChar == char16_t('\xD') || aChar == char16_t('\xA');
78 }
79
80 /**
81 * Functions generally used by SVG Content classes. Functions here
82 * should not generally depend on layout methods/classes e.g. nsSVGUtils
83 */
84 class SVGContentUtils {
85 public:
86 typedef mozilla::gfx::Float Float;
87 typedef mozilla::gfx::Matrix Matrix;
88 typedef mozilla::gfx::Rect Rect;
89 typedef mozilla::gfx::StrokeOptions StrokeOptions;
90 typedef mozilla::SVGAnimatedPreserveAspectRatio
91 SVGAnimatedPreserveAspectRatio;
92 typedef mozilla::SVGPreserveAspectRatio SVGPreserveAspectRatio;
93
94 /*
95 * Get the outer SVG element of an nsIContent
96 */
97 static mozilla::dom::SVGSVGElement* GetOuterSVGElement(
98 nsSVGElement* aSVGElement);
99
100 /**
101 * Activates the animation element aContent as a result of navigation to the
102 * fragment identifier that identifies aContent. aContent must be an instance
103 * of nsSVGAnimationElement.
104 *
105 * This is just a shim to allow nsSVGAnimationElement::ActivateByHyperlink to
106 * be called from layout/base without adding to that directory's include
107 * paths.
108 */
109 static void ActivateByHyperlink(nsIContent* aContent);
110
111 /**
112 * Moz2D's StrokeOptions requires someone else to own its mDashPattern
113 * buffer, which is a pain when you want to initialize a StrokeOptions object
114 * in a helper function and pass it out. This sub-class owns the mDashPattern
115 * buffer so that consumers of such a helper function don't need to worry
116 * about creating it, passing it in, or deleting it. (An added benefit is
117 * that in the typical case when stroke-dasharray is short it will avoid
118 * allocating.)
119 */
120 struct AutoStrokeOptions : public StrokeOptions {
AutoStrokeOptionsAutoStrokeOptions121 AutoStrokeOptions() {
122 MOZ_ASSERT(mDashLength == 0, "InitDashPattern() depends on this");
123 }
~AutoStrokeOptionsAutoStrokeOptions124 ~AutoStrokeOptions() {
125 if (mDashPattern && mDashPattern != mSmallArray) {
126 delete[] mDashPattern;
127 }
128 }
129 /**
130 * Creates the buffer to store the stroke-dasharray, assuming out-of-memory
131 * does not occur. The buffer's address is assigned to mDashPattern and
132 * returned to the caller as a non-const pointer (so that the caller can
133 * initialize the values in the buffer, since mDashPattern is const).
134 */
InitDashPatternAutoStrokeOptions135 Float* InitDashPattern(size_t aDashCount) {
136 if (aDashCount <= MOZ_ARRAY_LENGTH(mSmallArray)) {
137 mDashPattern = mSmallArray;
138 return mSmallArray;
139 }
140 Float* nonConstArray = new (mozilla::fallible) Float[aDashCount];
141 mDashPattern = nonConstArray;
142 return nonConstArray;
143 }
DiscardDashPatternAutoStrokeOptions144 void DiscardDashPattern() {
145 if (mDashPattern && mDashPattern != mSmallArray) {
146 delete[] mDashPattern;
147 }
148 mDashLength = 0;
149 mDashPattern = nullptr;
150 }
151
152 private:
153 // Most dasharrays will fit in this and save us allocating
154 Float mSmallArray[16];
155 };
156
157 enum StrokeOptionFlags { eAllStrokeOptions, eIgnoreStrokeDashing };
158 /**
159 * Note: the linecap style returned in aStrokeOptions is not valid when
160 * ShapeTypeHasNoCorners(aElement) == true && aFlags == eIgnoreStrokeDashing,
161 * since when aElement has no corners the rendered linecap style depends on
162 * whether or not the stroke is dashed.
163 */
164 static void GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
165 nsSVGElement* aElement,
166 nsStyleContext* aStyleContext,
167 mozilla::SVGContextPaint* aContextPaint,
168 StrokeOptionFlags aFlags = eAllStrokeOptions);
169
170 /**
171 * Returns the current computed value of the CSS property 'stroke-width' for
172 * the given element. aStyleContext may be provided as an optimization.
173 * aContextPaint is also optional.
174 *
175 * Note that this function does NOT take account of the value of the 'stroke'
176 * and 'stroke-opacity' properties to, say, return zero if they are "none" or
177 * "0", respectively.
178 */
179 static Float GetStrokeWidth(nsSVGElement* aElement,
180 nsStyleContext* aStyleContext,
181 mozilla::SVGContextPaint* aContextPaint);
182
183 /*
184 * Get the number of CSS px (user units) per em (i.e. the em-height in user
185 * units) for an nsIContent
186 *
187 * XXX document the conditions under which these may fail, and what they
188 * return in those cases.
189 */
190 static float GetFontSize(mozilla::dom::Element* aElement);
191 static float GetFontSize(nsIFrame* aFrame);
192 static float GetFontSize(nsStyleContext* aStyleContext);
193 /*
194 * Get the number of CSS px (user units) per ex (i.e. the x-height in user
195 * units) for an nsIContent
196 *
197 * XXX document the conditions under which these may fail, and what they
198 * return in those cases.
199 */
200 static float GetFontXHeight(mozilla::dom::Element* aElement);
201 static float GetFontXHeight(nsIFrame* aFrame);
202 static float GetFontXHeight(nsStyleContext* aStyleContext);
203
204 /*
205 * Report a localized error message to the error console.
206 */
207 static nsresult ReportToConsole(nsIDocument* doc, const char* aWarning,
208 const char16_t** aParams,
209 uint32_t aParamsLength);
210
211 static Matrix GetCTM(nsSVGElement* aElement, bool aScreenCTM);
212
213 /**
214 * Gets the tight bounds-space stroke bounds of the non-scaling-stroked rect
215 * aRect.
216 * @param aToBoundsSpace transforms from source space to the space aBounds
217 * should be computed in. Must be rectilinear.
218 * @param aToNonScalingStrokeSpace transforms from source
219 * space to the space in which non-scaling stroke should be applied.
220 * Must be rectilinear.
221 */
222 static void RectilinearGetStrokeBounds(const Rect& aRect,
223 const Matrix& aToBoundsSpace,
224 const Matrix& aToNonScalingStrokeSpace,
225 float aStrokeWidth, Rect* aBounds);
226
227 /**
228 * Check if this is one of the SVG elements that SVG 1.1 Full says
229 * establishes a viewport: svg, symbol, image or foreignObject.
230 */
231 static bool EstablishesViewport(nsIContent* aContent);
232
233 static mozilla::dom::SVGViewportElement* GetNearestViewportElement(
234 const nsIContent* aContent);
235
236 /* enum for specifying coordinate direction for ObjectSpace/UserSpace */
237 enum ctxDirection { X, Y, XY };
238
239 /**
240 * Computes sqrt((aWidth^2 + aHeight^2)/2);
241 */
242 static double ComputeNormalizedHypotenuse(double aWidth, double aHeight);
243
244 /* Returns the angle halfway between the two specified angles */
245 static float AngleBisect(float a1, float a2);
246
247 /* Generate a viewbox to viewport tranformation matrix */
248
249 static Matrix GetViewBoxTransform(
250 float aViewportWidth, float aViewportHeight, float aViewboxX,
251 float aViewboxY, float aViewboxWidth, float aViewboxHeight,
252 const SVGAnimatedPreserveAspectRatio& aPreserveAspectRatio);
253
254 static Matrix GetViewBoxTransform(
255 float aViewportWidth, float aViewportHeight, float aViewboxX,
256 float aViewboxY, float aViewboxWidth, float aViewboxHeight,
257 const SVGPreserveAspectRatio& aPreserveAspectRatio);
258
259 static mozilla::RangedPtr<const char16_t> GetStartRangedPtr(
260 const nsAString& aString);
261
262 static mozilla::RangedPtr<const char16_t> GetEndRangedPtr(
263 const nsAString& aString);
264
265 /**
266 * True if 'aCh' is a decimal digit.
267 */
IsDigit(char16_t aCh)268 static inline bool IsDigit(char16_t aCh) { return aCh >= '0' && aCh <= '9'; }
269
270 /**
271 * Assuming that 'aCh' is a decimal digit, return its numeric value.
272 */
DecimalDigitValue(char16_t aCh)273 static inline uint32_t DecimalDigitValue(char16_t aCh) {
274 MOZ_ASSERT(IsDigit(aCh), "Digit expected");
275 return aCh - '0';
276 }
277
278 /**
279 * Parses the sign (+ or -) of a number and moves aIter to the next
280 * character if a sign is found.
281 * @param aSignMultiplier [outparam] -1 if the sign is negative otherwise 1
282 * @return false if we hit the end of the string (i.e. if aIter is initially
283 * at aEnd, or if we reach aEnd right after the sign character).
284 */
ParseOptionalSign(mozilla::RangedPtr<const char16_t> & aIter,const mozilla::RangedPtr<const char16_t> & aEnd,int32_t & aSignMultiplier)285 static inline bool ParseOptionalSign(
286 mozilla::RangedPtr<const char16_t>& aIter,
287 const mozilla::RangedPtr<const char16_t>& aEnd,
288 int32_t& aSignMultiplier) {
289 if (aIter == aEnd) {
290 return false;
291 }
292 aSignMultiplier = *aIter == '-' ? -1 : 1;
293
294 mozilla::RangedPtr<const char16_t> iter(aIter);
295
296 if (*iter == '-' || *iter == '+') {
297 ++iter;
298 if (iter == aEnd) {
299 return false;
300 }
301 }
302 aIter = iter;
303 return true;
304 }
305
306 /**
307 * Parse a number of the form:
308 * number ::= integer ([Ee] integer)? | [+-]? [0-9]* "." [0-9]+ ([Ee]
309 * integer)? Parsing fails if the number cannot be represented by a floatType.
310 * If parsing succeeds, aIter is updated so that it points to the character
311 * after the end of the number, otherwise it is left unchanged
312 */
313 template <class floatType>
314 static bool ParseNumber(mozilla::RangedPtr<const char16_t>& aIter,
315 const mozilla::RangedPtr<const char16_t>& aEnd,
316 floatType& aValue);
317
318 /**
319 * Parse a number of the form:
320 * number ::= integer ([Ee] integer)? | [+-]? [0-9]* "." [0-9]+ ([Ee]
321 * integer)? Parsing fails if there is anything left over after the number, or
322 * the number cannot be represented by a floatType.
323 */
324 template <class floatType>
325 static bool ParseNumber(const nsAString& aString, floatType& aValue);
326
327 /**
328 * Parse an integer of the form:
329 * integer ::= [+-]? [0-9]+
330 * The returned number is clamped to an int32_t if outside that range.
331 * If parsing succeeds, aIter is updated so that it points to the character
332 * after the end of the number, otherwise it is left unchanged
333 */
334 static bool ParseInteger(mozilla::RangedPtr<const char16_t>& aIter,
335 const mozilla::RangedPtr<const char16_t>& aEnd,
336 int32_t& aValue);
337
338 /**
339 * Parse an integer of the form:
340 * integer ::= [+-]? [0-9]+
341 * The returned number is clamped to an int32_t if outside that range.
342 * Parsing fails if there is anything left over after the number.
343 */
344 static bool ParseInteger(const nsAString& aString, int32_t& aValue);
345
346 /**
347 * Converts an nsStyleCoord into a userspace value. Handles units
348 * Factor (straight userspace), Coord (dimensioned), and Percent (of
349 * aContent's SVG viewport)
350 */
351 static float CoordToFloat(nsSVGElement* aContent, const nsStyleCoord& aCoord);
352 /**
353 * Parse the SVG path string
354 * Returns a path
355 * string formatted as an SVG path
356 */
357 static already_AddRefed<mozilla::gfx::Path> GetPath(
358 const nsAString& aPathString);
359
360 /**
361 * Returns true if aContent is one of the elements whose stroke is guaranteed
362 * to have no corners: circle or ellipse
363 */
364 static bool ShapeTypeHasNoCorners(const nsIContent* aContent);
365 };
366
367 #endif
368