/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/. */ // Main header first: // This is also necessary to ensure our definition of M_SQRT1_2 is picked up #include "nsSVGUtils.h" // Keep others in (case-insensitive) order: #include "gfxContext.h" #include "gfxImageSurface.h" #include "gfxMatrix.h" #include "gfxPlatform.h" #include "gfxRect.h" #include "gfxUtils.h" #include "mozilla/gfx/2D.h" #include "mozilla/Preferences.h" #include "nsCSSFrameConstructor.h" #include "nsDisplayList.h" #include "nsFrameList.h" #include "nsGkAtoms.h" #include "nsIContent.h" #include "nsIDocument.h" #include "nsIDOMSVGUnitTypes.h" #include "nsIFrame.h" #include "nsINameSpaceManager.h" #include "nsIPresShell.h" #include "nsISVGChildFrame.h" #include "nsPresContext.h" #include "nsRenderingContext.h" #include "nsStyleCoord.h" #include "nsStyleStruct.h" #include "nsSVGClipPathFrame.h" #include "nsSVGContainerFrame.h" #include "nsSVGEffects.h" #include "nsSVGFilterFrame.h" #include "nsSVGFilterPaintCallback.h" #include "nsSVGForeignObjectFrame.h" #include "nsSVGGeometryFrame.h" #include "nsSVGGlyphFrame.h" #include "nsSVGInnerSVGFrame.h" #include "nsSVGIntegrationUtils.h" #include "nsSVGLength2.h" #include "nsSVGMaskFrame.h" #include "nsSVGOuterSVGFrame.h" #include "nsSVGPathElement.h" #include "nsSVGPathGeometryElement.h" #include "nsSVGPathGeometryFrame.h" #include "nsSVGPaintServerFrame.h" #include "nsSVGSVGElement.h" #include "nsSVGTextContainerFrame.h" #include "nsTextFrame.h" #include "SVGContentUtils.h" #include "mozilla/unused.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::gfx; // c = n / 255 // (c <= 0.0031308 ? c * 12.92 : 1.055 * pow(c, 1 / 2.4) - 0.055) * 255 + 0.5 static const uint8_t glinearRGBTosRGBMap[256] = { 0, 13, 22, 28, 34, 38, 42, 46, 50, 53, 56, 59, 61, 64, 66, 69, 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 106, 108, 109, 110, 112, 113, 114, 115, 117, 118, 119, 120, 121, 122, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 148, 149, 150, 151, 152, 153, 154, 155, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 163, 164, 165, 166, 167, 167, 168, 169, 170, 170, 171, 172, 173, 173, 174, 175, 175, 176, 177, 178, 178, 179, 180, 180, 181, 182, 182, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 190, 191, 192, 192, 193, 194, 194, 195, 196, 196, 197, 197, 198, 199, 199, 200, 200, 201, 202, 202, 203, 203, 204, 205, 205, 206, 206, 207, 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216, 216, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 246, 247, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255, 255 }; // c = n / 255 // c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4)) * 255 + 0.5 static const uint8_t gsRGBToLinearRGBMap[256] = { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, 30, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 37, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 90, 91, 92, 93, 95, 96, 97, 99, 100, 101, 103, 104, 105, 107, 108, 109, 111, 112, 114, 115, 116, 118, 119, 121, 122, 124, 125, 127, 128, 130, 131, 133, 134, 136, 138, 139, 141, 142, 144, 146, 147, 149, 151, 152, 154, 156, 157, 159, 161, 163, 164, 166, 168, 170, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 229, 231, 233, 235, 237, 239, 242, 244, 246, 248, 250, 253, 255 }; static bool sSMILEnabled; static bool sSVGDisplayListHitTestingEnabled; static bool sSVGDisplayListPaintingEnabled; static bool sSVGTextCSSFramesEnabled; bool NS_SMILEnabled() { return sSMILEnabled; } bool NS_SVGDisplayListHitTestingEnabled() { return sSVGDisplayListHitTestingEnabled; } bool NS_SVGDisplayListPaintingEnabled() { return sSVGDisplayListPaintingEnabled; } bool NS_SVGTextCSSFramesEnabled() { return sSVGTextCSSFramesEnabled; } // we only take the address of this: static mozilla::gfx::UserDataKey sSVGAutoRenderStateKey; SVGAutoRenderState::SVGAutoRenderState(nsRenderingContext *aContext, RenderMode aMode) : mContext(aContext) , mOriginalRenderState(nullptr) , mMode(aMode) , mPaintingToWindow(false) { mOriginalRenderState = aContext->RemoveUserData(&sSVGAutoRenderStateKey); // We always remove ourselves from aContext before it dies, so // passing nullptr as the destroy function is okay. aContext->AddUserData(&sSVGAutoRenderStateKey, this, nullptr); } SVGAutoRenderState::~SVGAutoRenderState() { mContext->RemoveUserData(&sSVGAutoRenderStateKey); if (mOriginalRenderState) { mContext->AddUserData(&sSVGAutoRenderStateKey, mOriginalRenderState, nullptr); } } void SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) { mPaintingToWindow = aPaintingToWindow; } /* static */ SVGAutoRenderState::RenderMode SVGAutoRenderState::GetRenderMode(nsRenderingContext *aContext) { void *state = aContext->GetUserData(&sSVGAutoRenderStateKey); if (state) { return static_cast(state)->mMode; } return NORMAL; } /* static */ bool SVGAutoRenderState::IsPaintingToWindow(nsRenderingContext *aContext) { void *state = aContext->GetUserData(&sSVGAutoRenderStateKey); if (state) { return static_cast(state)->mPaintingToWindow; } return false; } void nsSVGUtils::Init() { Preferences::AddBoolVarCache(&sSMILEnabled, "svg.smil.enabled", true); Preferences::AddBoolVarCache(&sSVGDisplayListHitTestingEnabled, "svg.display-lists.hit-testing.enabled"); Preferences::AddBoolVarCache(&sSVGDisplayListPaintingEnabled, "svg.display-lists.painting.enabled"); Preferences::AddBoolVarCache(&sSVGTextCSSFramesEnabled, "svg.text.css-frames.enabled"); } void nsSVGUtils::UnPremultiplyImageDataAlpha(uint8_t *data, int32_t stride, const nsIntRect &rect) { for (int32_t y = rect.y; y < rect.YMost(); y++) { for (int32_t x = rect.x; x < rect.XMost(); x++) { uint8_t *pixel = data + stride * y + 4 * x; uint8_t a = pixel[GFX_ARGB32_OFFSET_A]; if (a == 255) continue; if (a) { pixel[GFX_ARGB32_OFFSET_B] = (255 * pixel[GFX_ARGB32_OFFSET_B]) / a; pixel[GFX_ARGB32_OFFSET_G] = (255 * pixel[GFX_ARGB32_OFFSET_G]) / a; pixel[GFX_ARGB32_OFFSET_R] = (255 * pixel[GFX_ARGB32_OFFSET_R]) / a; } else { pixel[GFX_ARGB32_OFFSET_B] = 0; pixel[GFX_ARGB32_OFFSET_G] = 0; pixel[GFX_ARGB32_OFFSET_R] = 0; } } } } void nsSVGUtils::PremultiplyImageDataAlpha(uint8_t *data, int32_t stride, const nsIntRect &rect) { for (int32_t y = rect.y; y < rect.YMost(); y++) { for (int32_t x = rect.x; x < rect.XMost(); x++) { uint8_t *pixel = data + stride * y + 4 * x; uint8_t a = pixel[GFX_ARGB32_OFFSET_A]; if (a == 255) continue; FAST_DIVIDE_BY_255(pixel[GFX_ARGB32_OFFSET_B], pixel[GFX_ARGB32_OFFSET_B] * a); FAST_DIVIDE_BY_255(pixel[GFX_ARGB32_OFFSET_G], pixel[GFX_ARGB32_OFFSET_G] * a); FAST_DIVIDE_BY_255(pixel[GFX_ARGB32_OFFSET_R], pixel[GFX_ARGB32_OFFSET_R] * a); } } } void nsSVGUtils::ConvertImageDataToLinearRGB(uint8_t *data, int32_t stride, const nsIntRect &rect) { for (int32_t y = rect.y; y < rect.YMost(); y++) { for (int32_t x = rect.x; x < rect.XMost(); x++) { uint8_t *pixel = data + stride * y + 4 * x; pixel[GFX_ARGB32_OFFSET_B] = gsRGBToLinearRGBMap[pixel[GFX_ARGB32_OFFSET_B]]; pixel[GFX_ARGB32_OFFSET_G] = gsRGBToLinearRGBMap[pixel[GFX_ARGB32_OFFSET_G]]; pixel[GFX_ARGB32_OFFSET_R] = gsRGBToLinearRGBMap[pixel[GFX_ARGB32_OFFSET_R]]; } } } void nsSVGUtils::ConvertImageDataFromLinearRGB(uint8_t *data, int32_t stride, const nsIntRect &rect) { for (int32_t y = rect.y; y < rect.YMost(); y++) { for (int32_t x = rect.x; x < rect.XMost(); x++) { uint8_t *pixel = data + stride * y + 4 * x; pixel[GFX_ARGB32_OFFSET_B] = glinearRGBTosRGBMap[pixel[GFX_ARGB32_OFFSET_B]]; pixel[GFX_ARGB32_OFFSET_G] = glinearRGBTosRGBMap[pixel[GFX_ARGB32_OFFSET_G]]; pixel[GFX_ARGB32_OFFSET_R] = glinearRGBTosRGBMap[pixel[GFX_ARGB32_OFFSET_R]]; } } } float nsSVGUtils::CoordToFloat(nsPresContext *aPresContext, nsSVGElement *aContent, const nsStyleCoord &aCoord) { switch (aCoord.GetUnit()) { case eStyleUnit_Factor: // user units return aCoord.GetFactorValue(); case eStyleUnit_Coord: return nsPresContext::AppUnitsToFloatCSSPixels(aCoord.GetCoordValue()); case eStyleUnit_Percent: { nsSVGSVGElement* ctx = aContent->GetCtx(); return ctx ? aCoord.GetPercentValue() * ctx->GetLength(SVGContentUtils::XY) : 0.0f; } default: return 0.0f; } } nsSVGDisplayContainerFrame* nsSVGUtils::GetNearestSVGViewport(nsIFrame *aFrame) { NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) { return nullptr; } while ((aFrame = aFrame->GetParent())) { NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected"); if (aFrame->GetType() == nsGkAtoms::svgInnerSVGFrame || aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) { return do_QueryFrame(aFrame); } } NS_NOTREACHED("This is not reached. It's only needed to compile."); return nullptr; } nsRect nsSVGUtils::GetPostFilterVisualOverflowRect(nsIFrame *aFrame, const nsRect &aPreFilterRect) { NS_ABORT_IF_FALSE(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT, "Called on invalid frame type"); nsSVGFilterFrame *filter = nsSVGEffects::GetFilterFrame(aFrame); if (!filter) { return aPreFilterRect; } return filter->GetPostFilterBounds(aFrame, nullptr, &aPreFilterRect); } bool nsSVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame *aFrame) { return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG(); } void nsSVGUtils::InvalidateBounds(nsIFrame *aFrame, bool aDuringUpdate, const nsRect *aBoundsSubArea, uint32_t aFlags) { NS_ABORT_IF_FALSE(aFrame->IsFrameOfType(nsIFrame::eSVG) && !(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG), "Passed bad frame!"); NS_ASSERTION(aDuringUpdate == OuterSVGIsCallingReflowSVG(aFrame), "aDuringUpdate lies!"); // Rendering observers must be notified about changes to the frames that they // are observing _before_ ReflowSVG is called on the SVG frame tree, so we // only need to notify observers if we're not under an ReflowSVG call. // In fact, it would actually be wrong to notify observers while under // ReflowSVG because the observers will try to mark themselves as dirty // and, since ReflowSVG would be in the process of _removeing_ dirty bits // from frames, that would mess things up. if (!aDuringUpdate) { NS_ASSERTION(!OuterSVGIsCallingReflowSVG(aFrame), "Must not InvalidateRenderingObservers() under " "nsISVGChildFrame::ReflowSVG!"); nsSVGEffects::InvalidateRenderingObservers(aFrame); } // Must come after InvalidateRenderingObservers if (aFrame->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD) { return; } // XXXjwatt: can this come before InvalidateRenderingObservers? if (aFrame->GetStateBits() & (NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) { // Nothing to do if we're already dirty, or if the outer- // hasn't yet had its initial reflow. return; } aFrame->InvalidateFrameSubtree(); if ((aFrame->GetType() == nsGkAtoms::svgPathGeometryFrame || aFrame->GetType() == nsGkAtoms::svgGlyphFrame) && NS_SVGDisplayListPaintingEnabled()) { return; } // Okay, so now we pass the area that needs to be invalidated up our parent // chain, accounting for filter effects and transforms as we go, until we // reach our nsSVGOuterSVGFrame where we can invalidate: nsRect invalidArea; if (aBoundsSubArea) { invalidArea = *aBoundsSubArea; } else { invalidArea = aFrame->GetVisualOverflowRect(); // GetVisualOverflowRect() already includes filter effects and transforms, // so advance to our parent before the loop below: invalidArea += aFrame->GetPosition(); aFrame = aFrame->GetParent(); } int32_t appUnitsPerCSSPx = aFrame->PresContext()->AppUnitsPerCSSPixel(); while (aFrame) { if ((aFrame->GetStateBits() & NS_FRAME_IS_DIRTY)) { // This ancestor frame has already been invalidated, so nothing to do. return; } if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { break; } if (aFrame->GetType() == nsGkAtoms::svgInnerSVGFrame && aFrame->GetStyleDisplay()->IsScrollableOverflow()) { // Clip rect to the viewport established by this inner-: float x, y, width, height; static_cast(aFrame->GetContent())-> GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); if (width <= 0.0f || height <= 0.0f) { return; // Nothing to invalidate } nsRect viewportRect = nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(0.0, 0.0, width, height), appUnitsPerCSSPx); invalidArea = invalidArea.Intersect(viewportRect); if (invalidArea.IsEmpty()) { return; // Nothing to invalidate } } nsSVGFilterFrame *filterFrame = nsSVGEffects::GetFilterFrame(aFrame); if (filterFrame) { invalidArea = filterFrame->GetPostFilterDirtyArea(aFrame, invalidArea); } if (aFrame->IsTransformed()) { invalidArea = nsDisplayTransform::TransformRect(invalidArea, aFrame, nsPoint(0, 0)); } invalidArea += aFrame->GetPosition(); aFrame = aFrame->GetParent(); } if (!aFrame) { // We seem to be able to get here, even though SVG frames are never created // without an ancestor nsSVGOuterSVGFrame. See bug 767996. return; } NS_ASSERTION(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG, "SVG frames must always have an nsSVGOuterSVGFrame ancestor!"); static_cast(aFrame)->InvalidateSVG(invalidArea); } void nsSVGUtils::ScheduleReflowSVG(nsIFrame *aFrame) { NS_ABORT_IF_FALSE(aFrame->IsFrameOfType(nsIFrame::eSVG), "Passed bad frame!"); // If this is triggered, the callers should be fixed to call us before // ReflowSVG is called. If we try to mark dirty bits on frames while we're // in the process of removing them, things will get messed up. NS_ASSERTION(!OuterSVGIsCallingReflowSVG(aFrame), "Do not call under nsISVGChildFrame::ReflowSVG!"); // We don't call nsSVGEffects::InvalidateRenderingObservers here because // we should only be called under InvalidateAndScheduleReflowSVG (which // calls InvalidateBounds) or nsSVGDisplayContainerFrame::InsertFrames // (at which point the frame has no observers). if (aFrame->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD) { return; } if (aFrame->GetStateBits() & (NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) { // Nothing to do if we're already dirty, or if the outer- // hasn't yet had its initial reflow. return; } nsSVGOuterSVGFrame *outerSVGFrame = nullptr; // We must not add dirty bits to the nsSVGOuterSVGFrame or else // PresShell::FrameNeedsReflow won't work when we pass it in below. if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { outerSVGFrame = static_cast(aFrame); } else { aFrame->AddStateBits(NS_FRAME_IS_DIRTY); nsIFrame *f = aFrame->GetParent(); while (f && !(f->GetStateBits() & NS_STATE_IS_OUTER_SVG)) { if (f->GetStateBits() & (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) { return; } f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); f = f->GetParent(); NS_ABORT_IF_FALSE(f->IsFrameOfType(nsIFrame::eSVG), "NS_STATE_IS_OUTER_SVG check above not valid!"); } outerSVGFrame = static_cast(f); NS_ABORT_IF_FALSE(outerSVGFrame && outerSVGFrame->GetType() == nsGkAtoms::svgOuterSVGFrame, "Did not find nsSVGOuterSVGFrame!"); } if (outerSVGFrame->GetStateBits() & NS_FRAME_IN_REFLOW) { // We're currently under an nsSVGOuterSVGFrame::Reflow call so there is no // need to call PresShell::FrameNeedsReflow, since we have an // nsSVGOuterSVGFrame::DidReflow call pending. return; } nsFrameState dirtyBit = (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY : NS_FRAME_HAS_DIRTY_CHILDREN); aFrame->PresContext()->PresShell()->FrameNeedsReflow( outerSVGFrame, nsIPresShell::eResize, dirtyBit); } bool nsSVGUtils::NeedsReflowSVG(nsIFrame *aFrame) { NS_ABORT_IF_FALSE(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG uses bits differently!"); // The flags we test here may change, hence why we have this separate // function. return NS_SUBTREE_DIRTY(aFrame); } void nsSVGUtils::NotifyAncestorsOfFilterRegionChange(nsIFrame *aFrame) { NS_ABORT_IF_FALSE(!(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG), "Not expecting to be called on the outer SVG Frame"); aFrame = aFrame->GetParent(); while (aFrame) { if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) return; nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame); if (property) { property->Invalidate(); } aFrame = aFrame->GetParent(); } } float nsSVGUtils::ObjectSpace(const gfxRect &aRect, const nsSVGLength2 *aLength) { float axis; switch (aLength->GetCtxType()) { case SVGContentUtils::X: axis = aRect.Width(); break; case SVGContentUtils::Y: axis = aRect.Height(); break; case SVGContentUtils::XY: axis = float(SVGContentUtils::ComputeNormalizedHypotenuse( aRect.Width(), aRect.Height())); break; default: NS_NOTREACHED("unexpected ctx type"); axis = 0.0f; break; } if (aLength->IsPercentage()) { // Multiply first to avoid precision errors: return axis * aLength->GetAnimValInSpecifiedUnits() / 100; } return aLength->GetAnimValue(static_cast(nullptr)) * axis; } float nsSVGUtils::UserSpace(nsSVGElement *aSVGElement, const nsSVGLength2 *aLength) { return aLength->GetAnimValue(aSVGElement); } float nsSVGUtils::UserSpace(nsIFrame *aNonSVGContext, const nsSVGLength2 *aLength) { return aLength->GetAnimValue(aNonSVGContext); } nsSVGOuterSVGFrame * nsSVGUtils::GetOuterSVGFrame(nsIFrame *aFrame) { while (aFrame) { if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { return static_cast(aFrame); } aFrame = aFrame->GetParent(); } return nullptr; } nsIFrame* nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, nsRect* aRect) { nsISVGChildFrame* svg = do_QueryFrame(aFrame); if (!svg) return nullptr; *aRect = (aFrame->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD) ? nsRect(0, 0, 0, 0) : svg->GetCoveredRegion(); return GetOuterSVGFrame(aFrame); } gfxMatrix nsSVGUtils::GetCanvasTM(nsIFrame *aFrame, uint32_t aFor) { // XXX yuck, we really need a common interface for GetCanvasTM if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) { return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); } if (!(aFrame->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) { if ((aFor == nsISVGChildFrame::FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) || (aFor == nsISVGChildFrame::FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) { return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); } } nsIAtom* type = aFrame->GetType(); if (type == nsGkAtoms::svgForeignObjectFrame) { return static_cast(aFrame)->GetCanvasTM(aFor); } if (type == nsGkAtoms::svgOuterSVGFrame) { return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame); } nsSVGContainerFrame *containerFrame = do_QueryFrame(aFrame); if (containerFrame) { return containerFrame->GetCanvasTM(aFor); } return static_cast(aFrame)->GetCanvasTM(aFor); } gfxMatrix nsSVGUtils::GetUserToCanvasTM(nsIFrame *aFrame, uint32_t aFor) { NS_ASSERTION(aFor == nsISVGChildFrame::FOR_OUTERSVG_TM, "Unexpected aFor?"); nsISVGChildFrame* svgFrame = do_QueryFrame(aFrame); NS_ASSERTION(svgFrame, "bad frame"); gfxMatrix tm; if (svgFrame) { nsSVGElement *content = static_cast(aFrame->GetContent()); tm = content->PrependLocalTransformsTo( GetCanvasTM(aFrame->GetParent(), aFor), nsSVGElement::eUserSpaceToParent); } return tm; } void nsSVGUtils::NotifyChildrenOfSVGChange(nsIFrame *aFrame, uint32_t aFlags) { nsIFrame *kid = aFrame->GetFirstPrincipalChild(); while (kid) { nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); if (SVGFrame) { SVGFrame->NotifySVGChanged(aFlags); } else { NS_ASSERTION(kid->IsFrameOfType(nsIFrame::eSVG) || kid->IsSVGText(), "SVG frame expected"); // recurse into the children of container frames e.g. , // in case they have child frames with transformation matrices if (kid->IsFrameOfType(nsIFrame::eSVG)) { NotifyChildrenOfSVGChange(kid, aFlags); } } kid = kid->GetNextSibling(); } } // ************************************************************ class SVGPaintCallback : public nsSVGFilterPaintCallback { public: virtual void Paint(nsRenderingContext *aContext, nsIFrame *aTarget, const nsIntRect* aDirtyRect) { nsISVGChildFrame *svgChildFrame = do_QueryFrame(aTarget); NS_ASSERTION(svgChildFrame, "Expected SVG frame here"); nsIntRect* dirtyRect = nullptr; nsIntRect tmpDirtyRect; // aDirtyRect is in user-space pixels, we need to convert to // outer-SVG-frame-relative device pixels. if (aDirtyRect) { gfxMatrix userToDeviceSpace = nsSVGUtils::GetCanvasTM(aTarget, nsISVGChildFrame::FOR_PAINTING); if (userToDeviceSpace.IsSingular()) { return; } gfxRect dirtyBounds = userToDeviceSpace.TransformBounds( gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height)); dirtyBounds.RoundOut(); if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) { dirtyRect = &tmpDirtyRect; } } svgChildFrame->PaintSVG(aContext, dirtyRect); } }; void nsSVGUtils::PaintFrameWithEffects(nsRenderingContext *aContext, const nsIntRect *aDirtyRect, nsIFrame *aFrame) { NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || (aFrame->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD) || aFrame->PresContext()->IsGlyph(), "If display lists are enabled, only painting of non-display " "SVG should take this code path"); nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame); if (!svgChildFrame) return; float opacity = aFrame->GetStyleDisplay()->mOpacity; if (opacity == 0.0f) return; const nsIContent* content = aFrame->GetContent(); if (content->IsSVG() && !static_cast(content)->HasValidDimensions()) { return; } /* Properties are added lazily and may have been removed by a restyle, so make sure all applicable ones are set again. */ nsSVGEffects::EffectProperties effectProperties = nsSVGEffects::GetEffectProperties(aFrame); bool isOK = true; nsSVGFilterFrame *filterFrame = effectProperties.GetFilterFrame(&isOK); if (aDirtyRect && !(aFrame->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) { // Here we convert aFrame's paint bounds to outer- device space, // compare it to aDirtyRect, and return early if they don't intersect. // We don't do this optimization for nondisplay SVG since nondisplay // SVG doesn't maintain bounds/overflow rects. nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf(); if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry)) { // Unlike containers, leaf frames do not include GetPosition() in // GetCanvasTM(). overflowRect = overflowRect + aFrame->GetPosition(); } uint32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel(); gfxMatrix tm = GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING); if (aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { gfxMatrix childrenOnlyTM; if (static_cast(aFrame)-> HasChildrenOnlyTransform(&childrenOnlyTM)) { // Undo the children-only transform: tm = childrenOnlyTM.Invert() * tm; } } nsIntRect bounds = TransformFrameRectToOuterSVG(overflowRect, tm, aFrame->PresContext()). ToOutsidePixels(appUnitsPerDevPx); if (!aDirtyRect->Intersects(bounds)) { return; } } /* SVG defines the following rendering model: * * 1. Render fill * 2. Render stroke * 3. Render markers * 4. Apply filter * 5. Apply clipping, masking, group opacity * * We follow this, but perform a couple of optimizations: * * + Use cairo's clipPath when representable natively (single object * clip region). * * + Merge opacity and masking if both used together. */ if (opacity != 1.0f && CanOptimizeOpacity(aFrame)) opacity = 1.0f; gfxContext *gfx = aContext->ThebesContext(); bool complexEffects = false; nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); nsSVGMaskFrame *maskFrame = effectProperties.GetMaskFrame(&isOK); bool isTrivialClip = clipPathFrame ? clipPathFrame->IsTrivial() : true; if (!isOK) { // Some resource is invalid. We shouldn't paint anything. return; } gfxMatrix matrix; if (clipPathFrame || maskFrame) matrix = GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING); /* Check if we need to do additional operations on this child's * rendering, which necessitates rendering into another surface. */ if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)) { complexEffects = true; gfx->Save(); if (!(aFrame->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) { // aFrame has a valid visual overflow rect, so clip to it before calling // PushGroup() to minimize the size of the surfaces we'll composite: gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(gfx); gfx->Multiply(GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING)); nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf(); if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry)) { // Unlike containers, leaf frames do not include GetPosition() in // GetCanvasTM(). overflowRect = overflowRect + aFrame->GetPosition(); } aContext->IntersectClip(overflowRect); } gfx->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA); } /* If this frame has only a trivial clipPath, set up cairo's clipping now so * we can just do normal painting and get it clipped appropriately. */ if (clipPathFrame && isTrivialClip) { gfx->Save(); clipPathFrame->ClipPaint(aContext, aFrame, matrix); } /* Paint the child */ if (filterFrame) { nsRect* dirtyRect = nullptr; nsRect tmpDirtyRect; if (aDirtyRect) { // aDirtyRect is in outer- device pixels, but the filter code needs // it in frame space. gfxMatrix userToDeviceSpace = GetUserToCanvasTM(aFrame, nsISVGChildFrame::FOR_OUTERSVG_TM); if (userToDeviceSpace.IsSingular()) { return; } gfxMatrix deviceToUserSpace = userToDeviceSpace; deviceToUserSpace.Invert(); gfxRect dirtyBounds = deviceToUserSpace.TransformBounds( gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height)); tmpDirtyRect = nsLayoutUtils::RoundGfxRectToAppRect( dirtyBounds, aFrame->PresContext()->AppUnitsPerCSSPixel()) - aFrame->GetPosition(); dirtyRect = &tmpDirtyRect; } SVGPaintCallback paintCallback; filterFrame->PaintFilteredFrame(aContext, aFrame, &paintCallback, dirtyRect); } else { svgChildFrame->PaintSVG(aContext, aDirtyRect); } if (clipPathFrame && isTrivialClip) { gfx->Restore(); } /* No more effects, we're done. */ if (!complexEffects) return; gfx->PopGroupToSource(); nsRefPtr maskSurface = maskFrame ? maskFrame->ComputeMaskAlpha(aContext, aFrame, matrix, opacity) : nullptr; nsRefPtr clipMaskSurface; if (clipPathFrame && !isTrivialClip) { gfx->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA); nsresult rv = clipPathFrame->ClipPaint(aContext, aFrame, matrix); clipMaskSurface = gfx->PopGroup(); if (NS_SUCCEEDED(rv) && clipMaskSurface) { // Still more set after clipping, so clip to another surface if (maskSurface || opacity != 1.0f) { gfx->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA); gfx->Mask(clipMaskSurface); gfx->PopGroupToSource(); } else { gfx->Mask(clipMaskSurface); } } } if (maskSurface) { gfx->Mask(maskSurface); } else if (opacity != 1.0f) { gfx->Paint(opacity); } gfx->Restore(); } bool nsSVGUtils::HitTestClip(nsIFrame *aFrame, const nsPoint &aPoint) { nsSVGEffects::EffectProperties props = nsSVGEffects::GetEffectProperties(aFrame); if (!props.mClipPath) return true; bool isOK = true; nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(&isOK); if (!clipPathFrame || !isOK) { // clipPath is not a valid resource, so nothing gets painted, so // hit-testing must fail. return false; } return clipPathFrame->ClipHitTest(aFrame, GetCanvasTM(aFrame, nsISVGChildFrame::FOR_HIT_TESTING), aPoint); } nsIFrame * nsSVGUtils::HitTestChildren(nsIFrame *aFrame, const nsPoint &aPoint) { // Traverse the list in reverse order, so that if we get a hit we know that's // the topmost frame that intersects the point; then we can just return it. nsIFrame* result = nullptr; for (nsIFrame* current = aFrame->PrincipalChildList().LastChild(); current; current = current->GetPrevSibling()) { nsISVGChildFrame* SVGFrame = do_QueryFrame(current); if (SVGFrame) { const nsIContent* content = current->GetContent(); if (content->IsSVG() && !static_cast(content)->HasValidDimensions()) { continue; } result = SVGFrame->GetFrameForPoint(aPoint); if (result) break; } } if (result && !HitTestClip(aFrame, aPoint)) result = nullptr; return result; } nsRect nsSVGUtils::GetCoveredRegion(const nsFrameList &aFrames) { nsRect rect; for (nsIFrame* kid = aFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { nsISVGChildFrame* child = do_QueryFrame(kid); if (child) { nsRect childRect = child->GetCoveredRegion(); rect.UnionRect(rect, childRect); } } return rect; } nsPoint nsSVGUtils::TransformOuterSVGPointToChildFrame(nsPoint aPoint, const gfxMatrix& aFrameToCanvasTM, nsPresContext* aPresContext) { NS_ABORT_IF_FALSE(!aFrameToCanvasTM.IsSingular(), "Callers must not pass a singular matrix"); gfxMatrix canvasDevToFrameUserSpace = aFrameToCanvasTM; canvasDevToFrameUserSpace.Invert(); gfxPoint devPt = gfxPoint(aPoint.x, aPoint.y) / aPresContext->AppUnitsPerDevPixel(); gfxPoint userPt = canvasDevToFrameUserSpace.Transform(devPt); gfxPoint appPt = (userPt * aPresContext->AppUnitsPerCSSPixel()).Round(); userPt.x = clamped(appPt.x, gfxFloat(nscoord_MIN), gfxFloat(nscoord_MAX)); userPt.y = clamped(appPt.y, gfxFloat(nscoord_MIN), gfxFloat(nscoord_MAX)); // now guaranteed to be safe: return nsPoint(nscoord(userPt.x), nscoord(userPt.y)); } nsRect nsSVGUtils::TransformFrameRectToOuterSVG(const nsRect& aRect, const gfxMatrix& aMatrix, nsPresContext* aPresContext) { gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height); r.Scale(1.0 / nsPresContext::AppUnitsPerCSSPixel()); return nsLayoutUtils::RoundGfxRectToAppRect( aMatrix.TransformBounds(r), aPresContext->AppUnitsPerDevPixel()); } gfxIntSize nsSVGUtils::ConvertToSurfaceSize(const gfxSize& aSize, bool *aResultOverflows) { gfxIntSize surfaceSize(ClampToInt(ceil(aSize.width)), ClampToInt(ceil(aSize.height))); *aResultOverflows = surfaceSize.width != ceil(aSize.width) || surfaceSize.height != ceil(aSize.height); if (!gfxASurface::CheckSurfaceSize(surfaceSize)) { surfaceSize.width = NS_MIN(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.width); surfaceSize.height = NS_MIN(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.height); *aResultOverflows = true; } return surfaceSize; } bool nsSVGUtils::HitTestRect(const gfxMatrix &aMatrix, float aRX, float aRY, float aRWidth, float aRHeight, float aX, float aY) { gfxRect rect(aRX, aRY, aRWidth, aRHeight); if (rect.IsEmpty() || aMatrix.IsSingular()) { return false; } gfxMatrix toRectSpace = aMatrix; toRectSpace.Invert(); gfxPoint p = toRectSpace.Transform(gfxPoint(aX, aY)); return rect.x <= p.x && p.x <= rect.XMost() && rect.y <= p.y && p.y <= rect.YMost(); } gfxRect nsSVGUtils::GetClipRectForFrame(nsIFrame *aFrame, float aX, float aY, float aWidth, float aHeight) { const nsStyleDisplay* disp = aFrame->GetStyleDisplay(); if (!(disp->mClipFlags & NS_STYLE_CLIP_RECT)) { NS_ASSERTION(disp->mClipFlags == NS_STYLE_CLIP_AUTO, "We don't know about this type of clip."); return gfxRect(aX, aY, aWidth, aHeight); } if (disp->mOverflowX == NS_STYLE_OVERFLOW_HIDDEN || disp->mOverflowY == NS_STYLE_OVERFLOW_HIDDEN) { nsIntRect clipPxRect = disp->mClip.ToOutsidePixels(aFrame->PresContext()->AppUnitsPerDevPixel()); gfxRect clipRect = gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height); if (NS_STYLE_CLIP_RIGHT_AUTO & disp->mClipFlags) { clipRect.width = aWidth - clipRect.X(); } if (NS_STYLE_CLIP_BOTTOM_AUTO & disp->mClipFlags) { clipRect.height = aHeight - clipRect.Y(); } if (disp->mOverflowX != NS_STYLE_OVERFLOW_HIDDEN) { clipRect.x = aX; clipRect.width = aWidth; } if (disp->mOverflowY != NS_STYLE_OVERFLOW_HIDDEN) { clipRect.y = aY; clipRect.height = aHeight; } return clipRect; } return gfxRect(aX, aY, aWidth, aHeight); } void nsSVGUtils::CompositeSurfaceMatrix(gfxContext *aContext, gfxASurface *aSurface, const gfxMatrix &aCTM, float aOpacity) { if (aCTM.IsSingular()) return; if (aContext->IsCairo()) { aContext->Save(); aContext->Multiply(aCTM); aContext->SetSource(aSurface); aContext->Paint(aOpacity); aContext->Restore(); } else { DrawTarget *dt = aContext->GetDrawTarget(); Matrix oldMat = dt->GetTransform(); RefPtr surf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(dt, aSurface); dt->SetTransform(ToMatrix(aCTM) * oldMat); gfxSize size = aSurface->GetSize(); NS_ASSERTION(size.width >= 0 && size.height >= 0, "Failure to get size for aSurface."); gfxPoint pt = aSurface->GetDeviceOffset(); dt->FillRect(Rect(-pt.x, -pt.y, size.width, size.height), SurfacePattern(surf, EXTEND_CLAMP, Matrix(1.0f, 0, 0, 1.0f, -pt.x, -pt.y)), DrawOptions(aOpacity)); dt->SetTransform(oldMat); } } void nsSVGUtils::CompositePatternMatrix(gfxContext *aContext, gfxPattern *aPattern, const gfxMatrix &aCTM, float aWidth, float aHeight, float aOpacity) { if (aCTM.IsSingular()) return; aContext->Save(); SetClipRect(aContext, aCTM, gfxRect(0, 0, aWidth, aHeight)); aContext->Multiply(aCTM); aContext->SetPattern(aPattern); aContext->Paint(aOpacity); aContext->Restore(); } void nsSVGUtils::SetClipRect(gfxContext *aContext, const gfxMatrix &aCTM, const gfxRect &aRect) { if (aCTM.IsSingular()) return; gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(aContext); aContext->Multiply(aCTM); aContext->Clip(aRect); } void nsSVGUtils::ClipToGfxRect(nsIntRect* aRect, const gfxRect& aGfxRect) { gfxRect r = aGfxRect; r.RoundOut(); gfxRect r2(aRect->x, aRect->y, aRect->width, aRect->height); r = r.Intersect(r2); *aRect = nsIntRect(int32_t(r.X()), int32_t(r.Y()), int32_t(r.Width()), int32_t(r.Height())); } gfxRect nsSVGUtils::GetBBox(nsIFrame *aFrame, uint32_t aFlags) { if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) { aFrame = aFrame->GetParent(); } gfxRect bbox; nsISVGChildFrame *svg = do_QueryFrame(aFrame); if (svg || aFrame->IsSVGText()) { // It is possible to apply a gradient, pattern, clipping path, mask or // filter to text. When one of these facilities is applied to text // the bounding box is the entire text element in all // cases. if (NS_SVGTextCSSFramesEnabled()) { nsIFrame* ancestor = GetFirstNonAAncestorFrame(aFrame); if (ancestor && ancestor->IsSVGText()) { while (ancestor->GetType() != nsGkAtoms::svgTextFrame2) { ancestor = ancestor->GetParent(); } } svg = do_QueryFrame(ancestor); } else { nsSVGTextContainerFrame* metrics = do_QueryFrame( GetFirstNonAAncestorFrame(aFrame)); if (metrics) { while (aFrame->GetType() != nsGkAtoms::svgTextFrame) { aFrame = aFrame->GetParent(); } svg = do_QueryFrame(aFrame); } } nsIContent* content = aFrame->GetContent(); if (content->IsSVG() && !static_cast(content)->HasValidDimensions()) { return bbox; } gfxMatrix matrix; if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) { // The spec says getBBox "Returns the tight bounding box in *current user // space*". So we should really be doing this for all elements, but that // needs investigation to check that we won't break too much content. NS_ABORT_IF_FALSE(content->IsSVG(), "bad cast"); nsSVGElement *element = static_cast(content); matrix = element->PrependLocalTransformsTo(matrix, nsSVGElement::eChildToUserSpace); } return svg->GetBBoxContribution(matrix, aFlags); } return nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(aFrame); } gfxRect nsSVGUtils::GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH, const gfxRect &aBBox, nsIFrame *aFrame) { float x, y, width, height; if (aUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { x = aBBox.X() + ObjectSpace(aBBox, &aXYWH[0]); y = aBBox.Y() + ObjectSpace(aBBox, &aXYWH[1]); width = ObjectSpace(aBBox, &aXYWH[2]); height = ObjectSpace(aBBox, &aXYWH[3]); } else { x = UserSpace(aFrame, &aXYWH[0]); y = UserSpace(aFrame, &aXYWH[1]); width = UserSpace(aFrame, &aXYWH[2]); height = UserSpace(aFrame, &aXYWH[3]); } return gfxRect(x, y, width, height); } bool nsSVGUtils::CanOptimizeOpacity(nsIFrame *aFrame) { if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { return false; } nsIAtom *type = aFrame->GetType(); if (type != nsGkAtoms::svgImageFrame && type != nsGkAtoms::svgPathGeometryFrame) { return false; } if (aFrame->GetStyleSVGReset()->mFilter) { return false; } // XXX The SVG WG is intending to allow fill, stroke and markers on if (type == nsGkAtoms::svgImageFrame) { return true; } const nsStyleSVG *style = aFrame->GetStyleSVG(); if (style->mMarkerStart || style->mMarkerMid || style->mMarkerEnd) { return false; } if (style->mFill.mType == eStyleSVGPaintType_None || style->mFillOpacity <= 0 || !HasStroke(aFrame)) { return true; } return false; } float nsSVGUtils::MaxExpansion(const gfxMatrix &aMatrix) { // maximum expansion derivation from // http://lists.cairographics.org/archives/cairo/2004-October/001980.html // and also implemented in cairo_matrix_transformed_circle_major_axis double a = aMatrix.xx; double b = aMatrix.yx; double c = aMatrix.xy; double d = aMatrix.yy; double f = (a * a + b * b + c * c + d * d) / 2; double g = (a * a + b * b - c * c - d * d) / 2; double h = a * c + b * d; return sqrt(f + sqrt(g * g + h * h)); } gfxMatrix nsSVGUtils::AdjustMatrixForUnits(const gfxMatrix &aMatrix, nsSVGEnum *aUnits, nsIFrame *aFrame) { if (aFrame && aUnits->GetAnimValue() == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { gfxRect bbox = GetBBox(aFrame); return gfxMatrix().Scale(bbox.Width(), bbox.Height()) * gfxMatrix().Translate(gfxPoint(bbox.X(), bbox.Y())) * aMatrix; } return aMatrix; } nsIFrame* nsSVGUtils::GetFirstNonAAncestorFrame(nsIFrame* aStartFrame) { for (nsIFrame *ancestorFrame = aStartFrame; ancestorFrame; ancestorFrame = ancestorFrame->GetParent()) { if (ancestorFrame->GetType() != nsGkAtoms::svgAFrame) { return ancestorFrame; } } return nullptr; } gfxMatrix nsSVGUtils::GetStrokeTransform(nsIFrame *aFrame) { if (aFrame->GetStyleSVGReset()->mVectorEffect == NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE) { if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) { aFrame = aFrame->GetParent(); } nsIContent *content = aFrame->GetContent(); NS_ABORT_IF_FALSE(content->IsSVG(), "bad cast"); // a non-scaling stroke is in the screen co-ordinate // space rather so we need to invert the transform // to the screen co-ordinate space to get there. // See http://www.w3.org/TR/SVGTiny12/painting.html#NonScalingStroke gfxMatrix transform = SVGContentUtils::GetCTM( static_cast(content), true); if (!transform.IsSingular()) { return transform.Invert(); } } return gfxMatrix(); } // The logic here comes from _cairo_stroke_style_max_distance_from_path static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, nsIFrame* aFrame, double aStyleExpansionFactor, const gfxMatrix& aMatrix) { double style_expansion = aStyleExpansionFactor * nsSVGUtils::GetStrokeWidth(aFrame); gfxMatrix matrix = aMatrix; matrix.Multiply(nsSVGUtils::GetStrokeTransform(aFrame)); double dx = style_expansion * (fabs(matrix.xx) + fabs(matrix.xy)); double dy = style_expansion * (fabs(matrix.yy) + fabs(matrix.yx)); gfxRect strokeExtents = aPathExtents; strokeExtents.Inflate(dx, dy); return strokeExtents; } /*static*/ gfxRect nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, nsSVGGeometryFrame* aFrame, const gfxMatrix& aMatrix) { return ::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, aMatrix); } /*static*/ gfxRect nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, nsTextFrame* aFrame, const gfxMatrix& aMatrix) { NS_ASSERTION(aFrame->IsSVGText(), "expected an nsTextFrame for SVG text"); return ::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, aMatrix); } /*static*/ gfxRect nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, nsSVGPathGeometryFrame* aFrame, const gfxMatrix& aMatrix) { double styleExpansionFactor = 0.5; if (static_cast(aFrame->GetContent())->IsMarkable()) { const nsStyleSVG* style = aFrame->GetStyleSVG(); if (style->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_SQUARE) { styleExpansionFactor = M_SQRT1_2; } if (style->mStrokeLinejoin == NS_STYLE_STROKE_LINEJOIN_MITER && styleExpansionFactor < style->mStrokeMiterlimit && aFrame->GetContent()->Tag() != nsGkAtoms::line) { styleExpansionFactor = style->mStrokeMiterlimit; } } return ::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, styleExpansionFactor, aMatrix); } // ---------------------------------------------------------------------- /* static */ nscolor nsSVGUtils::GetFallbackOrPaintColor(gfxContext *aContext, nsStyleContext *aStyleContext, nsStyleSVGPaint nsStyleSVG::*aFillOrStroke) { const nsStyleSVGPaint &paint = aStyleContext->GetStyleSVG()->*aFillOrStroke; nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited(); bool isServer = paint.mType == eStyleSVGPaintType_Server || paint.mType == eStyleSVGPaintType_ObjectFill || paint.mType == eStyleSVGPaintType_ObjectStroke; nscolor color = isServer ? paint.mFallbackColor : paint.mPaint.mColor; if (styleIfVisited) { const nsStyleSVGPaint &paintIfVisited = styleIfVisited->GetStyleSVG()->*aFillOrStroke; // To prevent Web content from detecting if a user has visited a URL // (via URL loading triggered by paint servers or performance // differences between paint servers or between a paint server and a // color), we do not allow whether links are visited to change which // paint server is used or switch between paint servers and simple // colors. A :visited style may only override a simple color with // another simple color. if (paintIfVisited.mType == eStyleSVGPaintType_Color && paint.mType == eStyleSVGPaintType_Color) { nscolor colors[2] = { color, paintIfVisited.mPaint.mColor }; return nsStyleContext::CombineVisitedColors( colors, aStyleContext->RelevantLinkVisited()); } } return color; } static void SetupFallbackOrPaintColor(gfxContext *aContext, nsStyleContext *aStyleContext, nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, float aOpacity) { nscolor color = nsSVGUtils::GetFallbackOrPaintColor( aContext, aStyleContext, aFillOrStroke); aContext->SetColor(gfxRGBA(NS_GET_R(color)/255.0, NS_GET_G(color)/255.0, NS_GET_B(color)/255.0, NS_GET_A(color)/255.0 * aOpacity)); } static float MaybeOptimizeOpacity(nsIFrame *aFrame, float aFillOrStrokeOpacity) { float opacity = aFrame->GetStyleDisplay()->mOpacity; if (opacity < 1 && nsSVGUtils::CanOptimizeOpacity(aFrame)) { return aFillOrStrokeOpacity * opacity; } return aFillOrStrokeOpacity; } /* static */ bool nsSVGUtils::SetupObjectPaint(gfxContext *aContext, gfxTextObjectPaint *aObjectPaint, const nsStyleSVGPaint &aPaint, float aOpacity) { nsRefPtr pattern; if (!aObjectPaint) { return false; } switch (aPaint.mType) { case eStyleSVGPaintType_ObjectFill: pattern = aObjectPaint->GetFillPattern(aOpacity, aContext->CurrentMatrix()); break; case eStyleSVGPaintType_ObjectStroke: pattern = aObjectPaint->GetStrokePattern(aOpacity, aContext->CurrentMatrix()); break; default: return false; } if (!pattern) { return false; } aContext->SetPattern(pattern); return true; } bool nsSVGUtils::SetupCairoFillPaint(nsIFrame *aFrame, gfxContext* aContext, gfxTextObjectPaint *aObjectPaint) { const nsStyleSVG* style = aFrame->GetStyleSVG(); if (style->mFill.mType == eStyleSVGPaintType_None) return false; if (style->mFillRule == NS_STYLE_FILL_RULE_EVENODD) aContext->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); else aContext->SetFillRule(gfxContext::FILL_RULE_WINDING); float opacity = MaybeOptimizeOpacity(aFrame, GetOpacity(style->mFillOpacitySource, style->mFillOpacity, aObjectPaint)); nsSVGPaintServerFrame *ps = nsSVGEffects::GetPaintServer(aFrame, &style->mFill, nsSVGEffects::FillProperty()); if (ps && ps->SetupPaintServer(aContext, aFrame, &nsStyleSVG::mFill, opacity)) return true; if (SetupObjectPaint(aContext, aObjectPaint, style->mFill, opacity)) { return true; } // On failure, use the fallback colour in case we have an // objectBoundingBox where the width or height of the object is zero. // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox SetupFallbackOrPaintColor(aContext, aFrame->GetStyleContext(), &nsStyleSVG::mFill, opacity); return true; } bool nsSVGUtils::SetupCairoStrokePaint(nsIFrame *aFrame, gfxContext* aContext, gfxTextObjectPaint *aObjectPaint) { const nsStyleSVG* style = aFrame->GetStyleSVG(); if (style->mStroke.mType == eStyleSVGPaintType_None) return false; float opacity = MaybeOptimizeOpacity(aFrame, GetOpacity(style->mStrokeOpacitySource, style->mStrokeOpacity, aObjectPaint)); nsSVGPaintServerFrame *ps = nsSVGEffects::GetPaintServer(aFrame, &style->mStroke, nsSVGEffects::StrokeProperty()); if (ps && ps->SetupPaintServer(aContext, aFrame, &nsStyleSVG::mStroke, opacity)) return true; if (SetupObjectPaint(aContext, aObjectPaint, style->mStroke, opacity)) { return true; } // On failure, use the fallback colour in case we have an // objectBoundingBox where the width or height of the object is zero. // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox SetupFallbackOrPaintColor(aContext, aFrame->GetStyleContext(), &nsStyleSVG::mStroke, opacity); return true; } /* static */ float nsSVGUtils::GetOpacity(nsStyleSVGOpacitySource aOpacityType, const float& aOpacity, gfxTextObjectPaint *aOuterObjectPaint) { float opacity = 1.0f; switch (aOpacityType) { case eStyleSVGOpacitySource_Normal: opacity = aOpacity; break; case eStyleSVGOpacitySource_ObjectFillOpacity: if (aOuterObjectPaint) { opacity = aOuterObjectPaint->GetFillOpacity(); } else { NS_WARNING("objectFillOpacity used outside of an SVG glyph"); } break; case eStyleSVGOpacitySource_ObjectStrokeOpacity: if (aOuterObjectPaint) { opacity = aOuterObjectPaint->GetStrokeOpacity(); } else { NS_WARNING("objectStrokeOpacity used outside of an SVG glyph"); } break; default: NS_NOTREACHED("Unknown object opacity inheritance type for SVG glyph"); } return opacity; } bool nsSVGUtils::HasStroke(nsIFrame* aFrame, gfxTextObjectPaint *aObjectPaint) { const nsStyleSVG *style = aFrame->GetStyleSVG(); return style->mStroke.mType != eStyleSVGPaintType_None && style->mStrokeOpacity > 0 && GetStrokeWidth(aFrame, aObjectPaint) > 0; } float nsSVGUtils::GetStrokeWidth(nsIFrame* aFrame, gfxTextObjectPaint *aObjectPaint) { const nsStyleSVG *style = aFrame->GetStyleSVG(); if (aObjectPaint && style->mStrokeWidthFromObject) { return aObjectPaint->GetStrokeWidth(); } nsIContent* content = aFrame->GetContent(); if (content->IsNodeOfType(nsINode::eTEXT)) { content = content->GetParent(); } nsSVGElement *ctx = static_cast(content); return CoordToFloat(aFrame->PresContext(), ctx, style->mStrokeWidth); } void nsSVGUtils::SetupCairoStrokeGeometry(nsIFrame* aFrame, gfxContext *aContext, gfxTextObjectPaint *aObjectPaint) { float width = GetStrokeWidth(aFrame, aObjectPaint); if (width <= 0) return; aContext->SetLineWidth(width); // Apply any stroke-specific transform aContext->Multiply(GetStrokeTransform(aFrame)); const nsStyleSVG* style = aFrame->GetStyleSVG(); switch (style->mStrokeLinecap) { case NS_STYLE_STROKE_LINECAP_BUTT: aContext->SetLineCap(gfxContext::LINE_CAP_BUTT); break; case NS_STYLE_STROKE_LINECAP_ROUND: aContext->SetLineCap(gfxContext::LINE_CAP_ROUND); break; case NS_STYLE_STROKE_LINECAP_SQUARE: aContext->SetLineCap(gfxContext::LINE_CAP_SQUARE); break; } aContext->SetMiterLimit(style->mStrokeMiterlimit); switch (style->mStrokeLinejoin) { case NS_STYLE_STROKE_LINEJOIN_MITER: aContext->SetLineJoin(gfxContext::LINE_JOIN_MITER); break; case NS_STYLE_STROKE_LINEJOIN_ROUND: aContext->SetLineJoin(gfxContext::LINE_JOIN_ROUND); break; case NS_STYLE_STROKE_LINEJOIN_BEVEL: aContext->SetLineJoin(gfxContext::LINE_JOIN_BEVEL); break; } } static bool GetStrokeDashData(nsIFrame* aFrame, FallibleTArray& aDashes, gfxFloat* aDashOffset, gfxTextObjectPaint *aObjectPaint) { const nsStyleSVG* style = aFrame->GetStyleSVG(); nsPresContext *presContext = aFrame->PresContext(); nsIContent *content = aFrame->GetContent(); nsSVGElement *ctx = static_cast (content->IsNodeOfType(nsINode::eTEXT) ? content->GetParent() : content); gfxFloat totalLength = 0.0; if (aObjectPaint && style->mStrokeDasharrayFromObject) { aDashes = aObjectPaint->GetStrokeDashArray(); for (uint32_t i = 0; i < aDashes.Length(); i++) { if (aDashes[i] < 0.0) { return false; } totalLength += aDashes[i]; } } else { uint32_t count = style->mStrokeDasharrayLength; if (!count || !aDashes.SetLength(count)) { return false; } gfxFloat pathScale = 1.0; if (content->Tag() == nsGkAtoms::path) { pathScale = static_cast(content)-> GetPathLengthScale(nsSVGPathElement::eForStroking); if (pathScale <= 0) { return false; } } const nsStyleCoord *dasharray = style->mStrokeDasharray; for (uint32_t i = 0; i < count; i++) { aDashes[i] = nsSVGUtils::CoordToFloat(presContext, ctx, dasharray[i]) * pathScale; if (aDashes[i] < 0.0) { return false; } totalLength += aDashes[i]; } } if (aObjectPaint && style->mStrokeDashoffsetFromObject) { *aDashOffset = aObjectPaint->GetStrokeDashOffset(); } else { *aDashOffset = nsSVGUtils::CoordToFloat(presContext, ctx, style->mStrokeDashoffset); } if (content->IsNodeOfType(nsINode::eTEXT)) { content = content->GetParent(); } return (totalLength > 0.0); } void nsSVGUtils::SetupCairoStrokeHitGeometry(nsIFrame* aFrame, gfxContext* aContext, gfxTextObjectPaint *aObjectPaint) { SetupCairoStrokeGeometry(aFrame, aContext, aObjectPaint); AutoFallibleTArray dashes; gfxFloat dashOffset; if (GetStrokeDashData(aFrame, dashes, &dashOffset, aObjectPaint)) { aContext->SetDash(dashes.Elements(), dashes.Length(), dashOffset); } } uint16_t nsSVGUtils::GetGeometryHitTestFlags(nsIFrame* aFrame) { uint16_t flags = 0; switch(aFrame->GetStyleVisibility()->mPointerEvents) { case NS_STYLE_POINTER_EVENTS_NONE: break; case NS_STYLE_POINTER_EVENTS_AUTO: case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: if (aFrame->GetStyleVisibility()->IsVisible()) { if (aFrame->GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None) flags |= SVG_HIT_TEST_FILL; if (aFrame->GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None) flags |= SVG_HIT_TEST_STROKE; if (aFrame->GetStyleSVG()->mStrokeOpacity > 0) flags |= SVG_HIT_TEST_CHECK_MRECT; } break; case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: if (aFrame->GetStyleVisibility()->IsVisible()) { flags |= SVG_HIT_TEST_FILL; } break; case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: if (aFrame->GetStyleVisibility()->IsVisible()) { flags |= SVG_HIT_TEST_STROKE; } break; case NS_STYLE_POINTER_EVENTS_VISIBLE: if (aFrame->GetStyleVisibility()->IsVisible()) { flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; } break; case NS_STYLE_POINTER_EVENTS_PAINTED: if (aFrame->GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None) flags |= SVG_HIT_TEST_FILL; if (aFrame->GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None) flags |= SVG_HIT_TEST_STROKE; if (aFrame->GetStyleSVG()->mStrokeOpacity) flags |= SVG_HIT_TEST_CHECK_MRECT; break; case NS_STYLE_POINTER_EVENTS_FILL: flags |= SVG_HIT_TEST_FILL; break; case NS_STYLE_POINTER_EVENTS_STROKE: flags |= SVG_HIT_TEST_STROKE; break; case NS_STYLE_POINTER_EVENTS_ALL: flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; break; default: NS_ERROR("not reached"); break; } return flags; } bool nsSVGUtils::SetupCairoStroke(nsIFrame* aFrame, gfxContext* aContext, gfxTextObjectPaint *aObjectPaint) { if (!HasStroke(aFrame, aObjectPaint)) { return false; } SetupCairoStrokeHitGeometry(aFrame, aContext, aObjectPaint); return SetupCairoStrokePaint(aFrame, aContext, aObjectPaint); }