Bug 506826 - Add nsSVGIntegrationUtils::DrawPaintServer for use in -moz-element drawing. r=roc
authorMarkus Stange <mstange@themasta.com>
Fri, 13 Aug 2010 15:32:27 +0200
changeset 50426 255b19177dd428fd0380566cd8495a69be7f9765
parent 50425 1b2d8d1344223eb7ddd791a22202ad62260991f1
child 50427 c799b49b3f26722f7a68e917efadb3d722df99e0
push idunknown
push userunknown
push dateunknown
reviewersroc
bugs506826
milestone2.0b4pre
Bug 506826 - Add nsSVGIntegrationUtils::DrawPaintServer for use in -moz-element drawing. r=roc
layout/svg/base/src/nsSVGIntegrationUtils.cpp
layout/svg/base/src/nsSVGIntegrationUtils.h
layout/svg/base/src/nsSVGUtils.cpp
layout/svg/base/src/nsSVGUtils.h
--- a/layout/svg/base/src/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/base/src/nsSVGIntegrationUtils.cpp
@@ -41,30 +41,32 @@
 #include "nsSVGEffects.h"
 #include "nsRegion.h"
 #include "nsLayoutUtils.h"
 #include "nsDisplayList.h"
 #include "nsSVGFilterPaintCallback.h"
 #include "nsSVGFilterFrame.h"
 #include "nsSVGClipPathFrame.h"
 #include "nsSVGMaskFrame.h"
+#include "gfxPlatform.h"
+#include "gfxDrawable.h"
+#include "nsSVGPaintServerFrame.h"
 
 // ----------------------------------------------------------------------
 
 PRBool
 nsSVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame)
 {
   const nsStyleSVGReset *style = aFrame->GetStyleSVGReset();
   return (style->mFilter || style->mClipPath || style->mMask) &&
           !aFrame->IsFrameOfType(nsIFrame::eSVG);
 }
 
-// Get the union the frame border-box rects over all continuations,
-// relative to aFirst. This defines "user space" for non-SVG frames.
-static nsRect GetNonSVGUserSpace(nsIFrame* aFirst)
+/* static */ nsRect
+nsSVGIntegrationUtils::GetNonSVGUserSpace(nsIFrame* aFirst)
 {
   NS_ASSERTION(!aFirst->GetPrevContinuation(), "Not first continuation");
   return nsLayoutUtils::GetAllInFlowRectsUnion(aFirst, aFirst);
 }
 
 static nsRect
 GetPreEffectsOverflowRect(nsIFrame* aFrame)
 {
@@ -393,8 +395,158 @@ nsSVGIntegrationUtils::GetSVGBBoxForNonS
     nsLayoutUtils::GetFirstContinuationOrSpecialSibling(aNonSVGFrame);
   nsRect userSpaceRect = GetNonSVGUserSpace(firstFrame);
   nsRect r = GetSVGBBox(firstFrame, nsnull, nsRect(), userSpaceRect);
   gfxRect result(r.x, r.y, r.width, r.height);
   nsPresContext* presContext = aNonSVGFrame->PresContext();
   result.ScaleInverse(presContext->AppUnitsPerCSSPixel());
   return result;
 }
