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