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/. */
7 #ifndef LAYOUT_SVG_SVGUTILS_H_
8 #define LAYOUT_SVG_SVGUTILS_H_
10 // include math.h to pick up definition of M_ maths defines e.g. M_PI
14 #include "ImgDrawResult.h"
15 #include "gfx2DGlue.h"
16 #include "gfxMatrix.h"
19 #include "mozilla/gfx/Rect.h"
20 #include "nsAlgorithm.h"
21 #include "nsChangeHint.h"
26 #include "nsISupports.h"
27 #include "nsMathUtils.h"
28 #include "nsStyleStruct.h"
42 class SVGAnimatedEnumeration
;
43 class SVGAnimatedLength
;
44 class SVGContextPaint
;
45 struct SVGContextPaintImpl
;
46 class SVGDisplayContainerFrame
;
47 class SVGGeometryFrame
;
48 class SVGOuterSVGFrame
;
52 class UserSpaceMetrics
;
58 } // namespace mozilla
60 // maximum dimension of an offscreen surface - choose so that
61 // the surface size doesn't overflow a 32-bit signed int using
62 // 4 bytes per pixel; in line with Factory::CheckSurfaceSize
63 // In fact Macs can't even manage that
64 #define NS_SVG_OFFSCREEN_MAX_DIMENSION 4096
66 #define SVG_HIT_TEST_FILL 0x01
67 #define SVG_HIT_TEST_STROKE 0x02
68 #define SVG_HIT_TEST_CHECK_MRECT 0x04
70 bool NS_SVGDisplayListHitTestingEnabled();
71 bool NS_SVGDisplayListPaintingEnabled();
72 bool NS_SVGNewGetBBoxEnabled();
77 * Sometimes we need to distinguish between an empty box and a box
78 * that contains an element that has no size e.g. a point at the origin.
81 using Rect
= gfx::Rect
;
84 SVGBBox() : mIsEmpty(true) {}
86 MOZ_IMPLICIT
SVGBBox(const Rect
& aRect
) : mBBox(aRect
), mIsEmpty(false) {}
88 MOZ_IMPLICIT
SVGBBox(const gfxRect
& aRect
)
89 : mBBox(ToRect(aRect
)), mIsEmpty(false) {}
91 operator const Rect
&() { return mBBox
; }
93 gfxRect
ToThebesRect() const { return ThebesRect(mBBox
); }
95 bool IsEmpty() const { return mIsEmpty
; }
97 bool IsFinite() const { return mBBox
.IsFinite(); }
99 void Scale(float aScale
) { mBBox
.Scale(aScale
); }
100 void MoveBy(float x
, float y
) { mBBox
.MoveBy(x
, y
); }
102 void UnionEdges(const SVGBBox
& aSVGBBox
) {
103 if (aSVGBBox
.mIsEmpty
) {
106 mBBox
= mIsEmpty
? aSVGBBox
.mBBox
: mBBox
.UnionEdges(aSVGBBox
.mBBox
);
110 void Intersect(const SVGBBox
& aSVGBBox
) {
111 if (!mIsEmpty
&& !aSVGBBox
.mIsEmpty
) {
112 mBBox
= mBBox
.Intersect(aSVGBBox
.mBBox
);
113 if (mBBox
.IsEmpty()) {
115 mBBox
= Rect(0, 0, 0, 0);
119 mBBox
= Rect(0, 0, 0, 0);
128 // GRRR WINDOWS HATE HATE HATE
131 class MOZ_RAII SVGAutoRenderState final
{
132 using DrawTarget
= gfx::DrawTarget
;
135 explicit SVGAutoRenderState(DrawTarget
* aDrawTarget
);
136 ~SVGAutoRenderState();
138 void SetPaintingToWindow(bool aPaintingToWindow
);
140 static bool IsPaintingToWindow(DrawTarget
* aDrawTarget
);
143 DrawTarget
* mDrawTarget
;
144 void* mOriginalRenderState
;
145 bool mPaintingToWindow
;
149 * General functions used by all of SVG layout and possibly content code.
150 * If a method is used by content and depends only on other content methods
151 * it should go in SVGContentUtils instead.
153 class SVGUtils final
{
155 using Element
= dom::Element
;
156 using SVGElement
= dom::SVGElement
;
157 using AntialiasMode
= gfx::AntialiasMode
;
158 using DrawTarget
= gfx::DrawTarget
;
159 using FillRule
= gfx::FillRule
;
160 using GeneralPattern
= gfx::GeneralPattern
;
161 using Size
= gfx::Size
;
162 using imgDrawingParams
= image::imgDrawingParams
;
164 NS_DECLARE_FRAME_PROPERTY_DELETABLE(ObjectBoundingBoxProperty
, gfxRect
)
167 * Returns the frame's post-filter ink overflow rect when passed the
168 * frame's pre-filter ink overflow rect. If the frame is not currently
169 * being filtered, this function simply returns aUnfilteredRect.
171 static nsRect
GetPostFilterInkOverflowRect(nsIFrame
* aFrame
,
172 const nsRect
& aPreFilterRect
);
175 * Schedules an update of the frame's bounds (which will in turn invalidate
176 * the new area that the frame should paint to).
178 * This does nothing when passed an NS_FRAME_IS_NONDISPLAY frame.
179 * In future we may want to allow ReflowSVG to be called on such frames,
180 * but that would be better implemented as a ForceReflowSVG function to
181 * be called synchronously while painting them without marking or paying
182 * attention to dirty bits like this function.
184 * This is very similar to PresShell::FrameNeedsReflow. The main reason that
185 * we have this function instead of using FrameNeedsReflow is because we need
186 * to be able to call it under SVGOuterSVGFrame::NotifyViewportChange when
187 * that function is called by SVGOuterSVGFrame::Reflow. FrameNeedsReflow
188 * is not suitable for calling during reflow though, and it asserts as much.
189 * The reason that we want to be callable under NotifyViewportChange is
190 * because we want to synchronously notify and dirty the SVGOuterSVGFrame's
191 * children so that when SVGOuterSVGFrame::DidReflow is called its children
192 * will be updated for the new size as appropriate. Otherwise we'd have to
193 * post an event to the event loop to mark dirty flags and request an update.
195 * Another reason that we don't currently want to call
196 * PresShell::FrameNeedsReflow is because passing eRestyle to it to get it to
197 * mark descendants dirty would cause it to descend through
198 * SVGForeignObjectFrame frames to mark their children dirty, but we want to
199 * handle SVGForeignObjectFrame specially. It would also do unnecessary work
200 * descending into NS_FRAME_IS_NONDISPLAY frames.
202 static void ScheduleReflowSVG(nsIFrame
* aFrame
);
205 * Returns true if the frame or any of its children need ReflowSVG
206 * to be called on them.
208 static bool NeedsReflowSVG(nsIFrame
* aFrame
);
211 * Percentage lengths in SVG are resolved against the width/height of the
212 * nearest viewport (or its viewBox, if set). This helper returns the size
213 * of this "context" for the given frame so that percentage values can be
216 static Size
GetContextSize(const nsIFrame
* aFrame
);
218 /* Computes the input length in terms of object space coordinates.
219 Input: rect - bounding box
220 length - length to be converted
222 static float ObjectSpace(const gfxRect
& aRect
,
223 const SVGAnimatedLength
* aLength
);
225 /* Computes the input length in terms of user space coordinates.
226 Input: content - object to be used for determining user space
227 Input: length - length to be converted
229 static float UserSpace(SVGElement
* aSVGElement
,
230 const SVGAnimatedLength
* aLength
);
231 static float UserSpace(nsIFrame
* aNonSVGContext
,
232 const SVGAnimatedLength
* aLength
);
233 static float UserSpace(const dom::UserSpaceMetrics
& aMetrics
,
234 const SVGAnimatedLength
* aLength
);
236 /* Find the outermost SVG frame of the passed frame */
237 static SVGOuterSVGFrame
* GetOuterSVGFrame(nsIFrame
* aFrame
);
240 * Get the covered region for a frame. Return null if it's not an SVG frame.
241 * @param aRect gets a rectangle in app units
242 * @return the outer SVG frame which aRect is relative to
244 static nsIFrame
* GetOuterSVGFrameAndCoveredRegion(nsIFrame
* aFrame
,
247 /* Paint SVG frame with SVG effects - aDirtyRect is the area being
248 * redrawn, in device pixel coordinates relative to the outer svg */
249 static void PaintFrameWithEffects(nsIFrame
* aFrame
, gfxContext
& aContext
,
250 const gfxMatrix
& aTransform
,
251 imgDrawingParams
& aImgParams
,
252 const nsIntRect
* aDirtyRect
= nullptr);
254 /* Hit testing - check if point hits the clipPath of indicated
255 * frame. Returns true if no clipPath set. */
256 static bool HitTestClip(nsIFrame
* aFrame
, const gfxPoint
& aPoint
);
259 * Hit testing - check if point hits any children of aFrame. aPoint is
260 * expected to be in the coordinate space established by aFrame for its
261 * children (e.g. the space established by the 'viewBox' attribute on <svg>).
263 static nsIFrame
* HitTestChildren(SVGDisplayContainerFrame
* aFrame
,
264 const gfxPoint
& aPoint
);
267 * Returns the CanvasTM of the indicated frame, whether it's a
268 * child SVG frame, container SVG frame, or a regular frame.
269 * For regular frames, we just return an identity matrix.
271 static gfxMatrix
GetCanvasTM(nsIFrame
* aFrame
);
274 * Notify the descendants of aFrame of a change to one of their ancestors
275 * that might affect them.
277 static void NotifyChildrenOfSVGChange(nsIFrame
* aFrame
, uint32_t aFlags
);
279 static nsRect
TransformFrameRectToOuterSVG(const nsRect
& aRect
,
280 const gfxMatrix
& aMatrix
,
281 nsPresContext
* aPresContext
);
284 * Convert a surface size to an integer for use by thebes
285 * possibly making it smaller in the process so the surface does not
286 * use excessive memory.
288 * @param aSize the desired surface size
289 * @param aResultOverflows true if the desired surface size is too big
290 * @return the surface size to use
292 static gfx::IntSize
ConvertToSurfaceSize(const gfxSize
& aSize
,
293 bool* aResultOverflows
);
296 * Hit test a given rectangle/matrix.
298 static bool HitTestRect(const gfx::Matrix
& aMatrix
, float aRX
, float aRY
,
299 float aRWidth
, float aRHeight
, float aX
, float aY
);
302 * Get the clip rect for the given frame, taking into account the CSS 'clip'
304 * http://www.w3.org/TR/SVG11/masking.html#OverflowAndClipProperties
305 * The arguments for aX, aY, aWidth and aHeight should be the dimensions of
306 * the viewport established by aFrame.
308 static gfxRect
GetClipRectForFrame(nsIFrame
* aFrame
, float aX
, float aY
,
309 float aWidth
, float aHeight
);
311 static void SetClipRect(gfxContext
* aContext
, const gfxMatrix
& aCTM
,
312 const gfxRect
& aRect
);
314 /* Using group opacity instead of fill or stroke opacity on a
315 * geometry object seems to be a common authoring mistake. If we're
316 * not applying filters and not both stroking and filling, we can
317 * generate the same result without going through the overhead of a
319 static bool CanOptimizeOpacity(nsIFrame
* aFrame
);
322 * Take the CTM to userspace for an element, and adjust it to a CTM to its
323 * object bounding box space if aUnits is SVG_UNIT_TYPE_OBJECTBOUNDINGBOX.
324 * (I.e. so that [0,0] is at the top left of its bbox, and [1,1] is at the
325 * bottom right of its bbox).
327 * If the bbox is empty, this will return a singular matrix.
329 * @param aFlags One or more of the BBoxFlags values defined below.
331 static gfxMatrix
AdjustMatrixForUnits(const gfxMatrix
& aMatrix
,
332 SVGAnimatedEnumeration
* aUnits
,
333 nsIFrame
* aFrame
, uint32_t aFlags
);
336 eBBoxIncludeFill
= 1 << 0,
337 // Include the geometry of the fill even when the fill does not
338 // actually render (e.g. when fill="none" or fill-opacity="0")
339 eBBoxIncludeFillGeometry
= 1 << 1,
340 eBBoxIncludeStroke
= 1 << 2,
341 // Include the geometry of the stroke even when the stroke does not
342 // actually render (e.g. when stroke="none" or stroke-opacity="0")
343 eBBoxIncludeStrokeGeometry
= 1 << 3,
344 eBBoxIncludeMarkers
= 1 << 4,
345 eBBoxIncludeClipped
= 1 << 5,
346 // Normally a getBBox call on outer-<svg> should only return the
347 // bounds of the elements children. This flag will cause the
348 // element's bounds to be returned instead.
349 eUseFrameBoundsForOuterSVG
= 1 << 6,
350 // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
351 eForGetClientRects
= 1 << 7,
352 // If the given frame is an HTML element, only include the region of the
353 // given frame, instead of all continuations of it, while computing bbox if
355 eIncludeOnlyCurrentFrameForNonSVGElement
= 1 << 8,
356 // This flag is only has an effect when the target is a <use> element.
357 // getBBox returns the bounds of the elements children in user space if
358 // this flag is set; Otherwise, getBBox returns the union bounds in
359 // the coordinate system formed by the <use> element.
360 eUseUserSpaceOfUseElement
= 1 << 9,
361 // For a frame with a clip-path, if this flag is set then the result
362 // will not be clipped to the bbox of the content inside the clip-path.
363 eDoNotClipToBBoxOfContentInsideClipPath
= 1 << 10,
366 * This function in primarily for implementing the SVG DOM function getBBox()
367 * and the SVG attribute value 'objectBoundingBox'. However, it has been
368 * extended with various extra parameters in order to become more of a
369 * general purpose getter of all sorts of bounds that we might need to obtain
370 * for SVG elements, or even for other elements that have SVG effects applied
373 * @param aFrame The frame of the element for which the bounds are to be
375 * @param aFlags One or more of the BBoxFlags values defined above.
376 * @param aToBoundsSpace If not specified the returned rect is in aFrame's
377 * element's "user space". A matrix can optionally be pass to specify a
378 * transform from aFrame's user space to the bounds space of interest
379 * (typically this will be the ancestor SVGOuterSVGFrame, but it could be
380 * to any other coordinate space).
382 static gfxRect
GetBBox(nsIFrame
* aFrame
,
383 // If the default arg changes, update the handling for
384 // ObjectBoundingBoxProperty() in the implementation.
385 uint32_t aFlags
= eBBoxIncludeFillGeometry
,
386 const gfxMatrix
* aToBoundsSpace
= nullptr);
389 * "User space" is the space that the frame's BBox (as calculated by
390 * SVGUtils::GetBBox) is in. "Frame space" is the space that has its origin
391 * at the top left of the union of the frame's border-box rects over all
393 * This function returns the offset one needs to add to something in frame
394 * space in order to get its coordinates in user space.
396 static gfxPoint
FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame
* aFrame
);
399 * Convert a userSpaceOnUse/objectBoundingBoxUnits rectangle that's specified
400 * using four SVGAnimatedLength values into a user unit rectangle in user
403 * @param aXYWH pointer to 4 consecutive SVGAnimatedLength objects containing
404 * the x, y, width and height values in that order
405 * @param aBBox the bounding box of the object the rect is relative to;
406 * may be null if aUnits is not SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
407 * @param aFrame the object in which to interpret user-space units;
408 * may be null if aUnits is SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
410 static gfxRect
GetRelativeRect(uint16_t aUnits
,
411 const SVGAnimatedLength
* aXYWH
,
412 const gfxRect
& aBBox
, nsIFrame
* aFrame
);
414 static gfxRect
GetRelativeRect(uint16_t aUnits
,
415 const SVGAnimatedLength
* aXYWH
,
416 const gfxRect
& aBBox
,
417 const dom::UserSpaceMetrics
& aMetrics
);
420 * Find the first frame, starting with aStartFrame and going up its
421 * parent chain, that is not an svgAFrame.
423 static nsIFrame
* GetFirstNonAAncestorFrame(nsIFrame
* aStartFrame
);
425 static bool OuterSVGIsCallingReflowSVG(nsIFrame
* aFrame
);
426 static bool AnyOuterSVGIsCallingReflowSVG(nsIFrame
* aFrame
);
429 * See https://svgwg.org/svg2-draft/painting.html#NonScalingStroke
431 * If the computed value of the 'vector-effect' property on aFrame is
432 * 'non-scaling-stroke', then this function will set aUserToOuterSVG to the
433 * transform from aFrame's SVG user space to the initial coordinate system
434 * established by the viewport of aFrame's outer-<svg>'s (the coordinate
435 * system in which the stroke is fixed). If aUserToOuterSVG is set to a
436 * non-identity matrix this function returns true, else it returns false.
438 static bool GetNonScalingStrokeTransform(nsIFrame
* aFrame
,
439 gfxMatrix
* aUserToOuterSVG
);
442 * Compute the maximum possible device space stroke extents of a path given
443 * the path's device space path extents, its stroke style and its ctm.
445 * This is a workaround for the lack of suitable cairo API for getting the
446 * tight device space stroke extents of a path. This basically gives us the
447 * tightest extents that we can guarantee fully enclose the inked stroke
448 * without doing the calculations for the actual tight extents. We exploit
449 * the fact that cairo does have an API for getting the tight device space
452 * This should die once bug 478152 is fixed.
454 static gfxRect
PathExtentsToMaxStrokeExtents(const gfxRect
& aPathExtents
,
456 const gfxMatrix
& aMatrix
);
457 static gfxRect
PathExtentsToMaxStrokeExtents(const gfxRect
& aPathExtents
,
458 SVGGeometryFrame
* aFrame
,
459 const gfxMatrix
& aMatrix
);
462 * Convert a floating-point value to a 32-bit integer value, clamping to
463 * the range of valid integers.
465 static int32_t ClampToInt(double aVal
) {
467 std::max(double(INT32_MIN
), std::min(double(INT32_MAX
), aVal
)));
471 * Convert a floating-point value to a 64-bit integer value, clamping to
472 * the lowest and highest integers that can be safely compared to a double.
474 static int64_t ClampToInt64(double aVal
) {
475 return static_cast<int64_t>(
476 std::clamp
<double>(aVal
, INT64_MIN
, std::nexttoward(INT64_MAX
, 0)));
479 static nscolor
GetFallbackOrPaintColor(
480 const ComputedStyle
&, StyleSVGPaint
nsStyleSVG::*aFillOrStroke
);
482 static void MakeFillPatternFor(nsIFrame
* aFrame
, gfxContext
* aContext
,
483 GeneralPattern
* aOutPattern
,
484 imgDrawingParams
& aImgParams
,
485 SVGContextPaint
* aContextPaint
= nullptr);
487 static void MakeStrokePatternFor(nsIFrame
* aFrame
, gfxContext
* aContext
,
488 GeneralPattern
* aOutPattern
,
489 imgDrawingParams
& aImgParams
,
490 SVGContextPaint
* aContextPaint
= nullptr);
492 static float GetOpacity(const StyleSVGOpacity
&, SVGContextPaint
*);
495 * @return false if there is no stroke
497 static bool HasStroke(nsIFrame
* aFrame
,
498 SVGContextPaint
* aContextPaint
= nullptr);
500 static float GetStrokeWidth(nsIFrame
* aFrame
,
501 SVGContextPaint
* aContextPaint
= nullptr);
504 * Set up a context for a stroked path (including any dashing that applies).
506 static void SetupStrokeGeometry(nsIFrame
* aFrame
, gfxContext
* aContext
,
507 SVGContextPaint
* aContextPaint
= nullptr);
510 * This function returns a set of bit flags indicating which parts of the
511 * element (fill, stroke, bounds) should intercept pointer events. It takes
512 * into account the type of element and the value of the 'pointer-events'
513 * property on the element.
515 static uint16_t GetGeometryHitTestFlags(nsIFrame
* aFrame
);
517 static FillRule
ToFillRule(StyleFillRule aFillRule
) {
518 return aFillRule
== StyleFillRule::Evenodd
? FillRule::FILL_EVEN_ODD
519 : FillRule::FILL_WINDING
;
522 static AntialiasMode
ToAntialiasMode(StyleTextRendering aTextRendering
) {
523 return aTextRendering
== StyleTextRendering::Optimizespeed
524 ? AntialiasMode::NONE
525 : AntialiasMode::SUBPIXEL
;
529 * Render a SVG glyph.
530 * @param aElement the SVG glyph element to render
531 * @param aContext the thebes aContext to draw to
532 * @return true if rendering succeeded
534 static void PaintSVGGlyph(Element
* aElement
, gfxContext
* aContext
);
537 * Get the extents of a SVG glyph.
538 * @param aElement the SVG glyph element
539 * @param aSVGToAppSpace the matrix mapping the SVG glyph space to the
540 * target context space
541 * @param aResult the result (valid when true is returned)
542 * @return true if calculating the extents succeeded
544 static bool GetSVGGlyphExtents(Element
* aElement
,
545 const gfxMatrix
& aSVGToAppSpace
,
549 * Returns the app unit canvas bounds of a userspace rect.
551 * @param aToCanvas Transform from userspace to canvas device space.
553 static nsRect
ToCanvasBounds(const gfxRect
& aUserspaceRect
,
554 const gfxMatrix
& aToCanvas
,
555 const nsPresContext
* presContext
);
558 bool shouldGenerateMaskLayer
;
559 bool shouldGenerateClipMaskLayer
;
560 bool shouldApplyClipPath
;
561 bool shouldApplyBasicShapeOrPath
;
565 : shouldGenerateMaskLayer(false),
566 shouldGenerateClipMaskLayer(false),
567 shouldApplyClipPath(false),
568 shouldApplyBasicShapeOrPath(false),
571 bool shouldDoSomething() {
572 return shouldGenerateMaskLayer
|| shouldGenerateClipMaskLayer
||
573 shouldApplyClipPath
|| shouldApplyBasicShapeOrPath
||
578 static void DetermineMaskUsage(nsIFrame
* aFrame
, bool aHandleOpacity
,
581 static float ComputeOpacity(nsIFrame
* aFrame
, bool aHandleOpacity
);
584 * SVG frames expect to paint in SVG user units, which are equal to CSS px
585 * units. This method provides a transform matrix to multiply onto a
586 * gfxContext's current transform to convert the context's current units from
587 * its usual dev pixels to SVG user units/CSS px to keep the SVG code happy.
589 static gfxMatrix
GetCSSPxToDevPxMatrix(nsIFrame
* aNonSVGFrame
);
591 static bool IsInSVGTextSubtree(const nsIFrame
* aFrame
) {
592 // Returns true if the frame is an SVGTextFrame or one of its descendants.
593 return aFrame
->HasAnyStateBits(NS_FRAME_IS_SVG_TEXT
);
597 * It is a replacement of
598 * SVGElement::PrependLocalTransformsTo(eUserSpaceToParent).
599 * If no CSS transform is involved, they should behave exactly the same;
600 * if there are CSS transforms, this one will take them into account
601 * while SVGElement::PrependLocalTransformsTo won't.
603 static gfxMatrix
GetTransformMatrixInUserSpace(const nsIFrame
* aFrame
);
606 } // namespace mozilla
608 #endif // LAYOUT_SVG_SVGUTILS_H_