Bug 715652 - Implement significantly more of the 2D API for the Cairo backend. r=jrmuizel
authorJoe Drew <joe@drew.ca>
Mon, 09 Jan 2012 16:50:01 -0500
changeset 85460 4b4d5cd00d43f83f097b2b7a0b7d1e407b5e4081
parent 85459 53a8a6b4c812103b51e17023410224ea14b0de40
child 85461 14930a83054b9cfda27d7e60f54680215fd8ac63
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs715652
milestone12.0a1
Bug 715652 - Implement significantly more of the 2D API for the Cairo backend. r=jrmuizel
gfx/2d/DrawTargetCairo.cpp
gfx/2d/DrawTargetCairo.h
gfx/2d/SourceSurfaceCairo.cpp
gfx/2d/SourceSurfaceCairo.h
gfx/2d/Types.h
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -30,190 +30,819 @@
  * 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 "DrawTargetCairo.h"
+
 #include "SourceSurfaceCairo.h"
 
+#include "cairo.h"
+
+#include "Blur.h"
+
+#ifdef CAIRO_HAS_QUARTZ_SURFACE
+#include "cairo-quartz.h"
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
+#ifdef CAIRO_HAS_XLIB_SURFACE
+#include "cairo-xlib.h"
+#endif
+
+#include <algorithm>
+
 namespace mozilla {
 namespace gfx {
 
-cairo_operator_t
+static cairo_operator_t
 GfxOpToCairoOp(CompositionOp op)
 {
   switch (op)
   {
     case OP_OVER:
       return CAIRO_OPERATOR_OVER;
-    case OP_SOURCE:
-      return CAIRO_OPERATOR_SOURCE;
     case OP_ADD:
       return CAIRO_OPERATOR_ADD;
     case OP_ATOP:
       return CAIRO_OPERATOR_ATOP;
+    case OP_OUT:
+      return CAIRO_OPERATOR_OUT;
+    case OP_IN:
+      return CAIRO_OPERATOR_IN;
+    case OP_SOURCE:
+      return CAIRO_OPERATOR_SOURCE;
+    case OP_DEST_IN:
+      return CAIRO_OPERATOR_DEST_IN;
+    case OP_DEST_OUT:
+      return CAIRO_OPERATOR_DEST_OUT;
+    case OP_DEST_OVER:
+      return CAIRO_OPERATOR_DEST_OVER;
+    case OP_DEST_ATOP:
+      return CAIRO_OPERATOR_DEST_ATOP;
+    case OP_XOR:
+      return CAIRO_OPERATOR_XOR;
     case OP_COUNT:
       break;
   }
 
   return CAIRO_OPERATOR_OVER;
 }
 
-cairo_filter_t
+static cairo_filter_t
 GfxFilterToCairoFilter(Filter filter)
 {
   switch (filter)
   {
     case FILTER_LINEAR:
       return CAIRO_FILTER_BILINEAR;
     case FILTER_POINT:
       return CAIRO_FILTER_NEAREST;
   }
 
   return CAIRO_FILTER_BILINEAR;
 }
 
-cairo_format_t
+static cairo_extend_t
+GfxExtendToCairoExtend(ExtendMode extend)
+{
+  switch (extend)
+  {
+    case EXTEND_CLAMP:
+      return CAIRO_EXTEND_PAD;
+    case EXTEND_REPEAT:
+      return CAIRO_EXTEND_REPEAT;
+    case EXTEND_REFLECT:
+      return CAIRO_EXTEND_REFLECT;
+  }
+
+  return CAIRO_EXTEND_PAD;
+}
+
+static cairo_format_t
 GfxFormatToCairoFormat(SurfaceFormat format)
 {
   switch (format)
   {
     case FORMAT_B8G8R8A8:
       return CAIRO_FORMAT_ARGB32;
     case FORMAT_B8G8R8X8:
       return CAIRO_FORMAT_RGB24;
     case FORMAT_A8:
       return CAIRO_FORMAT_A8;
   }
 
   return CAIRO_FORMAT_ARGB32;
 }
 
+static cairo_content_t
+GfxFormatToCairoContent(SurfaceFormat format)
+{
+  switch (format)
+  {
+    case FORMAT_B8G8R8A8:
+      return CAIRO_CONTENT_COLOR_ALPHA;
+    case FORMAT_B8G8R8X8:
+      return CAIRO_CONTENT_COLOR;
+    case FORMAT_A8:
+      return CAIRO_CONTENT_ALPHA;
+  }
+
+  return CAIRO_CONTENT_COLOR_ALPHA;
+}
+
+static cairo_line_join_t
+GfxLineJoinToCairoLineJoin(JoinStyle style)
+{
+  switch (style)
+  {
+    case JOIN_BEVEL:
+      return CAIRO_LINE_JOIN_BEVEL;
+    case JOIN_ROUND:
+      return CAIRO_LINE_JOIN_ROUND;
+    case JOIN_MITER:
+      return CAIRO_LINE_JOIN_MITER;
+    case JOIN_MITER_OR_BEVEL:
+      return CAIRO_LINE_JOIN_MITER;
+  }
+
+  return CAIRO_LINE_JOIN_MITER;
+}
+
+static cairo_line_cap_t
+GfxLineCapToCairoLineCap(CapStyle style)
+{
+  switch (style)
+  {
+    case CAP_BUTT:
+      return CAIRO_LINE_CAP_BUTT;
+    case CAP_ROUND:
+      return CAIRO_LINE_CAP_ROUND;
+    case CAP_SQUARE:
+      return CAIRO_LINE_CAP_SQUARE;
+  }
+
+  return CAIRO_LINE_CAP_BUTT;
+}
+
+static SurfaceFormat
+CairoContentToGfxFormat(cairo_content_t content)
+{
+  switch (content)
+  {
+    case CAIRO_CONTENT_COLOR_ALPHA:
+      return FORMAT_B8G8R8A8;
+    case CAIRO_CONTENT_COLOR:
+      return FORMAT_B8G8R8X8;
+    case CAIRO_CONTENT_ALPHA:
+      return FORMAT_A8;
+  }
+
+  return FORMAT_B8G8R8A8;
+}
+
+static bool
+GetCairoSurfaceSize(cairo_surface_t* surface, IntSize& size)
+{
+  switch (cairo_surface_get_type(surface))
+  {
+    case CAIRO_SURFACE_TYPE_IMAGE:
+    {
+      size.width = cairo_image_surface_get_width(surface);
+      size.height = cairo_image_surface_get_height(surface);
+      return true;
+    }
+
+#ifdef CAIRO_HAS_XLIB_SURFACE
+    case CAIRO_SURFACE_TYPE_XLIB:
+    {
+      size.width = cairo_xlib_surface_get_width(surface);
+      size.height = cairo_xlib_surface_get_height(surface);
+      return true;
+    }
+#endif
+
+#ifdef CAIRO_HAS_QUARTZ_SURFACE
+    case CAIRO_SURFACE_TYPE_QUARTZ:
+    {
+      CGContextRef cgc = cairo_quartz_surface_get_cg_context(surface);
+
+      // It's valid to call these CGBitmapContext functions on non-bitmap
+      // contexts; they'll just return 0 in that case.
+      size.width = CGBitmapContextGetWidth(cgc);
+      size.height = CGBitmapContextGetWidth(cgc);
+      return size.width != 0;
+    }
+#endif
+
+    default:
+      return false;
+  }
+}
+
 void
 GfxMatrixToCairoMatrix(const Matrix& mat, cairo_matrix_t& retval)
 {
   cairo_matrix_init(&retval, mat._11, mat._12, mat._21, mat._22, mat._31, mat._32);
 }
 
+cairo_pattern_t*
+GfxPatternToCairoPattern(const Pattern& aPattern, Float aAlpha)
+{
+  cairo_pattern_t* pat = NULL;
+
+  switch (aPattern.GetType())
+  {
+    case PATTERN_COLOR:
+    {
+      Color color = static_cast<const ColorPattern&>(aPattern).mColor;
+      pat = cairo_pattern_create_rgba(color.r, color.g, color.b, color.a * aAlpha);
+      break;
+    }
+
+    case PATTERN_SURFACE:
+    {
+      const SurfacePattern& pattern = static_cast<const SurfacePattern&>(aPattern);
+      cairo_surface_t* surf = NULL;
+
+      if (pattern.mSurface->GetType() == SURFACE_CAIRO) {
+        const SourceSurfaceCairo* sourcesurf = static_cast<const SourceSurfaceCairo*>(pattern.mSurface.get());
+        surf = sourcesurf->GetSurface();
+        cairo_surface_reference(surf);
+      } else if (pattern.mSurface->GetType() == SURFACE_CAIRO_IMAGE) {
+        const DataSourceSurfaceCairo* sourcesurf =
+          static_cast<const DataSourceSurfaceCairo*>(pattern.mSurface.get());
+        surf = sourcesurf->GetSurface();
+        cairo_surface_reference(surf);
+      } else {
+        RefPtr<DataSourceSurface> sourcesurf = pattern.mSurface->GetDataSurface();
+        surf = cairo_image_surface_create_for_data(sourcesurf->GetData(),
+                                                   GfxFormatToCairoFormat(sourcesurf->GetFormat()),
+                                                   sourcesurf->GetSize().width,
+                                                   sourcesurf->GetSize().height,
+                                                   sourcesurf->Stride());
+      }
+
+      pat = cairo_pattern_create_for_surface(surf);
+      cairo_pattern_set_filter(pat, GfxFilterToCairoFilter(pattern.mFilter));
+      cairo_pattern_set_extend(pat, GfxExtendToCairoExtend(pattern.mExtendMode));
+
+      cairo_surface_destroy(surf);
+
+      break;
+    }
+    case PATTERN_LINEAR_GRADIENT:
+    {
+      const LinearGradientPattern& pattern = static_cast<const LinearGradientPattern&>(aPattern);
+      RefPtr<GradientStops> stops = pattern.mStops;
+      if (stops->GetBackendType() == BACKEND_CAIRO) {
+        pat = cairo_pattern_create_linear(pattern.mBegin.x, pattern.mBegin.y,
+                                          pattern.mEnd.x, pattern.mEnd.y);
+
+        const std::vector<GradientStop>& stops =
+          static_cast<GradientStopsCairo*>(pattern.mStops.get())->GetStops();
+        for (std::vector<GradientStop>::const_iterator i = stops.begin();
+             i != stops.end();
+             ++i) {
+          cairo_pattern_add_color_stop_rgba(pat, i->offset, i->color.r,
+                                            i->color.g, i->color.b,
+                                            i->color.a);
+        }
+      }
+
+      break;
+    }
+    case PATTERN_RADIAL_GRADIENT:
+    {
+      const RadialGradientPattern& pattern = static_cast<const RadialGradientPattern&>(aPattern);
+      RefPtr<GradientStops> stops = pattern.mStops;
+      if (stops->GetBackendType() == BACKEND_CAIRO) {
+        pat = cairo_pattern_create_radial(pattern.mCenter1.x, pattern.mCenter1.y, pattern.mRadius1,
+                                          pattern.mCenter2.x, pattern.mCenter2.y, pattern.mRadius2);
+
+        const std::vector<GradientStop>& stops =
+          static_cast<GradientStopsCairo*>(pattern.mStops.get())->GetStops();
+        for (std::vector<GradientStop>::const_iterator i = stops.begin();
+             i != stops.end();
+             ++i) {
+          cairo_pattern_add_color_stop_rgba(pat, i->offset, i->color.r,
+                                            i->color.g, i->color.b,
+                                            i->color.a);
+        }
+      }
+
+      break;
+    }
+  }
+
+  return pat;
+}
+
+bool
+NeedIntermediateSurface(const Pattern& aPattern, const DrawOptions& aOptions)
+{
+  // We pre-multiply colours' alpha by the global alpha, so we don't need to
+  // use an intermediate surface for them.
+  if (aPattern.GetType() == PATTERN_COLOR)
+    return false;
+
+  if (aOptions.mAlpha == 1.0)
+    return false;
+
+  return true;
+}
+
 DrawTargetCairo::DrawTargetCairo()
   : mContext(NULL)
 {
 }
 
 DrawTargetCairo::~DrawTargetCairo()
 {
+  MarkSnapshotsIndependent();
   cairo_destroy(mContext);
 }
 
+IntSize
+DrawTargetCairo::GetSize()
+{
+  return IntSize();
+}
+
 TemporaryRef<SourceSurface>
 DrawTargetCairo::Snapshot()
 {
+  cairo_surface_t* csurf = cairo_get_target(mContext);
+  IntSize size;
+  if (GetCairoSurfaceSize(csurf, size)) {
+    cairo_content_t content = cairo_surface_get_content(csurf);
+    RefPtr<SourceSurfaceCairo> surf = new SourceSurfaceCairo(csurf, size,
+                                                             CairoContentToGfxFormat(content),
+                                                             this);
+    AppendSnapshot(surf);
+    return surf;
+  }
+
   return NULL;
 }
 
 void
 DrawTargetCairo::Flush()
 {
   cairo_surface_t* surf = cairo_get_target(mContext);
   cairo_surface_flush(surf);
 }
 
 void
+DrawTargetCairo::PrepareForDrawing(cairo_t* aContext)
+{
+  MarkChanged();
+
+  cairo_matrix_t mat;
+  GfxMatrixToCairoMatrix(mTransform, mat);
+  cairo_set_matrix(aContext, &mat);
+}
+
+void
 DrawTargetCairo::DrawSurface(SourceSurface *aSurface,
                              const Rect &aDest,
                              const Rect &aSource,
                              const DrawSurfaceOptions &aSurfOptions,
                              const DrawOptions &aOptions)
 {
+  PrepareForDrawing(mContext);
+
   float sx = aSource.Width() / aDest.Width();
   float sy = aSource.Height() / aDest.Height();
 
   cairo_matrix_t src_mat;
   cairo_matrix_init_scale(&src_mat, sx, sy);
-  cairo_matrix_translate(&src_mat, -aSource.X(), -aSource.Y());
+  cairo_matrix_translate(&src_mat, aSource.X(), aSource.Y());
 
   cairo_surface_t* surf = NULL;
   if (aSurface->GetType() == SURFACE_CAIRO) {
     surf = static_cast<SourceSurfaceCairo*>(aSurface)->GetSurface();
   }
 
   cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf);
   cairo_pattern_set_matrix(pat, &src_mat);
   cairo_pattern_set_filter(pat, GfxFilterToCairoFilter(aSurfOptions.mFilter));
 
+  cairo_save(mContext);
+
   cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
-  cairo_rectangle(mContext, aDest.X(), aDest.Y(),
-                  aDest.Width(), aDest.Height());
-  cairo_fill(mContext);
+
+  cairo_translate(mContext, aDest.X(), aDest.Y());
+
+  cairo_set_source(mContext, pat);
+
+  cairo_new_path(mContext);
+  cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height());
+  cairo_clip(mContext);
+  cairo_paint_with_alpha(mContext, aOptions.mAlpha);
+
+  cairo_restore(mContext);
 
   cairo_pattern_destroy(pat);
 }
 
 void
+DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface *aSurface,
+                                       const Point &aDest,
+                                       const Color &aColor,
+                                       const Point &aOffset,
+                                       Float aSigma,
+                                       CompositionOp aOperator)
+{
+  MarkChanged();
+
+  if (aSurface->GetType() != SURFACE_CAIRO) {
+    return;
+  }
+
+  SourceSurfaceCairo* sourcesurf = static_cast<SourceSurfaceCairo*>(aSurface);
+
+  Float width = aSurface->GetSize().width,
+        height = aSurface->GetSize().height;
+  Rect extents(0, 0, width, height);
+
+  AlphaBoxBlur blur(extents, IntSize(0, 0),
+                    AlphaBoxBlur::CalculateBlurRadius(Point(aSigma, aSigma)),
+                    NULL, NULL);
+  if (!blur.GetData()) {
+    return;
+  }
+
+  IntSize blursize = blur.GetSize();
+
+  cairo_surface_t* blursurf = cairo_image_surface_create_for_data(blur.GetData(),
+                                                                  CAIRO_FORMAT_A8,
+                                                                  blursize.width,
+                                                                  blursize.height,
+                                                                  blur.GetStride());
+
+  // Draw the source surface into the surface we're going to blur.
+  cairo_surface_t* surf = sourcesurf->GetSurface();
+  cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf);
+
+  cairo_t* ctx = cairo_create(blursurf);
+
+  cairo_set_source(ctx, pat);
+
+  IntRect blurrect = blur.GetRect();
+  cairo_new_path(ctx);
+  cairo_rectangle(ctx, blurrect.x, blurrect.y, blurrect.width, blurrect.height);
+  cairo_clip(ctx);
+  cairo_paint(ctx);
+
+  cairo_destroy(ctx);
+
+  // Blur the result, then use that blurred result as a mask to draw the shadow
+  // colour to the surface.
+  blur.Blur();
+
+  cairo_save(mContext);
+
+  cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
+  cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a);
+
+  cairo_identity_matrix(mContext);
+  cairo_translate(mContext, aDest.x, aDest.y);
+
+  cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y);
+
+  // Now that the shadow has been drawn, we can draw the surface on top.
+
+  cairo_set_operator(mContext, GfxOpToCairoOp(aOperator));
+
+  cairo_set_source(mContext, pat);
+
+  cairo_new_path(mContext);
+  cairo_rectangle(mContext, 0, 0, width, height);
+  cairo_clip(mContext);
+
+  cairo_paint(mContext);
+
+  cairo_restore(mContext);
+
+  cairo_pattern_destroy(pat);
+}
+
+void
+SetStrokeOptions(cairo_t* aCtx, const StrokeOptions& aStrokeOptions)
+{
+  cairo_set_line_width(aCtx, aStrokeOptions.mLineWidth);
+
+  cairo_set_miter_limit(aCtx, aStrokeOptions.mMiterLimit);
+
+  if (aStrokeOptions.mDashPattern) {
+    // Convert array of floats to array of doubles
+    std::vector<double> dashes(aStrokeOptions.mDashLength);
+    for (size_t i = 0; i < aStrokeOptions.mDashLength; ++i) {
+      dashes[i] = aStrokeOptions.mDashPattern[i];
+    }
+    cairo_set_dash(aCtx, &dashes[0], aStrokeOptions.mDashLength,
+                   aStrokeOptions.mDashOffset);
+  }
+
+  cairo_set_line_join(aCtx, GfxLineJoinToCairoLineJoin(aStrokeOptions.mLineJoin));
+
+  cairo_set_line_cap(aCtx, GfxLineCapToCairoLineCap(aStrokeOptions.mLineCap));
+}
+
+void
+DrawTargetCairo::DrawPattern(const Rect& aRect,
+                             const Pattern& aPattern,
+                             const StrokeOptions& aStrokeOptions,
+                             const DrawOptions& aOptions,
+                             DrawPatternType aDrawType)
+{
+  cairo_save(mContext);
+
+  cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
+
+  bool needIntermediate = NeedIntermediateSurface(aPattern, aOptions);
+  if (needIntermediate) {
+    cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA);
+
+    // Don't want operators to be applied twice
+    cairo_set_operator(mContext, CAIRO_OPERATOR_SOURCE);
+  }
+
+  cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha);
+
+  if (pat) {
+    cairo_set_source(mContext, pat);
+
+    cairo_new_path(mContext);
+    cairo_rectangle(mContext, aRect.x, aRect.y, aRect.Width(), aRect.Height());
+
+    if (aDrawType == DRAW_STROKE) {
+      SetStrokeOptions(mContext, aStrokeOptions);
+      cairo_stroke(mContext);
+    } else {
+      // It's possible that we could simply always clip and paint here, but the
+      // old canvas implementation didn't, so to maintain similar performance
+      // characteristics we choose the same.
+      if (needIntermediate || aOptions.mAlpha == 1.0) {
+        cairo_fill(mContext);
+      } else {
+        cairo_clip(mContext);
+        cairo_paint(mContext);
+      }
+    }
+
+    cairo_pattern_destroy(pat);
+  }
+
+  if (needIntermediate) {
+    cairo_pop_group_to_source(mContext);
+
+    cairo_paint_with_alpha(mContext, aOptions.mAlpha);
+  }
+
+  cairo_restore(mContext);
+}
+
+void
 DrawTargetCairo::FillRect(const Rect &aRect,
                           const Pattern &aPattern,
                           const DrawOptions &aOptions)
 {
+  PrepareForDrawing(mContext);
+
+  DrawPattern(aRect, aPattern, StrokeOptions(), aOptions, DRAW_FILL);
+}
+
+void
+DrawTargetCairo::CopySurface(SourceSurface *aSurface,
+                             const IntRect &aSourceRect,
+                             const IntPoint &aDestination)
+{
+  PrepareForDrawing(mContext);
+}
+
+void
+DrawTargetCairo::ClearRect(const Rect& aRect)
+{
+  PrepareForDrawing(mContext);
+
+  cairo_save(mContext);
+
   cairo_new_path(mContext);
-  cairo_rectangle(mContext, aRect.x, aRect.y, aRect.Width(), aRect.Height());
+  cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR);
+  cairo_rectangle(mContext, aRect.X(), aRect.Y(),
+                  aRect.Width(), aRect.Height());
+  cairo_fill(mContext);
 
