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