+
+class PaintFrameCallback : public gfxDrawingCallback {
+public:
+  PaintFrameCallback(nsIFrame* aFrame,
+                     nsIFrame* aTarget,
+                     const nsSize aPaintServerSize,
+                     const gfxIntSize aRenderSize)
+   : mFrame(aFrame)
+   , mTarget(aTarget)
+   , mPaintServerSize(aPaintServerSize)
+   , mRenderSize(aRenderSize)
+  {}
+  virtual PRBool operator()(gfxContext* aContext,
+                            const gfxRect& aFillRect,
+                            const gfxPattern::GraphicsFilter& aFilter,
+                            const gfxMatrix& aTransform);
+private:
+  nsIFrame* mFrame;
+  nsIFrame* mTarget;
+  nsSize mPaintServerSize;
+  gfxIntSize mRenderSize;
+};
+
+PRBool
+PaintFrameCallback::operator()(gfxContext* aContext,
+                               const gfxRect& aFillRect,
+                               const gfxPattern::GraphicsFilter& aFilter,
+                               const gfxMatrix& aTransform)
+{
+  if (mFrame->GetStateBits() & NS_FRAME_DRAWING_AS_PAINTSERVER)
+    return PR_FALSE;
+
+  mFrame->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER);
+
+  nsSVGRenderState renderState(aContext);
+  aContext->Save();
+
+  // Clip to aFillRect so that we don't paint outside.
+  aContext->NewPath();
+  aContext->Rectangle(aFillRect);
+  aContext->Clip();
+  gfxMatrix savedMatrix(aContext->CurrentMatrix());
+
+  aContext->Multiply(gfxMatrix(aTransform).Invert());
+
+  // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want
+  // to have it anchored at the top left corner of the bounding box of all of
+  // mFrame's continuations. So we add a translation transform.
+  nsRect bbox = nsSVGIntegrationUtils::GetNonSVGUserSpace(mFrame);
+  PRInt32 appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+  gfxPoint offset = gfxPoint(bbox.x, bbox.y) / appUnitsPerDevPixel;
+  aContext->Multiply(gfxMatrix().Translate(-offset));
+
+  gfxSize paintServerSize =
+    gfxSize(mPaintServerSize.width, mPaintServerSize.height) /
+      mFrame->PresContext()->AppUnitsPerDevPixel();
+
+  // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we
+  // want it to render with mRenderSize, so we need to set up a scale transform.
+  gfxFloat scaleX = mRenderSize.width / paintServerSize.width;
+  gfxFloat scaleY = mRenderSize.height / paintServerSize.height;
+  gfxMatrix scaleMatrix = gfxMatrix().Scale(scaleX, scaleY);
+  aContext->Multiply(scaleMatrix);
+
+  // Draw.
+  nsRect dirty(bbox.x, bbox.y, mPaintServerSize.width, mPaintServerSize.height);
+  nsLayoutUtils::PaintFrame(renderState.GetRenderingContext(mTarget), mFrame,
+                            dirty, NS_RGBA(0, 0, 0, 0),
+                            nsLayoutUtils::PAINT_IN_TRANSFORM |
+                            nsLayoutUtils::PAINT_ALL_CONTINUATIONS);
+
+  aContext->SetMatrix(savedMatrix);
+  aContext->Restore();
+
+  mFrame->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER);
+
+  return PR_TRUE;
+}
+
+static already_AddRefed<gfxDrawable>
+DrawableFromPaintServer(nsIFrame*         aFrame,
+                        nsIFrame*         aTarget,
+                        const nsSize&     aPaintServerSize,
+                        const gfxIntSize& aRenderSize)
+{
+  // aPaintServerSize is the size that would be filled when using
+  // background-repeat:no-repeat and background-size:auto. For normal background
+  // images, this would be the intrinsic size of the image; for gradients and
+  // patterns this would be the whole target frame fill area.
+  // aRenderSize is what we will be actually filling after accounting for
+  // background-size.
+  if (aFrame->IsFrameOfType(nsIFrame::eSVGPaintServer)) {
+    // aFrame is either a pattern or a gradient. These fill the whole target
+    // frame by default, so aPaintServerSize is the whole target background fill
+    // area.
+    nsSVGPaintServerFrame* server =
+      static_cast<nsSVGPaintServerFrame*>(aFrame);
+
+    gfxRect overrideBounds(0, 0,
+                           aPaintServerSize.width, aPaintServerSize.height);
+    overrideBounds.ScaleInverse(aFrame->PresContext()->AppUnitsPerDevPixel());
+    nsRefPtr<gfxPattern> pattern =
+      server->GetPaintServerPattern(aTarget, 1.0, &overrideBounds);
+
+    // pattern is now set up to fill aPaintServerSize. But we want it to
+    // fill aRenderSize, so we need to add a scaling transform.
+    // We couldn't just have set overrideBounds to aRenderSize - it would have
+    // worked for gradients, but for patterns it would result in a different
+    // pattern size.
+    gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width;
+    gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height;
+    gfxMatrix scaleMatrix = gfxMatrix().Scale(scaleX, scaleY);
+    pattern->SetMatrix(scaleMatrix.Multiply(pattern->GetMatrix()));
+    nsRefPtr<gfxDrawable> drawable =
+      new gfxPatternDrawable(pattern, aRenderSize);
+    return drawable.forget();
+  }
+
+  // We don't want to paint into a surface as long as we don't need to, so we
+  // set up a drawing callback.
+  nsRefPtr<gfxDrawingCallback> cb =
+    new PaintFrameCallback(aFrame, aTarget, aPaintServerSize, aRenderSize);
+  nsRefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, aRenderSize);
+  return drawable.forget();
+}
+
+/* static */ void
+nsSVGIntegrationUtils::DrawPaintServer(nsIRenderingContext* aRenderingContext,
+                                       nsIFrame*            aTarget,
+                                       nsIFrame*            aPaintServer,
+                                       gfxPattern::GraphicsFilter aFilter,
+                                       const nsRect&        aDest,
+                                       const nsRect&        aFill,
+                                       const nsPoint&       aAnchor,
+                                       const nsRect&        aDirty,
+                                       const nsSize&        aPaintServerSize)
+{
+  if (aDest.IsEmpty() || aFill.IsEmpty())
+    return;
+
+  PRInt32 appUnitsPerDevPixel = aTarget->PresContext()->AppUnitsPerDevPixel();
+  nsRect destSize = aDest - aDest.TopLeft();
+  nsIntSize roundedOut = destSize.ToOutsidePixels(appUnitsPerDevPixel).Size();
+  gfxIntSize imageSize(roundedOut.width, roundedOut.height);
+  nsRefPtr<gfxDrawable> drawable =
+    DrawableFromPaintServer(aPaintServer, aTarget, aPaintServerSize, imageSize);
+
+  nsLayoutUtils::DrawPixelSnapped(aRenderingContext, drawable, aFilter,
+                                  aDest, aFill, aAnchor, aDirty);
+}
--- a/layout/svg/base/src/nsSVGIntegrationUtils.h
+++ b/layout/svg/base/src/nsSVGIntegrationUtils.h
@@ -37,16 +37,17 @@
 
 #ifndef NSSVGINTEGRATIONUTILS_H_
 #define NSSVGINTEGRATIONUTILS_H_
 
 #include "nsPoint.h"
 #include "nsRect.h"
 #include "gfxRect.h"
 #include "gfxMatrix.h"