-  cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
+  cairo_restore(mContext);
+}
 
-  if (aPattern.GetType() == PATTERN_COLOR) {
-    Color color = static_cast<const ColorPattern&>(aPattern).mColor;
-    cairo_set_source_rgba(mContext, color.r, color.g,
-                                    color.b, color.a);
+void
+DrawTargetCairo::StrokeRect(const Rect &aRect,
+                            const Pattern &aPattern,
+                            const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
+                            const DrawOptions &aOptions /* = DrawOptions() */)
+{
+  PrepareForDrawing(mContext);
+
+  DrawPattern(aRect, aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
+}
+
+void
+DrawTargetCairo::StrokeLine(const Point &aStart,
+                            const Point &aEnd,
+                            const Pattern &aPattern,
+                            const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
+                            const DrawOptions &aOptions /* = DrawOptions() */)
+{
+  PrepareForDrawing(mContext);
+
+  cairo_save(mContext);
+
+  cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha);
+  if (pat) {
+    SetStrokeOptions(mContext, aStrokeOptions);
+    cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
+
+    cairo_set_source(mContext, pat);
+
+    cairo_new_path(mContext);
+    cairo_move_to(mContext, aStart.x, aStart.y);
+    cairo_line_to(mContext, aEnd.x, aEnd.y);
+
+    cairo_stroke(mContext);
+
+    cairo_pattern_destroy(pat);
   }
 
-  cairo_fill(mContext);
+  cairo_restore(mContext);
+}
+
+void
+DrawTargetCairo::Stroke(const Path *aPath,
+                        const Pattern &aPattern,
+                        const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
+                        const DrawOptions &aOptions /* = DrawOptions() */)
+{
+  PrepareForDrawing(mContext);
+}
+
+void
+DrawTargetCairo::Fill(const Path *aPath,
+                      const Pattern &aPattern,
+                      const DrawOptions &aOptions /* = DrawOptions() */)
+{
+  PrepareForDrawing(mContext);
+}
+
+void
+DrawTargetCairo::FillGlyphs(ScaledFont *aFont,
+                            const GlyphBuffer &aBuffer,
+                            const Pattern &aPattern,
+                            const DrawOptions &aOptions)
+{
+  PrepareForDrawing(mContext);
+}
+
+void
+DrawTargetCairo::PushClip(const Path *aPath)
+{
+}
+
+void
+DrawTargetCairo::PopClip()
+{
+}
+
+TemporaryRef<PathBuilder>
+DrawTargetCairo::CreatePathBuilder(FillRule aFillRule /* = FILL_WINDING */) const
+{
+  return NULL;
+}
+
+TemporaryRef<GradientStops>
+DrawTargetCairo::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, ExtendMode aExtendMode) const
+{
+  RefPtr<GradientStopsCairo> stops = new GradientStopsCairo(aStops, aNumStops);
+  return stops;
 }
 
 TemporaryRef<SourceSurface>
 DrawTargetCairo::CreateSourceSurfaceFromData(unsigned char *aData,
                                              const IntSize &aSize,
                                              int32_t aStride,
                                              SurfaceFormat aFormat) const
 {
   cairo_surface_t* surf = cairo_image_surface_create_for_data(aData,
                                                               GfxFormatToCairoFormat(aFormat),
                                                               aSize.width,
                                                               aSize.height,
                                                               aStride);
-  RefPtr<SourceSurfaceCairo> source_surf = new SourceSurfaceCairo();
-  source_surf->InitFromSurface(surf, aSize, aFormat);
+  RefPtr<SourceSurfaceCairo> source_surf = new SourceSurfaceCairo(surf, aSize, aFormat);
   cairo_surface_destroy(surf);
   return source_surf;
 }
 
 TemporaryRef<SourceSurface>
 DrawTargetCairo::OptimizeSourceSurface(SourceSurface *aSurface) const
 {
-  return NULL;
+  return aSurface;
 }
 
 TemporaryRef<SourceSurface>
 DrawTargetCairo::CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const
 {
+  if (aSurface.mType == NATIVE_SURFACE_CAIRO_SURFACE) {
+    IntSize size;
+    cairo_surface_t* surf = static_cast<cairo_surface_t*>(aSurface.mSurface);
+    if (GetCairoSurfaceSize(surf, size)) {
+      RefPtr<SourceSurfaceCairo> sourcesurf =
+        new SourceSurfaceCairo(surf, size, aSurface.mFormat);
+      return sourcesurf;
+    }
+  }
+
+  return NULL;
+}
+
+TemporaryRef<DrawTarget>
+DrawTargetCairo::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
+{
+  cairo_surface_t* similar = cairo_surface_create_similar(cairo_get_target(mContext),
+                                                          GfxFormatToCairoContent(aFormat),
+                                                          aSize.width, aSize.height);
+
+  if (!cairo_surface_status(similar)) {
+    RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
+    target->Init(similar);
+    return target;
+  }
+
   return NULL;
 }
 
 bool
 DrawTargetCairo::Init(cairo_surface_t* aSurface)
 {
   mContext = cairo_create(aSurface);
 
   return true;
 }
 
