diff options
Diffstat (limited to 'layout/svg/SVGUtils.h')
-rw-r--r-- | layout/svg/SVGUtils.h | 622 |
1 files changed, 622 insertions, 0 deletions
diff --git a/layout/svg/SVGUtils.h b/layout/svg/SVGUtils.h new file mode 100644 index 0000000000..f28879b796 --- /dev/null +++ b/layout/svg/SVGUtils.h @@ -0,0 +1,622 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef LAYOUT_SVG_SVGUTILS_H_ +#define LAYOUT_SVG_SVGUTILS_H_ + +// include math.h to pick up definition of M_ maths defines e.g. M_PI +#include <math.h> + +#include "DrawMode.h" +#include "ImgDrawResult.h" +#include "gfx2DGlue.h" +#include "gfxMatrix.h" +#include "gfxPoint.h" +#include "gfxRect.h" +#include "mozilla/gfx/Rect.h" +#include "nsAlgorithm.h" +#include "nsChangeHint.h" +#include "nsColor.h" +#include "nsCOMPtr.h" +#include "nsID.h" +#include "nsIFrame.h" +#include "nsISupports.h" +#include "nsMathUtils.h" +#include "nsStyleStruct.h" +#include <algorithm> + +class gfxContext; +class nsFrameList; +class nsIContent; + +class nsPresContext; +class nsTextFrame; + +struct nsStyleSVG; +struct nsRect; + +namespace mozilla { +class SVGAnimatedEnumeration; +class SVGAnimatedLength; +class SVGContextPaint; +struct SVGContextPaintImpl; +class SVGDisplayContainerFrame; +class SVGGeometryFrame; +class SVGOuterSVGFrame; +namespace dom { +class Element; +class SVGElement; +class UserSpaceMetrics; +} // namespace dom +namespace gfx { +class DrawTarget; +class GeneralPattern; +} // namespace gfx +} // namespace mozilla + +// maximum dimension of an offscreen surface - choose so that +// the surface size doesn't overflow a 32-bit signed int using +// 4 bytes per pixel; in line with Factory::CheckSurfaceSize +// In fact Macs can't even manage that +#define NS_SVG_OFFSCREEN_MAX_DIMENSION 4096 + +#define SVG_HIT_TEST_FILL 0x01 +#define SVG_HIT_TEST_STROKE 0x02 + +bool NS_SVGNewGetBBoxEnabled(); + +namespace mozilla { + +/** + * Sometimes we need to distinguish between an empty box and a box + * that contains an element that has no size e.g. a point at the origin. + */ +class SVGBBox final { + using Rect = gfx::Rect; + + public: + SVGBBox() : mIsEmpty(true) {} + + MOZ_IMPLICIT SVGBBox(const Rect& aRect) : mBBox(aRect), mIsEmpty(false) {} + + MOZ_IMPLICIT SVGBBox(const gfxRect& aRect) + : mBBox(ToRect(aRect)), mIsEmpty(false) {} + + operator const Rect&() { return mBBox; } + + gfxRect ToThebesRect() const { return ThebesRect(mBBox); } + + bool IsEmpty() const { return mIsEmpty; } + + bool IsFinite() const { return mBBox.IsFinite(); } + + void Scale(float aScale) { mBBox.Scale(aScale); } + + void UnionEdges(const SVGBBox& aSVGBBox) { + if (aSVGBBox.mIsEmpty) { + return; + } + mBBox = mIsEmpty ? aSVGBBox.mBBox : mBBox.UnionEdges(aSVGBBox.mBBox); + mIsEmpty = false; + } + + void Intersect(const SVGBBox& aSVGBBox) { + if (!mIsEmpty && !aSVGBBox.mIsEmpty) { + mBBox = mBBox.Intersect(aSVGBBox.mBBox); + if (mBBox.IsEmpty()) { + mIsEmpty = true; + mBBox = Rect(0, 0, 0, 0); + } + } else { + mIsEmpty = true; + mBBox = Rect(0, 0, 0, 0); + } + } + + private: + Rect mBBox; + bool mIsEmpty; +}; + +// GRRR WINDOWS HATE HATE HATE +#undef CLIP_MASK + +class MOZ_RAII SVGAutoRenderState final { + using DrawTarget = gfx::DrawTarget; + + public: + explicit SVGAutoRenderState(DrawTarget* aDrawTarget); + ~SVGAutoRenderState(); + + void SetPaintingToWindow(bool aPaintingToWindow); + + static bool IsPaintingToWindow(DrawTarget* aDrawTarget); + + private: + DrawTarget* mDrawTarget; + void* mOriginalRenderState; + bool mPaintingToWindow; +}; + +/** + * General functions used by all of SVG layout and possibly content code. + * If a method is used by content and depends only on other content methods + * it should go in SVGContentUtils instead. + */ +class SVGUtils final { + public: + using Element = dom::Element; + using SVGElement = dom::SVGElement; + using AntialiasMode = gfx::AntialiasMode; + using DrawTarget = gfx::DrawTarget; + using FillRule = gfx::FillRule; + using GeneralPattern = gfx::GeneralPattern; + using Size = gfx::Size; + using imgDrawingParams = image::imgDrawingParams; + + NS_DECLARE_FRAME_PROPERTY_DELETABLE(ObjectBoundingBoxProperty, gfxRect) + + /** + * Returns the frame's post-filter ink overflow rect when passed the + * frame's pre-filter ink overflow rect. If the frame is not currently + * being filtered, this function simply returns aUnfilteredRect. + */ + static nsRect GetPostFilterInkOverflowRect(nsIFrame* aFrame, + const nsRect& aPreFilterRect); + + /** + * Schedules an update of the frame's bounds (which will in turn invalidate + * the new area that the frame should paint to). + * + * This does nothing when passed an NS_FRAME_IS_NONDISPLAY frame. + * In future we may want to allow ReflowSVG to be called on such frames, + * but that would be better implemented as a ForceReflowSVG function to + * be called synchronously while painting them without marking or paying + * attention to dirty bits like this function. + * + * This is very similar to PresShell::FrameNeedsReflow. The main reason that + * we have this function instead of using FrameNeedsReflow is because we need + * to be able to call it under SVGOuterSVGFrame::NotifyViewportChange when + * that function is called by SVGOuterSVGFrame::Reflow. FrameNeedsReflow + * is not suitable for calling during reflow though, and it asserts as much. + * The reason that we want to be callable under NotifyViewportChange is + * because we want to synchronously notify and dirty the SVGOuterSVGFrame's + * children so that when SVGOuterSVGFrame::DidReflow is called its children + * will be updated for the new size as appropriate. Otherwise we'd have to + * post an event to the event loop to mark dirty flags and request an update. + * + * Another reason that we don't currently want to call + * PresShell::FrameNeedsReflow is because passing eRestyle to it to get it to + * mark descendants dirty would cause it to descend through + * SVGForeignObjectFrame frames to mark their children dirty, but we want to + * handle SVGForeignObjectFrame specially. It would also do unnecessary work + * descending into NS_FRAME_IS_NONDISPLAY frames. + */ + static void ScheduleReflowSVG(nsIFrame* aFrame); + + /** + * Returns true if the frame or any of its children need ReflowSVG + * to be called on them. + */ + static bool NeedsReflowSVG(const nsIFrame* aFrame); + + /** + * Percentage lengths in SVG are resolved against the width/height of the + * nearest viewport (or its viewBox, if set). This helper returns the size + * of this "context" for the given frame so that percentage values can be + * resolved. + */ + static Size GetContextSize(const nsIFrame* aFrame); + + /* Computes the input length in terms of object space coordinates. + Input: rect - bounding box + length - length to be converted + */ + static float ObjectSpace(const gfxRect& aRect, + const SVGAnimatedLength* aLength); + + /* Computes the input length in terms of user space coordinates. + Input: content - object to be used for determining user space + Input: length - length to be converted + */ + static float UserSpace(nsIFrame* aNonSVGContext, + const SVGAnimatedLength* aLength); + static float UserSpace(const dom::UserSpaceMetrics& aMetrics, + const SVGAnimatedLength* aLength); + + /* Find the outermost SVG frame of the passed frame */ + static SVGOuterSVGFrame* GetOuterSVGFrame(nsIFrame* aFrame); + + /** + * Get the covered region for a frame. Return null if it's not an SVG frame. + * @param aRect gets a rectangle in app units + * @return the outer SVG frame which aRect is relative to + */ + static nsIFrame* GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, + nsRect* aRect); + + /* Paint SVG frame with SVG effects + */ + static void PaintFrameWithEffects(nsIFrame* aFrame, gfxContext& aContext, + const gfxMatrix& aTransform, + imgDrawingParams& aImgParams); + + /* Hit testing - check if point hits the clipPath of indicated + * frame. Returns true if no clipPath set. */ + static bool HitTestClip(nsIFrame* aFrame, const gfxPoint& aPoint); + + /* + * Returns the CanvasTM of the indicated frame, whether it's a + * child SVG frame, container SVG frame, or a regular frame. + * For regular frames, we just return an identity matrix. + */ + static gfxMatrix GetCanvasTM(nsIFrame* aFrame); + + /* + * Returns whether the frame is transformed and what those transforms are. + */ + static bool IsSVGTransformed(const nsIFrame* aFrame, + gfx::Matrix* aOwnTransform, + gfx::Matrix* aFromParentTransform); + + /** + * Notify the descendants of aFrame of a change to one of their ancestors + * that might affect them. + */ + static void NotifyChildrenOfSVGChange(nsIFrame* aFrame, uint32_t aFlags); + + /* + * Convert a surface size to an integer for use by thebes + * possibly making it smaller in the process so the surface does not + * use excessive memory. + * + * @param aSize the desired surface size + * @param aResultOverflows true if the desired surface size is too big + * @return the surface size to use + */ + static gfx::IntSize ConvertToSurfaceSize(const gfxSize& aSize, + bool* aResultOverflows); + + /* + * Hit test a given rectangle/matrix. + */ + static bool HitTestRect(const gfx::Matrix& aMatrix, float aRX, float aRY, + float aRWidth, float aRHeight, float aX, float aY); + + /** + * Get the clip rect for the given frame, taking into account the CSS 'clip' + * property. See: + * http://www.w3.org/TR/SVG11/masking.html#OverflowAndClipProperties + * The arguments for aX, aY, aWidth and aHeight should be the dimensions of + * the viewport established by aFrame. + */ + static gfxRect GetClipRectForFrame(const nsIFrame* aFrame, float aX, float aY, + float aWidth, float aHeight); + + /* Using group opacity instead of fill or stroke opacity on a + * geometry object seems to be a common authoring mistake. If we're + * not applying filters and not both stroking and filling, we can + * generate the same result without going through the overhead of a + * push/pop group. */ + static bool CanOptimizeOpacity(const nsIFrame* aFrame); + + /** + * Take the CTM to userspace for an element, and adjust it to a CTM to its + * object bounding box space if aUnits is SVG_UNIT_TYPE_OBJECTBOUNDINGBOX. + * (I.e. so that [0,0] is at the top left of its bbox, and [1,1] is at the + * bottom right of its bbox). + * + * If the bbox is empty, this will return a singular matrix. + * + * @param aFlags One or more of the BBoxFlags values defined below. + */ + static gfxMatrix AdjustMatrixForUnits(const gfxMatrix& aMatrix, + const SVGAnimatedEnumeration* aUnits, + nsIFrame* aFrame, uint32_t aFlags); + + enum BBoxFlags { + eBBoxIncludeFill = 1 << 0, + // Include the geometry of the fill even when the fill does not + // actually render (e.g. when fill="none" or fill-opacity="0") + eBBoxIncludeFillGeometry = 1 << 1, + eBBoxIncludeStroke = 1 << 2, + // Include the geometry of the stroke even when the stroke does not + // actually render (e.g. when stroke="none" or stroke-opacity="0") + eBBoxIncludeStrokeGeometry = 1 << 3, + eBBoxIncludeMarkers = 1 << 4, + eBBoxIncludeClipped = 1 << 5, + // Normally a getBBox call on outer-<svg> should only return the + // bounds of the elements children. This flag will cause the + // element's bounds to be returned instead. + eUseFrameBoundsForOuterSVG = 1 << 6, + // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect + eForGetClientRects = 1 << 7, + // If the given frame is an HTML element, only include the region of the + // given frame, instead of all continuations of it, while computing bbox if + // this flag is set. + eIncludeOnlyCurrentFrameForNonSVGElement = 1 << 8, + // This flag is only has an effect when the target is a <use> element. + // getBBox returns the bounds of the elements children in user space if + // this flag is set; Otherwise, getBBox returns the union bounds in + // the coordinate system formed by the <use> element. + eUseUserSpaceOfUseElement = 1 << 9, + // For a frame with a clip-path, if this flag is set then the result + // will not be clipped to the bbox of the content inside the clip-path. + eDoNotClipToBBoxOfContentInsideClipPath = 1 << 10, + }; + /** + * This function in primarily for implementing the SVG DOM function getBBox() + * and the SVG attribute value 'objectBoundingBox'. However, it has been + * extended with various extra parameters in order to become more of a + * general purpose getter of all sorts of bounds that we might need to obtain + * for SVG elements, or even for other elements that have SVG effects applied + * to them. + * + * @param aFrame The frame of the element for which the bounds are to be + * obtained. + * @param aFlags One or more of the BBoxFlags values defined above. + * @param aToBoundsSpace If not specified the returned rect is in aFrame's + * element's "user space". A matrix can optionally be pass to specify a + * transform from aFrame's user space to the bounds space of interest + * (typically this will be the ancestor SVGOuterSVGFrame, but it could be + * to any other coordinate space). + */ + static gfxRect GetBBox(nsIFrame* aFrame, + // If the default arg changes, update the handling for + // ObjectBoundingBoxProperty() in the implementation. + uint32_t aFlags = eBBoxIncludeFillGeometry, + const gfxMatrix* aToBoundsSpace = nullptr); + + /* + * "User space" is the space that the frame's BBox (as calculated by + * SVGUtils::GetBBox) is in. "Frame space" is the space that has its origin + * at the top left of the union of the frame's border-box rects over all + * continuations. + * This function returns the offset one needs to add to something in frame + * space in order to get its coordinates in user space. + */ + static gfxPoint FrameSpaceInCSSPxToUserSpaceOffset(const nsIFrame* aFrame); + + /** + * Convert a userSpaceOnUse/objectBoundingBoxUnits rectangle that's specified + * using four SVGAnimatedLength values into a user unit rectangle in user + * space. + * + * @param aXYWH pointer to 4 consecutive SVGAnimatedLength objects containing + * the x, y, width and height values in that order + * @param aBBox the bounding box of the object the rect is relative to; + * may be null if aUnits is not SVG_UNIT_TYPE_OBJECTBOUNDINGBOX + * @param aFrame the object in which to interpret user-space units; + * may be null if aUnits is SVG_UNIT_TYPE_OBJECTBOUNDINGBOX + */ + static gfxRect GetRelativeRect(uint16_t aUnits, + const SVGAnimatedLength* aXYWH, + const gfxRect& aBBox, nsIFrame* aFrame); + + static gfxRect GetRelativeRect(uint16_t aUnits, + const SVGAnimatedLength* aXYWH, + const gfxRect& aBBox, + const dom::UserSpaceMetrics& aMetrics); + + static bool OuterSVGIsCallingReflowSVG(nsIFrame* aFrame); + static bool AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame); + + /** + * See https://svgwg.org/svg2-draft/painting.html#NonScalingStroke + * + * If the computed value of the 'vector-effect' property on aFrame is + * 'non-scaling-stroke', then this function will set aUserToOuterSVG to the + * transform from aFrame's SVG user space to the initial coordinate system + * established by the viewport of aFrame's outer-<svg>'s (the coordinate + * system in which the stroke is fixed). If aUserToOuterSVG is set to a + * non-identity matrix this function returns true, else it returns false. + */ + static bool GetNonScalingStrokeTransform(const nsIFrame* aFrame, + gfxMatrix* aUserToOuterSVG); + + /** + * Compute the maximum possible device space stroke extents of a path given + * the path's device space path extents, its stroke style and its ctm. + * + * This is a workaround for the lack of suitable cairo API for getting the + * tight device space stroke extents of a path. This basically gives us the + * tightest extents that we can guarantee fully enclose the inked stroke + * without doing the calculations for the actual tight extents. We exploit + * the fact that cairo does have an API for getting the tight device space + * fill/path extents. + * + * This should die once bug 478152 is fixed. + */ + static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, + const nsTextFrame* aFrame, + const gfxMatrix& aMatrix); + static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, + const SVGGeometryFrame* aFrame, + const gfxMatrix& aMatrix); + + /** + * Convert a floating-point value to a 32-bit integer value, clamping to + * the range of valid integers. + */ + static int32_t ClampToInt(double aVal) { + return NS_lround( + std::max(double(INT32_MIN), std::min(double(INT32_MAX), aVal))); + } + + /** + * Convert a floating-point value to a 64-bit integer value, clamping to + * the lowest and highest integers that can be safely compared to a double. + */ + static int64_t ClampToInt64(double aVal) { + return static_cast<int64_t>( + std::clamp<double>(aVal, INT64_MIN, std::nexttoward(INT64_MAX, 0))); + } + + static nscolor GetFallbackOrPaintColor( + const ComputedStyle&, StyleSVGPaint nsStyleSVG::*aFillOrStroke, + nscolor aDefaultContextFallbackColor); + + static void MakeFillPatternFor(nsIFrame* aFrame, gfxContext* aContext, + GeneralPattern* aOutPattern, + imgDrawingParams& aImgParams, + SVGContextPaint* aContextPaint = nullptr); + + static void MakeStrokePatternFor(nsIFrame* aFrame, gfxContext* aContext, + GeneralPattern* aOutPattern, + imgDrawingParams& aImgParams, + SVGContextPaint* aContextPaint = nullptr); + + static float GetOpacity(const StyleSVGOpacity&, const SVGContextPaint*); + + /* + * @return false if there is no stroke + */ + static bool HasStroke(const nsIFrame* aFrame, + const SVGContextPaint* aContextPaint = nullptr); + + static float GetStrokeWidth(const nsIFrame* aFrame, + const SVGContextPaint* aContextPaint = nullptr); + + /* + * Set up a context for a stroked path (including any dashing that applies). + */ + static void SetupStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext, + SVGContextPaint* aContextPaint = nullptr); + + /** + * This function returns a set of bit flags indicating which parts of the + * element (fill, stroke, bounds) should intercept pointer events. It takes + * into account the type of element and the value of the 'pointer-events' + * property on the element. + */ + static uint16_t GetGeometryHitTestFlags(const nsIFrame* aFrame); + + static FillRule ToFillRule(StyleFillRule aFillRule) { + return aFillRule == StyleFillRule::Evenodd ? FillRule::FILL_EVEN_ODD + : FillRule::FILL_WINDING; + } + + static AntialiasMode ToAntialiasMode(StyleTextRendering aTextRendering) { + return aTextRendering == StyleTextRendering::Optimizespeed + ? AntialiasMode::NONE + : AntialiasMode::SUBPIXEL; + } + + /** + * Render a SVG glyph. + * @param aElement the SVG glyph element to render + * @param aContext the thebes aContext to draw to + * @return true if rendering succeeded + */ + static void PaintSVGGlyph(Element* aElement, gfxContext* aContext); + + /** + * Get the extents of a SVG glyph. + * @param aElement the SVG glyph element + * @param aSVGToAppSpace the matrix mapping the SVG glyph space to the + * target context space + * @param aResult the result (valid when true is returned) + * @return true if calculating the extents succeeded + */ + static bool GetSVGGlyphExtents(const Element* aElement, + const gfxMatrix& aSVGToAppSpace, + gfxRect* aResult); + + /** + * Returns the app unit canvas bounds of a userspace rect. + * + * @param aToCanvas Transform from userspace to canvas device space. + */ + static nsRect ToCanvasBounds(const gfxRect& aUserspaceRect, + const gfxMatrix& aToCanvas, + const nsPresContext* presContext); + + struct MaskUsage; + static MaskUsage DetermineMaskUsage(const nsIFrame* aFrame, + bool aHandleOpacity); + + struct MOZ_STACK_CLASS MaskUsage { + friend MaskUsage SVGUtils::DetermineMaskUsage(const nsIFrame* aFrame, + bool aHandleOpacity); + + bool ShouldGenerateMaskLayer() const { return mShouldGenerateMaskLayer; } + + bool ShouldGenerateClipMaskLayer() const { + return mShouldGenerateClipMaskLayer; + } + + bool ShouldGenerateLayer() const { + return mShouldGenerateMaskLayer || mShouldGenerateClipMaskLayer; + } + + bool ShouldGenerateMask() const { + return mShouldGenerateMaskLayer || mShouldGenerateClipMaskLayer || + !IsOpaque(); + } + + bool ShouldApplyClipPath() const { return mShouldApplyClipPath; } + + bool HasSVGClip() const { + return mShouldGenerateClipMaskLayer || mShouldApplyClipPath; + } + + bool ShouldApplyBasicShapeOrPath() const { + return mShouldApplyBasicShapeOrPath; + } + + bool IsSimpleClipShape() const { return mIsSimpleClipShape; } + + bool IsOpaque() const { return mOpacity == 1.0f; } + + bool IsTransparent() const { return mOpacity == 0.0f; } + + float Opacity() const { return mOpacity; } + + bool UsingMaskOrClipPath() const { + return mShouldGenerateMaskLayer || mShouldGenerateClipMaskLayer || + mShouldApplyClipPath || mShouldApplyBasicShapeOrPath; + } + + bool ShouldDoSomething() const { + return mShouldGenerateMaskLayer || mShouldGenerateClipMaskLayer || + mShouldApplyClipPath || mShouldApplyBasicShapeOrPath || + mOpacity != 1.0f; + } + + private: + MaskUsage() = default; + + float mOpacity = 0.0f; + bool mShouldGenerateMaskLayer = false; + bool mShouldGenerateClipMaskLayer = false; + bool mShouldApplyClipPath = false; + bool mShouldApplyBasicShapeOrPath = false; + bool mIsSimpleClipShape = false; + }; + + static float ComputeOpacity(const nsIFrame* aFrame, bool aHandleOpacity); + + /** + * SVG frames expect to paint in SVG user units, which are equal to CSS px + * units. This method provides a transform matrix to multiply onto a + * gfxContext's current transform to convert the context's current units from + * its usual dev pixels to SVG user units/CSS px to keep the SVG code happy. + */ + static gfxMatrix GetCSSPxToDevPxMatrix(const nsIFrame* aNonSVGFrame); + + /** + * It is a replacement of + * SVGElement::PrependLocalTransformsTo(eUserSpaceToParent). + * If no CSS transform is involved, they should behave exactly the same; + * if there are CSS transforms, this one will take them into account + * while SVGElement::PrependLocalTransformsTo won't. + */ + static gfxMatrix GetTransformMatrixInUserSpace(const nsIFrame* aFrame); +}; + +} // namespace mozilla + +#endif // LAYOUT_SVG_SVGUTILS_H_ |