+#include "gfxPattern.h"
 
 class nsIFrame;
 class nsDisplayListBuilder;
 class nsDisplayList;
 class nsIRenderingContext;
 
 /***** Integration of SVG effects with regular frame painting *****/
 
@@ -55,16 +56,22 @@ class nsSVGIntegrationUtils
 public:
   /**
    * Returns true if a non-SVG frame has SVG effects.
    */
   static PRBool
   UsingEffectsForFrame(const nsIFrame* aFrame);
 
   /**
+   * Get the union the frame border-box rects over all continuations,
+   * relative to aFirst. This defines "user space" for non-SVG frames.
+   */
+  static nsRect
+  GetNonSVGUserSpace(nsIFrame* aFirst);
+  /**
    * Adjust overflow rect for effects.
    * XXX this is a problem. We really need to compute the effects rect for
    * a whole chain of frames for a given element at once. but we have no
    * way to do this effectively with Gecko's current reflow architecture.
    * See http://groups.google.com/group/mozilla.dev.tech.layout/msg/6b179066f3051f65
    */
   static nsRect
   ComputeFrameEffectsRect(nsIFrame* aFrame, const nsRect& aOverflowRect);
@@ -109,11 +116,39 @@ public:
   GetSVGRectForNonSVGFrame(nsIFrame* aNonSVGFrame);
   /**
    * Returns aNonSVGFrame's bounding box in CSS units. This is the union
    * of all its continuations' overflow areas, relative to the top-left
    * of all the continuations' rectangles.
    */
   static gfxRect
   GetSVGBBoxForNonSVGFrame(nsIFrame* aNonSVGFrame);