+void *
+DrawTargetCairo::GetNativeSurface(NativeSurfaceType aType)
+{
+  if (aType == NATIVE_SURFACE_CAIRO_SURFACE) {
+    return cairo_get_target(mContext);
+  }
+
+  return NULL;
+}
+
 void
-DrawTargetCairo::SetTransform(const Matrix& aTransform)
+DrawTargetCairo::MarkSnapshotsIndependent()
+{
+  // Make a copy of the vector, since MarkIndependent implicitly modifies mSnapshots.
+  std::vector<SourceSurfaceCairo*> snapshots = mSnapshots;
+  for (std::vector<SourceSurfaceCairo*>::iterator iter = snapshots.begin();
+       iter != snapshots.end();
+       ++iter) {
+    (*iter)->MarkIndependent();
+  }
+}
+
+void
+DrawTargetCairo::AppendSnapshot(SourceSurfaceCairo* aSnapshot)
 {
-  cairo_matrix_t mat;
-  GfxMatrixToCairoMatrix(aTransform, mat);
-  cairo_set_matrix(mContext, &mat);
-  mTransform = aTransform;
+  mSnapshots.push_back(aSnapshot);
+}
+
+void
+DrawTargetCairo::RemoveSnapshot(SourceSurfaceCairo* aSnapshot)
+{
+  std::vector<SourceSurfaceCairo*>::iterator iter = std::find(mSnapshots.begin(),
+                                                              mSnapshots.end(),
+                                                              aSnapshot);
+  if (iter != mSnapshots.end()) {
+    mSnapshots.erase(iter);
+  }
 }
 
