/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Mozilla SVG project. * * The Initial Developer of the Original Code is IBM Corporation. * Portions created by the Initial Developer are Copyright (C) 2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsSVGPathGeometryFrame.h" #include "imgIContainer.h" #include "nsStubImageDecoderObserver.h" #include "nsImageLoadingContent.h" #include "nsIDOMSVGImageElement.h" #include "nsLayoutUtils.h" #include "nsSVGImageElement.h" #include "nsSVGUtils.h" #include "gfxContext.h" #include "gfxMatrix.h" #include "nsIInterfaceRequestorUtils.h" class nsSVGImageFrame; class nsSVGImageListener : public nsStubImageDecoderObserver { public: nsSVGImageListener(nsSVGImageFrame *aFrame); NS_DECL_ISUPPORTS // imgIDecoderObserver (override nsStubImageDecoderObserver) NS_IMETHOD OnStopDecode(imgIRequest *aRequest, nsresult status, const PRUnichar *statusArg); // imgIContainerObserver (override nsStubImageDecoderObserver) NS_IMETHOD FrameChanged(imgIContainer *aContainer, nsIntRect * dirtyRect); // imgIContainerObserver (override nsStubImageDecoderObserver) NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aContainer); void SetFrame(nsSVGImageFrame *frame) { mFrame = frame; } private: nsSVGImageFrame *mFrame; }; class nsSVGImageFrame : public nsSVGPathGeometryFrame { friend nsIFrame* NS_NewSVGImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); protected: nsSVGImageFrame(nsStyleContext* aContext) : nsSVGPathGeometryFrame(aContext) {} virtual ~nsSVGImageFrame(); public: NS_DECL_FRAMEARENA_HELPERS // nsISVGChildFrame interface: NS_IMETHOD PaintSVG(nsSVGRenderState *aContext, const nsIntRect *aDirtyRect); NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint); // nsSVGPathGeometryFrame methods: NS_IMETHOD UpdateCoveredRegion(); virtual PRUint16 GetHittestMask(); // nsIFrame interface: NS_IMETHOD AttributeChanged(PRInt32 aNameSpaceID, nsIAtom* aAttribute, PRInt32 aModType); NS_IMETHOD Init(nsIContent* aContent, nsIFrame* aParent, nsIFrame* aPrevInFlow); /** * Get the "type" of the frame * * @see nsGkAtoms::svgImageFrame */ virtual nsIAtom* GetType() const; #ifdef DEBUG NS_IMETHOD GetFrameName(nsAString& aResult) const { return MakeFrameName(NS_LITERAL_STRING("SVGImage"), aResult); } #endif private: gfxMatrix GetImageTransform(); nsCOMPtr mListener; nsCOMPtr mImageContainer; friend class nsSVGImageListener; }; //---------------------------------------------------------------------- // Implementation nsIFrame* NS_NewSVGImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) { return new (aPresShell) nsSVGImageFrame(aContext); } NS_IMPL_FRAMEARENA_HELPERS(nsSVGImageFrame) nsSVGImageFrame::~nsSVGImageFrame() { // set the frame to null so we don't send messages to a dead object. if (mListener) { nsCOMPtr imageLoader = do_QueryInterface(mContent); if (imageLoader) { imageLoader->RemoveObserver(mListener); } reinterpret_cast(mListener.get())->SetFrame(nsnull); } mListener = nsnull; } NS_IMETHODIMP nsSVGImageFrame::Init(nsIContent* aContent, nsIFrame* aParent, nsIFrame* aPrevInFlow) { #ifdef DEBUG nsCOMPtr image = do_QueryInterface(aContent); NS_ASSERTION(image, "Content is not an SVG image!"); #endif nsresult rv = nsSVGPathGeometryFrame::Init(aContent, aParent, aPrevInFlow); if (NS_FAILED(rv)) return rv; mListener = new nsSVGImageListener(this); if (!mListener) return NS_ERROR_OUT_OF_MEMORY; nsCOMPtr imageLoader = do_QueryInterface(mContent); NS_ENSURE_TRUE(imageLoader, NS_ERROR_UNEXPECTED); imageLoader->AddObserver(mListener); return NS_OK; } //---------------------------------------------------------------------- // nsIFrame methods: NS_IMETHODIMP nsSVGImageFrame::AttributeChanged(PRInt32 aNameSpaceID, nsIAtom* aAttribute, PRInt32 aModType) { if (aNameSpaceID == kNameSpaceID_None && (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y || aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height || aAttribute == nsGkAtoms::preserveAspectRatio)) { nsSVGUtils::UpdateGraphic(this); return NS_OK; } return nsSVGPathGeometryFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); } gfxMatrix nsSVGImageFrame::GetImageTransform() { float x, y, width, height; nsSVGImageElement *element = static_cast(mContent); element->GetAnimatedLengthValues(&x, &y, &width, &height, nsnull); PRInt32 nativeWidth, nativeHeight; mImageContainer->GetWidth(&nativeWidth); mImageContainer->GetHeight(&nativeHeight); gfxMatrix viewBoxTM = nsSVGUtils::GetViewBoxTransform(width, height, 0, 0, nativeWidth, nativeHeight, element->mPreserveAspectRatio); return viewBoxTM * gfxMatrix().Translate(gfxPoint(x, y)) * GetCanvasTM(); } //---------------------------------------------------------------------- // nsISVGChildFrame methods: NS_IMETHODIMP nsSVGImageFrame::PaintSVG(nsSVGRenderState *aContext, const nsIntRect *aDirtyRect) { nsresult rv = NS_OK; if (!GetStyleVisibility()->IsVisible()) return NS_OK; float x, y, width, height; nsSVGElement *element = static_cast(mContent); element->GetAnimatedLengthValues(&x, &y, &width, &height, nsnull); if (width <= 0 || height <= 0) return NS_OK; if (!mImageContainer) { nsCOMPtr currentRequest; nsCOMPtr imageLoader = do_QueryInterface(mContent); if (imageLoader) imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(currentRequest)); if (currentRequest) currentRequest->GetImage(getter_AddRefs(mImageContainer)); } // XXXbholley - I don't think huge images in SVGs are common enough to // warrant worrying about the responsiveness impact of doing synchronous // decodes. The extra code complexity of determinining when we want to // force sync probably just isn't worth it, so always pass FLAG_SYNC_DECODE nsRefPtr currentFrame; if (mImageContainer) mImageContainer->GetFrame(imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_SYNC_DECODE, getter_AddRefs(currentFrame)); // We need to wrap the surface in a pattern to have somewhere to set the // graphics filter. nsRefPtr thebesPattern; if (currentFrame) thebesPattern = new gfxPattern(currentFrame); if (thebesPattern) { thebesPattern->SetFilter(nsLayoutUtils::GetGraphicsFilterForFrame(this)); gfxContext *gfx = aContext->GetGfxContext(); if (GetStyleDisplay()->IsScrollableOverflow()) { gfx->Save(); gfxRect clipRect = nsSVGUtils::GetClipRectForFrame(this, x, y, width, height); nsSVGUtils::SetClipRect(gfx, GetCanvasTM(), clipRect); } // fill-opacity doesn't affect , so if we're allowed to // optimize group opacity, the opacity used for compositing the // image into the current canvas is just the group opacity. float opacity = 1.0f; if (nsSVGUtils::CanOptimizeOpacity(this)) { opacity = GetStyleDisplay()->mOpacity; } PRInt32 nativeWidth, nativeHeight; mImageContainer->GetWidth(&nativeWidth); mImageContainer->GetHeight(&nativeHeight); nsSVGUtils::CompositePatternMatrix(gfx, thebesPattern, GetImageTransform(), nativeWidth, nativeHeight, opacity); if (GetStyleDisplay()->IsScrollableOverflow()) gfx->Restore(); } return rv; } NS_IMETHODIMP_(nsIFrame*) nsSVGImageFrame::GetFrameForPoint(const nsPoint &aPoint) { if (GetStyleDisplay()->IsScrollableOverflow() && mImageContainer) { PRInt32 nativeWidth, nativeHeight; mImageContainer->GetWidth(&nativeWidth); mImageContainer->GetHeight(&nativeHeight); if (!nsSVGUtils::HitTestRect(GetImageTransform(), 0, 0, nativeWidth, nativeHeight, PresContext()->AppUnitsToDevPixels(aPoint.x), PresContext()->AppUnitsToDevPixels(aPoint.y))) { return nsnull; } } return nsSVGPathGeometryFrame::GetFrameForPoint(aPoint); } nsIAtom * nsSVGImageFrame::GetType() const { return nsGkAtoms::svgImageFrame; } //---------------------------------------------------------------------- // nsSVGPathGeometryFrame methods: // Lie about our fill/stroke so that covered region and hit detection work properly NS_IMETHODIMP nsSVGImageFrame::UpdateCoveredRegion() { mRect.Empty(); gfxContext context(nsSVGUtils::GetThebesComputationalSurface()); GeneratePath(&context); context.IdentityMatrix(); gfxRect extent = context.GetUserPathExtent(); if (!extent.IsEmpty()) { mRect = nsSVGUtils::ToAppPixelRect(PresContext(), extent); } return NS_OK; } PRUint16 nsSVGImageFrame::GetHittestMask() { PRUint16 mask = 0; switch(GetStyleVisibility()->mPointerEvents) { case NS_STYLE_POINTER_EVENTS_NONE: break; case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: case NS_STYLE_POINTER_EVENTS_AUTO: if (GetStyleVisibility()->IsVisible()) { /* XXX: should check pixel transparency */ mask |= HITTEST_MASK_FILL; } break; case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: case NS_STYLE_POINTER_EVENTS_VISIBLE: if (GetStyleVisibility()->IsVisible()) { mask |= HITTEST_MASK_FILL; } break; case NS_STYLE_POINTER_EVENTS_PAINTED: /* XXX: should check pixel transparency */ mask |= HITTEST_MASK_FILL; break; case NS_STYLE_POINTER_EVENTS_FILL: case NS_STYLE_POINTER_EVENTS_STROKE: case NS_STYLE_POINTER_EVENTS_ALL: mask |= HITTEST_MASK_FILL; break; default: NS_ERROR("not reached"); break; } return mask; } //---------------------------------------------------------------------- // nsSVGImageListener implementation NS_IMPL_ISUPPORTS2(nsSVGImageListener, imgIDecoderObserver, imgIContainerObserver) nsSVGImageListener::nsSVGImageListener(nsSVGImageFrame *aFrame) : mFrame(aFrame) { } NS_IMETHODIMP nsSVGImageListener::OnStopDecode(imgIRequest *aRequest, nsresult status, const PRUnichar *statusArg) { if (!mFrame) return NS_ERROR_FAILURE; nsSVGUtils::UpdateGraphic(mFrame); return NS_OK; } NS_IMETHODIMP nsSVGImageListener::FrameChanged(imgIContainer *aContainer, nsIntRect * dirtyRect) { if (!mFrame) return NS_ERROR_FAILURE; nsSVGUtils::UpdateGraphic(mFrame); return NS_OK; } NS_IMETHODIMP nsSVGImageListener::OnStartContainer(imgIRequest *aRequest, imgIContainer *aContainer) { if (!mFrame) return NS_ERROR_FAILURE; mFrame->mImageContainer = aContainer; nsSVGUtils::UpdateGraphic(mFrame); return NS_OK; }