+
+  /**
+   * @param aRenderingContext the target rendering context in which the paint
+   * server will be rendered
+   * @param aTarget the target frame onto which the paint server will be
+   * rendered
+   * @param aPaintServer a first-continuation frame to use as the source
+   * @param aFilter a filter to be applied when scaling
+   * @param aDest the area the paint server image should be mapped to
+   * @param aFill the area to be filled with copies of the paint server image
+   * @param aAnchor a point in aFill which we will ensure is pixel-aligned in
+   * the output
+   * @param aDirty pixels outside this area may be skipped
+   * @param aPaintServerSize the size that would be filled when using
+   * background-repeat:no-repeat and background-size:auto. For normal background
+   * images, this would be the intrinsic size of the image; for gradients and
+   * patterns this would be the whole target frame fill area.
+   */
+  static void
+  DrawPaintServer(nsIRenderingContext* aRenderingContext,
+                  nsIFrame*            aTarget,
+                  nsIFrame*            aPaintServer,
+                  gfxPattern::GraphicsFilter aFilter,
+                  const nsRect&        aDest,
+                  const nsRect&        aFill,
+                  const nsPoint&       aAnchor,
+                  const nsRect&        aDirty,
+                  const nsSize&        aPaintServerSize);
 };
 
 #endif /*NSSVGINTEGRATIONUTILS_H_*/
--- a/layout/svg/base/src/nsSVGUtils.cpp
+++ b/layout/svg/base/src/nsSVGUtils.cpp
@@ -1565,16 +1565,21 @@ nsSVGUtils::NumberFromString(const nsASt
 // ----------------------------------------------------------------------
 
 nsSVGRenderState::nsSVGRenderState(nsIRenderingContext *aContext) :
   mRenderMode(NORMAL), mRenderingContext(aContext)
 {
   mGfxContext = aContext->ThebesContext();
 }
 
+nsSVGRenderState::nsSVGRenderState(gfxContext *aContext) :
+  mRenderMode(NORMAL), mGfxContext(aContext)
+{
+}
+
 nsSVGRenderState::nsSVGRenderState(gfxASurface *aSurface) :
   mRenderMode(NORMAL)
 {
   mGfxContext = new gfxContext(aSurface);
 }
 
 nsIRenderingContext*
 nsSVGRenderState::GetRenderingContext(nsIFrame *aFrame)
--- a/layout/svg/base/src/nsSVGUtils.h
+++ b/layout/svg/base/src/nsSVGUtils.h
@@ -155,16 +155,20 @@ class nsSVGRenderState
 public:
   enum RenderMode { NORMAL, CLIP, CLIP_MASK };
 
   /**
    * Render SVG to a legacy rendering context
    */
   nsSVGRenderState(nsIRenderingContext *aContext);
   /**
+   * Render SVG to a modern rendering context
+   */
+  nsSVGRenderState(gfxContext *aContext);
+  /**
    * Render SVG to a temporary surface
    */
   nsSVGRenderState(gfxASurface *aSurface);
 
   nsIRenderingContext *GetRenderingContext(nsIFrame *aFrame);
   gfxContext *GetGfxContext() { return mGfxContext; }
 
   void SetRenderMode(RenderMode aMode) { mRenderMode = aMode; }