+void
+DrawTargetCairo::MarkChanged()
+{
+  if (!mSnapshots.empty()) {
+    for (std::vector<SourceSurfaceCairo*>::iterator iter = mSnapshots.begin();
+         iter != mSnapshots.end(); ++iter) {
+      (*iter)->DrawTargetWillChange();
+    }
+    // All snapshots will now have copied data.
+    mSnapshots.clear();
+  }
+}
+
+
 }
 }
--- a/gfx/2d/DrawTargetCairo.h
+++ b/gfx/2d/DrawTargetCairo.h
@@ -35,115 +35,156 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _MOZILLA_GFX_DRAWTARGET_CAIRO_H_
 #define _MOZILLA_GFX_DRAWTARGET_CAIRO_H_
 
 #include "2D.h"
 #include "cairo.h"
 
+#include <vector>
+
 namespace mozilla {
 namespace gfx {
 
+class SourceSurfaceCairo;
+
+class GradientStopsCairo : public GradientStops
+{
+  public:
+    GradientStopsCairo(GradientStop* aStops, uint32_t aNumStops)
+    {
+      for (uint32_t i = 0; i < aNumStops; ++i) {
+        mStops.push_back(aStops[i]);
+      }
+    }
+
+    virtual ~GradientStopsCairo() {}
+
+    const std::vector<GradientStop>& GetStops() const
+    {
+      return mStops;
+    }
+
+    virtual BackendType GetBackendType() const { return BACKEND_CAIRO; }
+
+  private:
+    std::vector<GradientStop> mStops;
+};
+
 class DrawTargetCairo : public DrawTarget
 {
 public:
   DrawTargetCairo();
   virtual ~DrawTargetCairo();
 
   virtual BackendType GetType() const { return BACKEND_CAIRO; }
   virtual TemporaryRef<SourceSurface> Snapshot();
-  virtual IntSize GetSize() { return IntSize(); }
+  virtual IntSize GetSize();
 
   virtual void Flush();
   virtual void DrawSurface(SourceSurface *aSurface,
                            const Rect &aDest,
                            const Rect &aSource,
                            const DrawSurfaceOptions &aSurfOptions = DrawSurfaceOptions(),
                            const DrawOptions &aOptions = DrawOptions());
   virtual void DrawSurfaceWithShadow(SourceSurface *aSurface,
                                      const Point &aDest,
                                      const Color &aColor,
                                      const Point &aOffset,
                                      Float aSigma,
-                                     CompositionOp aOperator)
-  { }
+                                     CompositionOp aOperator);
 
-  virtual void ClearRect(const Rect &aRect)
-  { }
+  virtual void ClearRect(const Rect &aRect);
 
   virtual void CopySurface(SourceSurface *aSurface,
                            const IntRect &aSourceRect,
-                           const IntPoint &aDestination)
-  { }
+                           const IntPoint &aDestination);
 
   virtual void FillRect(const Rect &aRect,
                         const Pattern &aPattern,
                         const DrawOptions &aOptions = DrawOptions());
   virtual void StrokeRect(const Rect &aRect,
                           const Pattern &aPattern,
                           const StrokeOptions &aStrokeOptions = StrokeOptions(),
-                          const DrawOptions &aOptions = DrawOptions())
-  { return; }
+                          const DrawOptions &aOptions = DrawOptions());
   virtual void StrokeLine(const Point &aStart,
                           const Point &aEnd,
                           const Pattern &aPattern,
                           const StrokeOptions &aStrokeOptions = StrokeOptions(),
-                          const DrawOptions &aOptions = DrawOptions())
-  { return; }
+                          const DrawOptions &aOptions = DrawOptions());
 
   virtual void Stroke(const Path *aPath,
                       const Pattern &aPattern,
                       const StrokeOptions &aStrokeOptions = StrokeOptions(),
-                      const DrawOptions &aOptions = DrawOptions())
-  { return; }
+                      const DrawOptions &aOptions = DrawOptions());
 
   virtual void Fill(const Path *aPath,
                     const Pattern &aPattern,
-                    const DrawOptions &aOptions = DrawOptions())
-  { return; }
+                    const DrawOptions &aOptions = DrawOptions());
 
   virtual void FillGlyphs(ScaledFont *aFont,
                           const GlyphBuffer &aBuffer,
                           const Pattern &aPattern,
-                          const DrawOptions &aOptions)
-  { return; }
+                          const DrawOptions &aOptions);
   virtual void Mask(const Pattern &aSource,
                     const Pattern &aMask,
                     const DrawOptions &aOptions = DrawOptions())
