Bug 1800263 - Part 1: Tidup, make MarkingState a private enum inside GCMarker r=sfink
[gecko.git] / layout / svg / SVGUtils.h
blob4ece798437ca2c4fdcab2976e91c609f698b0a8f
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
11 #include <math.h>
13 #include "DrawMode.h"
14 #include "ImgDrawResult.h"
15 #include "gfx2DGlue.h"
16 #include "gfxMatrix.h"
17 #include "gfxPoint.h"
18 #include "gfxRect.h"
19 #include "mozilla/gfx/Rect.h"
20 #include "nsAlgorithm.h"
21 #include "nsChangeHint.h"
22 #include "nsColor.h"
23 #include "nsCOMPtr.h"
24 #include "nsID.h"
25 #include "nsIFrame.h"
26 #include "nsISupports.h"
27 #include "nsMathUtils.h"
28 #include "nsStyleStruct.h"
29 #include <algorithm>
31 class gfxContext;
32 class nsFrameList;
33 class nsIContent;
35 class nsPresContext;
36 class nsTextFrame;
38 struct nsStyleSVG;
39 struct nsRect;
41 namespace mozilla {
42 class SVGAnimatedEnumeration;
43 class SVGAnimatedLength;
44 class SVGContextPaint;
45 struct SVGContextPaintImpl;
46 class SVGDisplayContainerFrame;
47 class SVGGeometryFrame;
48 class SVGOuterSVGFrame;
49 namespace dom {
50 class Element;
51 class SVGElement;
52 class UserSpaceMetrics;
53 } // namespace dom
54 namespace gfx {
55 class DrawTarget;
56 class GeneralPattern;
57 } // namespace gfx
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();
74 namespace mozilla {
76 /**
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.
80 class SVGBBox final {
81 using Rect = gfx::Rect;
83 public:
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) {
104 return;
106 mBBox = mIsEmpty ? aSVGBBox.mBBox : mBBox.UnionEdges(aSVGBBox.mBBox);
107 mIsEmpty = false;
110 void Intersect(const SVGBBox& aSVGBBox) {
111 if (!mIsEmpty && !aSVGBBox.mIsEmpty) {
112 mBBox = mBBox.Intersect(aSVGBBox.mBBox);
113 if (mBBox.IsEmpty()) {
114 mIsEmpty = true;
115 mBBox = Rect(0, 0, 0, 0);
117 } else {
118 mIsEmpty = true;
119 mBBox = Rect(0, 0, 0, 0);
123 private:
124 Rect mBBox;
125 bool mIsEmpty;
128 // GRRR WINDOWS HATE HATE HATE
129 #undef CLIP_MASK
131 class MOZ_RAII SVGAutoRenderState final {
132 using DrawTarget = gfx::DrawTarget;
134 public:
135 explicit SVGAutoRenderState(DrawTarget* aDrawTarget);
136 ~SVGAutoRenderState();
138 void SetPaintingToWindow(bool aPaintingToWindow);
140 static bool IsPaintingToWindow(DrawTarget* aDrawTarget);
142 private:
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 {
154 public:
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
214 * resolved.
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,
245 nsRect* aRect);
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'
303 * property. See:
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
318 * push/pop group. */
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);
335 enum BBoxFlags {
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
354 // this flag is set.
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
371 * to them.
373 * @param aFrame The frame of the element for which the bounds are to be
374 * obtained.
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
392 * continuations.
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
401 * space.
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
450 * fill/path extents.
452 * This should die once bug 478152 is fixed.
454 static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
455 nsTextFrame* aFrame,
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) {
466 return NS_lround(
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,
546 gfxRect* aResult);
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);
557 struct MaskUsage {
558 bool shouldGenerateMaskLayer;
559 bool shouldGenerateClipMaskLayer;
560 bool shouldApplyClipPath;
561 bool shouldApplyBasicShapeOrPath;
562 float opacity;
564 MaskUsage()
565 : shouldGenerateMaskLayer(false),
566 shouldGenerateClipMaskLayer(false),
567 shouldApplyClipPath(false),
568 shouldApplyBasicShapeOrPath(false),
569 opacity(0.0) {}
571 bool shouldDoSomething() {
572 return shouldGenerateMaskLayer || shouldGenerateClipMaskLayer ||
573 shouldApplyClipPath || shouldApplyBasicShapeOrPath ||
574 opacity != 1.0;
578 static void DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity,
579 MaskUsage& aUsage);
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_