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 84235 4b4d5cd00d43f83f097b2b7a0b7d1e407b5e4081
parent 84234 53a8a6b4c812103b51e17023410224ea14b0de40
child 84236 14930a83054b9cfda27d7e60f54680215fd8ac63
push id21832
push userbmo@edmorley.co.uk
push dateWed, 11 Jan 2012 17:04:15 +0000
treeherdermozilla-central@40c9f9ff9fd5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs715652
milestone12.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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