-  { return; }
+  { }
 
-  virtual void PushClip(const Path *aPath) { }
-  virtual void PushClipRect(const Rect &aRect) { }
-  virtual void PopClip() { }
+  virtual void PushClip(const Path *aPath);
+  virtual void PushClipRect(const Rect &aRect)
+  { }
+  virtual void PopClip();
 
-  virtual TemporaryRef<PathBuilder> CreatePathBuilder(FillRule aFillRule = FILL_WINDING) const { return NULL; }
+  virtual TemporaryRef<PathBuilder> CreatePathBuilder(FillRule aFillRule = FILL_WINDING) const;
 
   virtual TemporaryRef<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
                                                             const IntSize &aSize,
                                                             int32_t aStride,
                                                             SurfaceFormat aFormat) const;
   virtual TemporaryRef<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const;
   virtual TemporaryRef<SourceSurface>
     CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const;
   virtual TemporaryRef<DrawTarget>
-    CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
-  { return NULL; }
+    CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const;
 
-  virtual TemporaryRef<GradientStops> CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, ExtendMode aExtendMode = EXTEND_CLAMP) const
-  { return NULL; }
+  virtual TemporaryRef<GradientStops>
+    CreateGradientStops(GradientStop *aStops,
+                        uint32_t aNumStops,
+                        ExtendMode aExtendMode = EXTEND_CLAMP) const;
 
-  virtual void *GetNativeSurface(NativeSurfaceType aType)
-  { return NULL; }
-
-  virtual void SetTransform(const Matrix& aTransform);
+  virtual void *GetNativeSurface(NativeSurfaceType aType);
 
   bool Init(cairo_surface_t* aSurface);
 
