Bug 772726. Part 13: Fix handling of transforms in gfxTextObjectPaint. r=eflores,jwatt
authorRobert O'Callahan <robert@ocallahan.org>
Wed, 12 Sep 2012 17:13:12 +1200
changeset 114447 e8e05a20947651ea34087d06d8bc9d30a0362234
parent 114446 1fafcf64763d6ad12539ef9eb5c5162fb5b84481
child 114448 0db5a1331a2628590de61ce7edaeebc2f8e74ec3
push idunknown
push userunknown
push dateunknown
reviewerseflores, jwatt
bugs772726
milestone18.0a1
Bug 772726. Part 13: Fix handling of transforms in gfxTextObjectPaint. r=eflores,jwatt gfxTextObjectPaint::GetFillPattern/GetStrokePattern should take the destination's CTM as a parameter in order to set up the pattern matrix correctly, since the pattern matrix is combined with the CTM by cairo/Thebes --- but we want the pattern rendering to be independendt of the CTM, instead depending only on the TM set up when we initialized the gfxTextObjectPaint. We make SVGTextObjectPaint store matrices that map device space to pattern space.
content/canvas/src/nsCanvasRenderingContext2D.cpp
gfx/thebes/gfxFont.cpp
gfx/thebes/gfxPattern.cpp
gfx/thebes/gfxPattern.h
gfx/thebes/gfxSVGGlyphs.h
layout/style/nsStyleStruct.h
layout/svg/base/src/nsSVGGlyphFrame.cpp
layout/svg/base/src/nsSVGGlyphFrame.h
layout/svg/base/src/nsSVGUtils.cpp
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp
@@ -2824,17 +2824,18 @@ struct NS_STACK_CLASS nsCanvasBidiProces
             // TODO: restore this if/when we move to fractional coords
             // throughout the text layout process
         }
 
         nsRefPtr<gfxPattern> pattern = mThebes->GetPattern();
 
         bool isFill = mOp == nsCanvasRenderingContext2D::TEXT_DRAW_OPERATION_FILL;
         SimpleTextObjectPaint objectPaint(isFill ? pattern.get() : nullptr,
-                                          isFill ? nullptr : pattern.get());
+                                          isFill ? nullptr : pattern.get(),
+                                          mThebes->CurrentMatrix());
 
         mTextRun->Draw(mThebes,
                        point,
                        isFill ? gfxFont::GLYPH_FILL : gfxFont::GLYPH_STROKE,
                        0,
                        mTextRun->GetLength(),
                        nullptr,
                        nullptr,
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1531,37 +1531,33 @@ struct GlyphBuffer {
         }
 
         if (aDrawMode == gfxFont::GLYPH_PATH) {
             cairo_glyph_path(aCR, mGlyphBuffer, mNumGlyphs);
         } else {
             if (aDrawMode & gfxFont::GLYPH_FILL) {
                 SAMPLE_LABEL("GlyphBuffer", "cairo_show_glyphs");
                 nsRefPtr<gfxPattern> pattern;
-                if (aObjectPaint && !!(pattern = aObjectPaint->GetFillPattern())) {
-                    gfxMatrix matrix = pattern->GetMatrix().PreMultiply(aGlobalMatrix);
-                    pattern->SetMatrix(matrix);
-
+                if (aObjectPaint &&
+                    !!(pattern = aObjectPaint->GetFillPattern(aGlobalMatrix))) {
                     cairo_save(aCR);
                     cairo_set_source(aCR, pattern->CairoPattern());
                 }
 
                 cairo_show_glyphs(aCR, mGlyphBuffer, mNumGlyphs);
 
                 if (pattern) {
                     cairo_restore(aCR);
                 }
             }
 
             if (aDrawMode & gfxFont::GLYPH_STROKE) {
                 nsRefPtr<gfxPattern> pattern;
-                if (aObjectPaint && !!(pattern = aObjectPaint->GetStrokePattern())) {
-                    gfxMatrix matrix = pattern->GetMatrix().PreMultiply(aGlobalMatrix);
-                    pattern->SetMatrix(matrix);
-
+                if (aObjectPaint &&
+                    !!(pattern = aObjectPaint->GetStrokePattern(aGlobalMatrix))) {
                     cairo_save(aCR);
                     cairo_set_source(aCR, pattern->CairoPattern());
                 }
 
                 cairo_new_path(aCR);
                 cairo_glyph_path(aCR, mGlyphBuffer, mNumGlyphs);
                 cairo_stroke(aCR);
 
@@ -1585,17 +1581,17 @@ struct GlyphBufferAzure {
         : mNumGlyphs(0) { }
 
     Glyph *AppendGlyph() {
         return &mGlyphBuffer[mNumGlyphs++];
     }
 
     void Flush(DrawTarget *aDT, gfxTextObjectPaint *aObjectPaint, ScaledFont *aFont,
                gfxFont::DrawMode aDrawMode, bool aReverse, const GlyphRenderingOptions *aOptions,
-               gfxContext *aThebesContext, const Matrix *invFontMatrix, bool aFinish = false)
+               gfxContext *aThebesContext, const Matrix *aInvFontMatrix, bool aFinish = false)
     {
         // Ensure there's enough room for a glyph to be added to the buffer
         if (!aFinish && mNumGlyphs < GLYPH_BUFFER_SIZE || !mNumGlyphs) {
             return;
         }
 
         if (aReverse) {
             Glyph *begin = &mGlyphBuffer[0];
@@ -1608,45 +1604,52 @@ struct GlyphBufferAzure {
         buf.mNumGlyphs = mNumGlyphs;
 
         gfxContext::AzureState state = aThebesContext->CurrentState();
         if (aDrawMode & gfxFont::GLYPH_FILL) {
             if (state.pattern || aObjectPaint) {
                 Pattern *pat;
 
                 nsRefPtr<gfxPattern> fillPattern;
-                if (!aObjectPaint || !(fillPattern = aObjectPaint->GetFillPattern())) {
+                if (!aObjectPaint ||
+                    !(fillPattern = aObjectPaint->GetFillPattern(aThebesContext->CurrentMatrix()))) {
                     pat = state.pattern->GetPattern(aDT, state.patternTransformChanged ? &state.patternTransform : nullptr);
                 } else {
-                    pat = fillPattern->GetPattern(aDT, state.patternTransformChanged ? &state.patternTransform : nullptr);
+                    pat = fillPattern->GetPattern(aDT);
                 }
 
-                if (invFontMatrix) {
+                Matrix saved;
+                Matrix *mat = nullptr;
+                if (aInvFontMatrix) {
                     // The brush matrix needs to be multiplied with the inverted matrix
                     // as well, to move the brush into the space of the glyphs. Before
                     // the render target transformation
 
                     // This relies on the returned Pattern not to be reused by
                     // others, but regenerated on GetPattern calls. This is true!
-                    Matrix *mat = nullptr;
                     if (pat->GetType() == PATTERN_LINEAR_GRADIENT) {
                         mat = &static_cast<LinearGradientPattern*>(pat)->mMatrix;
                     } else if (pat->GetType() == PATTERN_RADIAL_GRADIENT) {
                         mat = &static_cast<RadialGradientPattern*>(pat)->mMatrix;
                     } else if (pat->GetType() == PATTERN_SURFACE) {
                         mat = &static_cast<SurfacePattern*>(pat)->mMatrix;
                     }
 
                     if (mat) {
-                        *mat = (*mat) * (*invFontMatrix);
+                        saved = *mat;
+                        *mat = (*mat) * (*aInvFontMatrix);
                     }
                 }
 
                 aDT->FillGlyphs(aFont, buf, *pat,
                                 DrawOptions(), aOptions);
+
+                if (mat) {
+                    *mat = saved;
+                }
             } else if (state.sourceSurface) {
                 aDT->FillGlyphs(aFont, buf, SurfacePattern(state.sourceSurface,
                                                            EXTEND_CLAMP,
                                                            state.surfTransform),
                                 DrawOptions(), aOptions);
             } else {
                 aDT->FillGlyphs(aFont, buf, ColorPattern(state.color),
                                 DrawOptions(), aOptions);
@@ -1654,17 +1657,18 @@ struct GlyphBufferAzure {
         }
         if (aDrawMode & gfxFont::GLYPH_PATH) {
             aThebesContext->EnsurePathBuilder();
             aFont->CopyGlyphsToBuilder(buf, aThebesContext->mPathBuilder);
         }
         if (aDrawMode & gfxFont::GLYPH_STROKE) {
             RefPtr<Path> path = aFont->GetPathForGlyphs(buf, aDT);
             if (aObjectPaint) {
-                nsRefPtr<gfxPattern> strokePattern = aObjectPaint->GetStrokePattern();
+                nsRefPtr<gfxPattern> strokePattern =
+                  aObjectPaint->GetStrokePattern(aThebesContext->CurrentMatrix());
                 if (strokePattern) {
                     aDT->Stroke(path, *strokePattern->GetPattern(aDT), state.strokeOptions);
                 }
             }
         }
 
         mNumGlyphs = 0;
     }
@@ -1708,24 +1712,26 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
     if (aStart >= aEnd)
         return;
 
     const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
     const uint32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
     const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit);
     bool isRTL = aTextRun->IsRightToLeft();
     double direction = aTextRun->GetDirection();
+    gfxMatrix globalMatrix = aContext->CurrentMatrix();
 
     bool haveSVGGlyphs = GetFontEntry()->TryGetSVGData();
     nsAutoPtr<gfxTextObjectPaint> objectPaint;
     if (haveSVGGlyphs && !aObjectPaint) {
         // If no pattern is specified for fill, use the current pattern
         NS_ASSERTION((aDrawMode & GLYPH_STROKE) == 0, "no pattern supplied for stroking text");
         nsRefPtr<gfxPattern> fillPattern = aContext->GetPattern();
-        objectPaint = new SimpleTextObjectPaint(fillPattern, nullptr);
+        objectPaint = new SimpleTextObjectPaint(fillPattern, nullptr,
+                                                aContext->CurrentMatrix());
         aObjectPaint = objectPaint;
     }
 
     // synthetic-bold strikes are each offset one device pixel in run direction
     // (these values are only needed if IsSyntheticBold() is true)
     double synBoldOnePixelOffset = 0;
     int32_t strikes = 0;
     if (IsSyntheticBold()) {
@@ -1736,17 +1742,16 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
     }
 
     uint32_t i;
     // Current position in appunits
     double x = aPt->x;
     double y = aPt->y;
 
     cairo_t *cr = aContext->GetCairo();
-    gfxMatrix globalMatrix = aContext->CurrentMatrix();
     RefPtr<DrawTarget> dt = aContext->GetDrawTarget();
 
     if (aContext->IsCairo()) {
       bool success = SetupCairoFont(aContext);
       if (NS_UNLIKELY(!success))
           return;
 
       ::GlyphBuffer glyphs;
--- a/gfx/thebes/gfxPattern.cpp
+++ b/gfx/thebes/gfxPattern.cpp
@@ -409,16 +409,24 @@ gfxPattern::CairoStatus()
 
 void
 gfxPattern::AdjustTransformForPattern(Matrix &aPatternTransform,
                                       const Matrix &aCurrentTransform,
                                       const Matrix *aOriginalTransform)
 {
   aPatternTransform.Invert();
   if (!aOriginalTransform) {
+    // User space is unchanged, so to get from pattern space to user space,
+    // just invert the cairo matrix.
     return;
   }
+  // aPatternTransform now maps from pattern space to the user space defined
+  // by *aOriginalTransform.
 
   Matrix mat = aCurrentTransform;
   mat.Invert();
+  // mat maps from device space to current user space
 
+  // First, transform from pattern space to original user space. Then transform
+  // from original user space to device space. Then transform from
+  // device space to current user space.
   aPatternTransform = aPatternTransform * *aOriginalTransform * mat;
 }
\ No newline at end of file
--- a/gfx/thebes/gfxPattern.h
+++ b/gfx/thebes/gfxPattern.h
@@ -98,16 +98,27 @@ public:
     /* returns TRUE if it succeeded */
     bool GetSolidColor(gfxRGBA& aColor);
 
     already_AddRefed<gfxASurface> GetSurface();
 
 protected:
     cairo_pattern_t *mPattern;
 
+    /**
+     * aPatternTransform is the cairo pattern transform --- from user space at
+     * the time the pattern was set, to pattern space.
+     * aCurrentTransform is the DrawTarget's CTM --- from user space to device
+     * space.
+     * aOriginalTransform, if non-null, is the DrawTarget's TM when
+     * aPatternTransform was set --- user space to device space. If null, then
+     * the DrawTarget's CTM is the same as the TM when aPatternTransfrom was set.
+     * This function sets aPatternTransform to the Azure pattern transform ---
+     * from pattern space to current DrawTarget user space.
+     */
     void AdjustTransformForPattern(mozilla::gfx::Matrix &aPatternTransform,
                                    const mozilla::gfx::Matrix &aCurrentTransform,
                                    const mozilla::gfx::Matrix *aOriginalTransform);
 
     union {
       mozilla::AlignedStorage2<mozilla::gfx::ColorPattern> mColorPattern;
       mozilla::AlignedStorage2<mozilla::gfx::LinearGradientPattern> mLinearGradientPattern;
       mozilla::AlignedStorage2<mozilla::gfx::RadialGradientPattern> mRadialGradientPattern;
--- a/gfx/thebes/gfxSVGGlyphs.h
+++ b/gfx/thebes/gfxSVGGlyphs.h
@@ -188,18 +188,20 @@ protected:
 public:
     static mozilla::gfx::UserDataKey sUserDataKey;
 
     /*
      * Get outer text object pattern with the specified opacity value.
      * This lets us inherit paints and paint opacities (i.e. fill/stroke and
      * fill-opacity/stroke-opacity) separately.
      */
-    virtual already_AddRefed<gfxPattern> GetFillPattern(float aOpacity) = 0;
-    virtual already_AddRefed<gfxPattern> GetStrokePattern(float aOpacity) = 0;
+    virtual already_AddRefed<gfxPattern> GetFillPattern(float aOpacity,
+                                                        const gfxMatrix& aCTM) = 0;
+    virtual already_AddRefed<gfxPattern> GetStrokePattern(float aOpacity,
+                                                          const gfxMatrix& aCTM) = 0;
 
     virtual float GetFillOpacity() { return 1.0f; }
     virtual float GetStrokeOpacity() { return 1.0f; }
 
     void InitStrokeGeometry(gfxContext *aContext,
                             float devUnitsPerSVGUnit);
 
     FallibleTArray<gfxFloat>& GetStrokeDashArray() {
@@ -209,22 +211,22 @@ public:
     gfxFloat GetStrokeDashOffset() {
         return mDashOffset;
     }
 
     gfxFloat GetStrokeWidth() {
         return mStrokeWidth;
     }
 
-    already_AddRefed<gfxPattern> GetFillPattern() {
-        return GetFillPattern(GetFillOpacity());
+    already_AddRefed<gfxPattern> GetFillPattern(const gfxMatrix& aCTM) {
+        return GetFillPattern(GetFillOpacity(), aCTM);
     }
 
-    already_AddRefed<gfxPattern> GetStrokePattern() {
-        return GetStrokePattern(GetStrokeOpacity());
+    already_AddRefed<gfxPattern> GetStrokePattern(const gfxMatrix& aCTM) {
+        return GetStrokePattern(GetStrokeOpacity(), aCTM);
     }
 
     virtual ~gfxTextObjectPaint() { }
 
 private:
     FallibleTArray<gfxFloat> mDashes;
     gfxFloat mDashOffset;
     gfxFloat mStrokeWidth;
@@ -235,35 +237,49 @@ private:
  * opacity value.
  */
 class SimpleTextObjectPaint : public gfxTextObjectPaint
 {
 private:
     static const gfxRGBA sZero;
 
 public:
-    SimpleTextObjectPaint(gfxPattern *aFillPattern, gfxPattern *aStrokePattern) :
-        mFillPattern(aFillPattern ? aFillPattern : new gfxPattern(sZero)),
-        mStrokePattern(aStrokePattern ? aStrokePattern : new gfxPattern(sZero)),
-        mFillMatrix(aFillPattern ? aFillPattern->GetMatrix() : gfxMatrix()),
-        mStrokeMatrix(aStrokePattern ? aStrokePattern->GetMatrix() : gfxMatrix())
+    static gfxMatrix SetupDeviceToPatternMatrix(gfxPattern *aPattern,
+                                                const gfxMatrix& aCTM)
     {
+        if (!aPattern) {
+            return gfxMatrix();
+        }
+        gfxMatrix deviceToUser = aCTM;
+        deviceToUser.Invert();
+        return deviceToUser * aPattern->GetMatrix();
     }
 
-    already_AddRefed<gfxPattern> GetFillPattern(float aOpacity) {
+    SimpleTextObjectPaint(gfxPattern *aFillPattern, gfxPattern *aStrokePattern,
+                          const gfxMatrix& aCTM) :
+        mFillPattern(aFillPattern ? aFillPattern : new gfxPattern(sZero)),
+        mStrokePattern(aStrokePattern ? aStrokePattern : new gfxPattern(sZero))
+    {
+        mFillMatrix = SetupDeviceToPatternMatrix(aFillPattern, aCTM);
+        mStrokeMatrix = SetupDeviceToPatternMatrix(aStrokePattern, aCTM);
+    }
+
+    already_AddRefed<gfxPattern> GetFillPattern(float aOpacity,
+                                                const gfxMatrix& aCTM) {
         if (mFillPattern) {
-            mFillPattern->SetMatrix(mFillMatrix);
+            mFillPattern->SetMatrix(aCTM * mFillMatrix);
         }
         nsRefPtr<gfxPattern> fillPattern = mFillPattern;
         return fillPattern.forget();
     }
 
-    already_AddRefed<gfxPattern> GetStrokePattern(float aOpacity) {
+    already_AddRefed<gfxPattern> GetStrokePattern(float aOpacity,
+                                                  const gfxMatrix& aCTM) {
         if (mStrokePattern) {
-            mStrokePattern->SetMatrix(mStrokeMatrix);
+            mStrokePattern->SetMatrix(aCTM * mStrokeMatrix);
         }
         nsRefPtr<gfxPattern> strokePattern = mStrokePattern;
         return strokePattern.forget();
     }
 
     float GetFillOpacity() {
         return mFillPattern ? 1.0f : 0.0f;
     }
@@ -271,13 +287,14 @@ public:
     float GetStrokeOpacity() {
         return mStrokePattern ? 1.0f : 0.0f;
     }
 
 private:
     nsRefPtr<gfxPattern> mFillPattern;
     nsRefPtr<gfxPattern> mStrokePattern;
 
+    // Device space to pattern space transforms
     gfxMatrix mFillMatrix;
     gfxMatrix mStrokeMatrix;
 };
 
 #endif
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2233,20 +2233,21 @@ struct nsStyleSVG {
   uint8_t          mFillRule;         // [inherited] see nsStyleConsts.h
   uint8_t          mImageRendering;   // [inherited] see nsStyleConsts.h
   uint8_t          mShapeRendering;   // [inherited] see nsStyleConsts.h
   uint8_t          mStrokeLinecap;    // [inherited] see nsStyleConsts.h
   uint8_t          mStrokeLinejoin;   // [inherited] see nsStyleConsts.h
   uint8_t          mTextAnchor;       // [inherited] see nsStyleConsts.h
   uint8_t          mTextRendering;    // [inherited] see nsStyleConsts.h
 
-  //  In SVG glyphs, whether we inherit fill or stroke opacity from the outer
-  // text object
-  nsStyleSVGOpacitySource mFillOpacitySource    : 2;
-  nsStyleSVGOpacitySource mStrokeOpacitySource  : 2;
+  // In SVG glyphs, whether we inherit fill or stroke opacity from the outer
+  // text object.
+  // Use 3 bits to avoid signedness problems in MSVC.
+  nsStyleSVGOpacitySource mFillOpacitySource    : 3;
+  nsStyleSVGOpacitySource mStrokeOpacitySource  : 3;
 
   // SVG glyph outer object inheritance for other properties
   bool mStrokeDasharrayFromObject   : 1;
   bool mStrokeDashoffsetFromObject  : 1;
   bool mStrokeWidthFromObject       : 1;
 };
 
 struct nsStyleSVGReset {
--- a/layout/svg/base/src/nsSVGGlyphFrame.cpp
+++ b/layout/svg/base/src/nsSVGGlyphFrame.cpp
@@ -1063,91 +1063,102 @@ nsSVGGlyphFrame::SetupObjectPaint(gfxCon
   const nsStyleSVG *style = GetStyleSVG();
   const nsStyleSVGPaint &paint = style->*aFillOrStroke;
 
   if (paint.mType != eStyleSVGPaintType_ObjectFill &&
       paint.mType != eStyleSVGPaintType_ObjectStroke) {
     return false;
   }
 
-  nsRefPtr<gfxPattern> pattern = paint.mType == eStyleSVGPaintType_ObjectFill ?
-                                 aOuterObjectPaint->GetFillPattern(aOpacity) :
-                                 aOuterObjectPaint->GetStrokePattern(aOpacity);
-
+  gfxMatrix current = aContext->CurrentMatrix();
+  nsRefPtr<gfxPattern> pattern =
+    paint.mType == eStyleSVGPaintType_ObjectFill ?
+      aOuterObjectPaint->GetFillPattern(aOpacity, current) :
+      aOuterObjectPaint->GetStrokePattern(aOpacity, current);
   if (!pattern) {
     return false;
   }
 
-  pattern->SetMatrix(aContext->CurrentMatrix().Multiply(pattern->GetMatrix()));
   aContext->SetPattern(pattern);
-
   return true;
 }
 
 //----------------------------------------------------------------------
 // SVGTextObjectPaint methods:
 
 already_AddRefed<gfxPattern>
-nsSVGGlyphFrame::SVGTextObjectPaint::GetFillPattern(float aOpacity)
+nsSVGGlyphFrame::SVGTextObjectPaint::GetFillPattern(float aOpacity,
+                                                    const gfxMatrix& aCTM)
 {
-  return mFillPaint.GetPattern(aOpacity, &nsStyleSVG::mFill);
+  return mFillPaint.GetPattern(aOpacity, &nsStyleSVG::mFill, aCTM);
 }
 
 already_AddRefed<gfxPattern>
-nsSVGGlyphFrame::SVGTextObjectPaint::GetStrokePattern(float aOpacity)
+nsSVGGlyphFrame::SVGTextObjectPaint::GetStrokePattern(float aOpacity,
+                                                      const gfxMatrix& aCTM)
 {
-  return mStrokePaint.GetPattern(aOpacity, &nsStyleSVG::mStroke);
+  return mStrokePaint.GetPattern(aOpacity, &nsStyleSVG::mStroke, aCTM);
 }
 
 already_AddRefed<gfxPattern>
 nsSVGGlyphFrame::SVGTextObjectPaint::Paint::GetPattern(float aOpacity,
-                                                       nsStyleSVGPaint nsStyleSVG::*aFillOrStroke)
+                                                       nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
+                                                       const gfxMatrix& aCTM)
 {
   nsRefPtr<gfxPattern> pattern;
   if (mPatternCache.Get(aOpacity, getter_AddRefs(pattern))) {
     // Set the pattern matrix just in case it was messed with by a previous
     // caller. We should get the same matrix each time a pattern is constructed
     // so this should be fine.
-    pattern->SetMatrix(mPatternMatrix);
+    pattern->SetMatrix(aCTM * mPatternMatrix);
     return pattern.forget();
   }
 
   switch (mPaintType) {
   case eStyleSVGPaintType_None:
     pattern = new gfxPattern(gfxRGBA(0.0f, 0.0f, 0.0f, 0.0f));
+    mPatternMatrix = gfxMatrix();
     break;
   case eStyleSVGPaintType_Color:
     pattern = new gfxPattern(gfxRGBA(NS_GET_R(mPaintDefinition.mColor) / 255.0,
                                      NS_GET_G(mPaintDefinition.mColor) / 255.0,
                                      NS_GET_B(mPaintDefinition.mColor) / 255.0,
                                      NS_GET_A(mPaintDefinition.mColor) / 255.0 * aOpacity));
+    mPatternMatrix = gfxMatrix();
     break;
   case eStyleSVGPaintType_Server:
     pattern = mPaintDefinition.mPaintServerFrame->GetPaintServerPattern(mFrame,
                                                                         mContextMatrix,
                                                                         aFillOrStroke,
                                                                         aOpacity);
+    {
+      // m maps original-user-space to pattern space
+      gfxMatrix m = pattern->GetMatrix();
+      gfxMatrix deviceToOriginalUserSpace = mContextMatrix;
+      deviceToOriginalUserSpace.Invert();
+      // mPatternMatrix maps device space to pattern space via original user space
+      mPatternMatrix = deviceToOriginalUserSpace * m;
+    }
+    pattern->SetMatrix(aCTM * mPatternMatrix);
     break;
   case eStyleSVGPaintType_ObjectFill:
-    pattern = mPaintDefinition.mObjectPaint->GetFillPattern(aOpacity);
-    break;
+    pattern = mPaintDefinition.mObjectPaint->GetFillPattern(aOpacity, aCTM);
+    // Don't cache this. mObjectPaint will have cached it anyway. If we
+    // cache it, we'll have to compute mPatternMatrix, which is annoying.
+    return pattern.forget();
   case eStyleSVGPaintType_ObjectStroke:
-    pattern = mPaintDefinition.mObjectPaint->GetStrokePattern(aOpacity);
-    break;
+    pattern = mPaintDefinition.mObjectPaint->GetStrokePattern(aOpacity, aCTM);
+    // Don't cache this. mObjectPaint will have cached it anyway. If we
+    // cache it, we'll have to compute mPatternMatrix, which is annoying.
+    return pattern.forget();
   default:
     return nullptr;
   }
 
-  gfxMatrix contextInverse = mContextMatrix;
-  contextInverse.Invert();
-
-  mPatternMatrix = pattern->GetMatrix().PreMultiply(contextInverse);
-  pattern->SetMatrix(mPatternMatrix);
   mPatternCache.Put(aOpacity, pattern);
-
   return pattern.forget();
 }
 
 //----------------------------------------------------------------------
 
 // Utilities for converting from indices in the uncompressed content
 // element strings to compressed frame string and back:
 static int
--- a/layout/svg/base/src/nsSVGGlyphFrame.h
+++ b/layout/svg/base/src/nsSVGGlyphFrame.h
@@ -268,18 +268,20 @@ private:
 
 private:
   DrawMode SetupCairoState(gfxContext *aContext,
                            gfxTextObjectPaint *aOuterObjectPaint,
                            gfxTextObjectPaint **aThisObjectPaint);
 
   // Slightly horrible callback for deferring application of opacity
   struct SVGTextObjectPaint : public gfxTextObjectPaint {
-    already_AddRefed<gfxPattern> GetFillPattern(float opacity);
-    already_AddRefed<gfxPattern> GetStrokePattern(float opacity);
+    already_AddRefed<gfxPattern> GetFillPattern(float aOpacity,
+                                                const gfxMatrix& aCTM);
+    already_AddRefed<gfxPattern> GetStrokePattern(float aOpacity,
+                                                  const gfxMatrix& aCTM);
 
     void SetFillOpacity(float aOpacity) { mFillOpacity = aOpacity; }
     float GetFillOpacity() { return mFillOpacity; }
 
     void SetStrokeOpacity(float aOpacity) { mStrokeOpacity = aOpacity; }
     float GetStrokeOpacity() { return mStrokeOpacity; }
 
     struct Paint {
@@ -311,24 +313,27 @@ private:
 
       union {
         nsSVGPaintServerFrame *mPaintServerFrame;
         gfxTextObjectPaint *mObjectPaint;
         nscolor mColor;
       } mPaintDefinition;
 
       nsIFrame *mFrame;
+      // CTM defining the user space for the pattern we will use.
       gfxMatrix mContextMatrix;
       nsStyleSVGPaintType mPaintType;
 
+      // Device-space-to-pattern-space
       gfxMatrix mPatternMatrix;
       nsRefPtrHashtable<nsFloatHashKey, gfxPattern> mPatternCache;
 
       already_AddRefed<gfxPattern> GetPattern(float aOpacity,
-                                              nsStyleSVGPaint nsStyleSVG::*aFillOrStroke);
+                                              nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
+                                              const gfxMatrix& aCTM);
     };
 
     Paint mFillPaint;
     Paint mStrokePaint;
 
     float mFillOpacity;
     float mStrokeOpacity;
   };
--- a/layout/svg/base/src/nsSVGUtils.cpp
+++ b/layout/svg/base/src/nsSVGUtils.cpp
@@ -1901,32 +1901,29 @@ nsSVGUtils::SetupObjectPaint(gfxContext 
   nsRefPtr<gfxPattern> pattern;
 
   if (!aObjectPaint) {
     return false;
   }
 
   switch (aPaint.mType) {
     case eStyleSVGPaintType_ObjectFill:
-      pattern = aObjectPaint->GetFillPattern(aOpacity);
+      pattern = aObjectPaint->GetFillPattern(aOpacity, aContext->CurrentMatrix());
       break;
     case eStyleSVGPaintType_ObjectStroke:
-      pattern = aObjectPaint->GetStrokePattern(aOpacity);
+      pattern = aObjectPaint->GetStrokePattern(aOpacity, aContext->CurrentMatrix());
       break;
     default:
       return false;
   }
 
   if (!pattern) {
     return false;
   }
 
-  // When the pattern is set, cairo sets the context source matrix to the
-  // inverse of the context matrix so we have to account for that here
-  pattern->SetMatrix(aContext->CurrentMatrix().Multiply(pattern->GetMatrix()));
   aContext->SetPattern(pattern);
 
   return true;
 }
 
 bool
 nsSVGUtils::SetupCairoFillPaint(nsIFrame *aFrame, gfxContext* aContext,
                                 gfxTextObjectPaint *aObjectPaint)