-private:
+private: // methods
+  void PrepareForDrawing(cairo_t* aContext);
+
+  enum DrawPatternType { DRAW_FILL, DRAW_STROKE };
+  void DrawPattern(const Rect& aRect,
+                   const Pattern& aPattern,
+                   const StrokeOptions& aStrokeOptions,
+                   const DrawOptions& aOptions,
+                   DrawPatternType aDrawType);
 
+  // Copy-on-write support for snapshot surfaces.
+  friend class SourceSurfaceCairo;
+  void AppendSnapshot(SourceSurfaceCairo* aSnapshot);
+  void RemoveSnapshot(SourceSurfaceCairo* aSnapshot);
+
+  // Call before you make any changes to the backing surface with which this
+  // context is associated.
+  void MarkChanged();
+
+  // Call if there is any reason to disassociate all snapshots from this draw
+  // target; for example, because we're going to be destroyed.
+  void MarkSnapshotsIndependent();
+
+private: // data
   cairo_t* mContext;
+  std::vector<SourceSurfaceCairo*> mSnapshots;
 };
 
 }
 }
 
 #endif // _MOZILLA_GFX_DRAWTARGET_CAIRO_H_
--- a/gfx/2d/SourceSurfaceCairo.cpp
+++ b/gfx/2d/SourceSurfaceCairo.cpp
@@ -31,28 +31,86 @@
  * 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 "SourceSurfaceCairo.h"
+#include "DrawTargetCairo.h"
 
 #include "cairo.h"
 
 namespace mozilla {
 namespace gfx {
 
-SourceSurfaceCairo::SourceSurfaceCairo()
+static cairo_format_t
+GfxFormatToCairoFormat(SurfaceFormat format)
+{
+  switch (format)
+  {
+    case FORMAT_B8G8R8A8:
+      return CAIRO_FORMAT_ARGB32;
+    case FORMAT_B8G8R8X8:
+      return CAIRO_FORMAT_RGB24;
+    case FORMAT_A8:
+      return CAIRO_FORMAT_A8;
+  }
+
+  return CAIRO_FORMAT_ARGB32;
+}
+
+static SurfaceFormat
+CairoFormatToSurfaceFormat(cairo_format_t format)
 {
+  switch (format)
+  {
+    case CAIRO_FORMAT_ARGB32:
+      return FORMAT_B8G8R8A8;
+    case CAIRO_FORMAT_RGB24:
+      return FORMAT_B8G8R8X8;
+    case CAIRO_FORMAT_A8:
+      return FORMAT_A8;
+  }
+
+  return FORMAT_B8G8R8A8;
+}
+
+static cairo_content_t
+GfxFormatToCairoContent(SurfaceFormat format)
+{
+  switch(format)
+  {
+    case FORMAT_B8G8R8A8:
+      return CAIRO_CONTENT_COLOR_ALPHA;
+    case FORMAT_B8G8R8X8:
+      return CAIRO_CONTENT_COLOR;
+    case FORMAT_A8:
+      return CAIRO_CONTENT_ALPHA;
+  }
+
+  return CAIRO_CONTENT_COLOR_ALPHA;
+}
+
+SourceSurfaceCairo::SourceSurfaceCairo(cairo_surface_t* aSurface,
+                                       const IntSize& aSize,
+                                       const SurfaceFormat& aFormat,
+                                       DrawTargetCairo* aDrawTarget /* = NULL */)
+ : mSize(aSize)
+ , mFormat(aFormat)
+ , mSurface(aSurface)
+ , mDrawTarget(aDrawTarget)
+{
+  cairo_surface_reference(mSurface);
 }
 
 SourceSurfaceCairo::~SourceSurfaceCairo()
 {
+  MarkIndependent();
   cairo_surface_destroy(mSurface);
 }
 
 IntSize
 SourceSurfaceCairo::GetSize() const
 {
   return mSize;
 }
@@ -61,32 +119,114 @@ SurfaceFormat
 SourceSurfaceCairo::GetFormat() const
 {
   return mFormat;
 }
 
 TemporaryRef<DataSourceSurface>
 SourceSurfaceCairo::GetDataSurface()
 {
-  return NULL;
+  RefPtr<DataSourceSurfaceCairo> dataSurf;
+
+  if (cairo_surface_get_type(mSurface) == CAIRO_SURFACE_TYPE_IMAGE) {
+    dataSurf = new DataSourceSurfaceCairo(mSurface);
+  } else {
+    cairo_surface_t* imageSurf = cairo_image_surface_create(GfxFormatToCairoFormat(mFormat),
+                                                            mSize.width, mSize.height);
+
+    // Fill the new image surface with the contents of our surface.
+    cairo_t* ctx = cairo_create(imageSurf);
+    cairo_pattern_t* pat = cairo_pattern_create_for_surface(mSurface);
+    cairo_set_source(ctx, pat);
+    cairo_paint(ctx);
+    cairo_destroy(ctx);
+
+    dataSurf = new DataSourceSurfaceCairo(imageSurf);
+    cairo_surface_destroy(imageSurf);
+  }
+
+  return dataSurf;
 }
 
 cairo_surface_t*
-SourceSurfaceCairo::GetSurface()
+SourceSurfaceCairo::GetSurface() const
 {
   return mSurface;
 }
 
-bool
-SourceSurfaceCairo::InitFromSurface(cairo_surface_t* aSurface,
-                                    const IntSize& aSize,
-                                    const SurfaceFormat& aFormat)
+void
+SourceSurfaceCairo::DrawTargetWillChange()
+{
+  if (mDrawTarget) {
+    mDrawTarget = NULL;
+
+    // We're about to lose our version of the surface, so make a copy of it.
+    cairo_surface_t* surface = cairo_surface_create_similar(mSurface,
+                                                            GfxFormatToCairoContent(mFormat),
+                                                            mSize.width, mSize.height);
+    cairo_t* ctx = cairo_create(surface);
+    cairo_pattern_t* pat = cairo_pattern_create_for_surface(mSurface);
+    cairo_set_source(ctx, pat);
+    cairo_paint(ctx);
+    cairo_destroy(ctx);
+
+    // Swap in this new surface.
+    cairo_surface_destroy(mSurface);
+    mSurface = surface;
+  }
+}
+
+void
+SourceSurfaceCairo::MarkIndependent()
+{
+  if (mDrawTarget) {
+    mDrawTarget->RemoveSnapshot(this);
+    mDrawTarget = NULL;
+  }
+}
+
+DataSourceSurfaceCairo::DataSourceSurfaceCairo(cairo_surface_t* imageSurf)
+ : mImageSurface(imageSurf)
 {
-  mSurface = aSurface;
-  cairo_surface_reference(mSurface);
-  mSize = aSize;
-  mFormat = aFormat;
+  cairo_surface_reference(mImageSurface);
+}
+
+DataSourceSurfaceCairo::~DataSourceSurfaceCairo()
+{
+  cairo_surface_destroy(mImageSurface);
+}
+
+unsigned char *
+DataSourceSurfaceCairo::GetData()
+{
+  return cairo_image_surface_get_data(mImageSurface);
+}
+
+int32_t
+DataSourceSurfaceCairo::Stride()
+{
+  return cairo_image_surface_get_stride(mImageSurface);
+}
 
-  return true;
+IntSize
+DataSourceSurfaceCairo::GetSize() const
+{
+  IntSize size;
+  size.width = cairo_image_surface_get_width(mImageSurface);
+  size.height = cairo_image_surface_get_height(mImageSurface);
+
+  return size;
+}
+
+SurfaceFormat
+DataSourceSurfaceCairo::GetFormat() const
+{
+  return CairoFormatToSurfaceFormat(cairo_image_surface_get_format(mImageSurface));
+}
+
+cairo_surface_t*
+DataSourceSurfaceCairo::GetSurface() const
+{
+  return mImageSurface;
 }
 
 }
 }
--- a/gfx/2d/SourceSurfaceCairo.h
+++ b/gfx/2d/SourceSurfaceCairo.h
@@ -37,35 +37,64 @@
 #ifndef _MOZILLA_GFX_OP_SOURCESURFACE_CAIRO_H_
 #define _MOZILLA_GFX_OP_SOURCESURFACE_CAIRO_H
 
 #include "2D.h"
 
 namespace mozilla {
 namespace gfx {
 
+class DrawTargetCairo;
+
 class SourceSurfaceCairo : public SourceSurface
 {
 public:
-  SourceSurfaceCairo();
-  ~SourceSurfaceCairo();
+  // Create a SourceSurfaceCairo. The surface will not be copied, but simply
+  // referenced.
+  // If aDrawTarget is non-NULL, it is assumed that this is a snapshot source
+  // surface, and we'll call DrawTargetCairo::RemoveSnapshot(this) on it when
+  // we're destroyed.
+  SourceSurfaceCairo(cairo_surface_t* aSurface, const IntSize& aSize,
+                     const SurfaceFormat& aFormat,
+                     DrawTargetCairo* aDrawTarget = NULL);
+  virtual ~SourceSurfaceCairo();
 
   virtual SurfaceType GetType() const { return SURFACE_CAIRO; }
   virtual IntSize GetSize() const;
   virtual SurfaceFormat GetFormat() const;
   virtual TemporaryRef<DataSourceSurface> GetDataSurface();
 
-  cairo_surface_t* GetSurface();
+  cairo_surface_t* GetSurface() const;
 
-  bool InitFromSurface(cairo_surface_t* aSurface,
-                       const IntSize& aSize,
-                       const SurfaceFormat& aFormat);
+private: // methods
+  friend class DrawTargetCairo;
+  void DrawTargetWillChange();
+  void MarkIndependent();
 
-private:
+private: // data
   IntSize mSize;
   SurfaceFormat mFormat;
   cairo_surface_t* mSurface;
+  DrawTargetCairo* mDrawTarget;
+};
+
+class DataSourceSurfaceCairo : public DataSourceSurface
+{
+public:
+  DataSourceSurfaceCairo(cairo_surface_t* imageSurf);
+  virtual ~DataSourceSurfaceCairo();
+  virtual unsigned char *GetData();
+  virtual int32_t Stride();
+
+  virtual SurfaceType GetType() const { return SURFACE_CAIRO_IMAGE; }
+  virtual IntSize GetSize() const;
+  virtual SurfaceFormat GetFormat() const;
+
+  cairo_surface_t* GetSurface() const;
+
+private:
+  cairo_surface_t* mImageSurface;
 };
 
 }
 }
 
 #endif // _MOZILLA_GFX_OP_SOURCESURFACE_CAIRO_H
--- a/gfx/2d/Types.h
+++ b/gfx/2d/Types.h
@@ -79,17 +79,18 @@ enum FontType
   FONT_DWRITE,
   FONT_GDI,
   FONT_MAC,
   FONT_SKIA
 };
 
 enum NativeSurfaceType
 {
-  NATIVE_SURFACE_D3D10_TEXTURE
+  NATIVE_SURFACE_D3D10_TEXTURE,
+  NATIVE_SURFACE_CAIRO_SURFACE
 };
 
 enum NativeFontType
 {
   NATIVE_FONT_DWRITE_FONT_FACE,
   NATIVE_FONT_GDI_FONT_FACE,
   NATIVE_FONT_MAC_FONT_FACE,
   NATIVE_FONT_SKIA_FONT_FACE