Bug 707848 - Implement paths in the 2D API's Cairo backend. r=jrmuizel
authorJoe Drew <joe@drew.ca>
Mon, 09 Jan 2012 17:15:10 -0500
changeset 85461 14930a83054b9cfda27d7e60f54680215fd8ac63
parent 85460 4b4d5cd00d43f83f097b2b7a0b7d1e407b5e4081
child 85462 7349c6b4ac7f856f12b606894872c526e8ffae28
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
bugs707848
milestone12.0a1
Bug 707848 - Implement paths in the 2D API's Cairo backend. r=jrmuizel
gfx/2d/DrawTargetCairo.cpp
gfx/2d/DrawTargetCairo.h
gfx/2d/HelpersCairo.h
gfx/2d/Makefile.in
gfx/2d/PathCairo.cpp
gfx/2d/PathCairo.h
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -32,16 +32,18 @@
  * 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 "PathCairo.h"
+#include "HelpersCairo.h"
 
 #include "cairo.h"
 
 #include "Blur.h"
 
 #ifdef CAIRO_HAS_QUARTZ_SURFACE
 #include "cairo-quartz.h"
 #include <ApplicationServices/ApplicationServices.h>
@@ -51,161 +53,44 @@
 #include "cairo-xlib.h"
 #endif
 
 #include <algorithm>
 
 namespace mozilla {
 namespace gfx {
 
-static cairo_operator_t
-GfxOpToCairoOp(CompositionOp op)
+namespace {
+
+// An RAII class to prepare to draw a context and optional path. Saves and
+// restores the context on construction/destruction.
+class AutoPrepareForDrawing
 {
-  switch (op)
+public:
+  AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx)
+    : mCtx(ctx)
   {
-    case OP_OVER:
-      return CAIRO_OPERATOR_OVER;
-    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;
-}
-
-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;
-}
-
-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;
+    dt->PrepareForDrawing(ctx);
+    cairo_save(mCtx);
   }
 
-  return CAIRO_EXTEND_PAD;
-}
-
-static cairo_format_t
-GfxFormatToCairoFormat(SurfaceFormat format)
-{
-  switch (format)
+  AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx, const Path* path)
+    : mCtx(ctx)
   {
-    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;
+    dt->PrepareForDrawing(ctx, path);
+    cairo_save(mCtx);
   }
 
-  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;
-}
+  ~AutoPrepareForDrawing() { cairo_restore(mCtx); }
 
-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;
-}
+private:
+  cairo_t* mCtx;
+};
 
-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;
-}
+} // end anonymous namespace
 
 static bool
 GetCairoSurfaceSize(cairo_surface_t* surface, IntSize& size)
 {
   switch (cairo_surface_get_type(surface))
   {
     case CAIRO_SURFACE_TYPE_IMAGE:
     {
@@ -236,23 +121,17 @@ GetCairoSurfaceSize(cairo_surface_t* sur
     }
 #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*
+static cairo_pattern_t*
 GfxPatternToCairoPattern(const Pattern& aPattern, Float aAlpha)
 {
   cairo_pattern_t* pat = NULL;
 
   switch (aPattern.GetType())
   {
     case PATTERN_COLOR:
     {
@@ -334,17 +213,17 @@ GfxPatternToCairoPattern(const Pattern& 
 
       break;
     }
   }
 
   return pat;
 }
 
-bool
+static 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)
@@ -356,16 +235,19 @@ NeedIntermediateSurface(const Pattern& a
 DrawTargetCairo::DrawTargetCairo()
   : mContext(NULL)
 {
 }
 
 DrawTargetCairo::~DrawTargetCairo()
 {
   MarkSnapshotsIndependent();
+  if (mPathObserver) {
+    mPathObserver->ForgetDrawTarget();
+  }
   cairo_destroy(mContext);
 }
 
 IntSize
 DrawTargetCairo::GetSize()
 {
   return IntSize();
 }
@@ -390,33 +272,29 @@ DrawTargetCairo::Snapshot()
 void
 DrawTargetCairo::Flush()
 {
   cairo_surface_t* surf = cairo_get_target(mContext);
   cairo_surface_flush(surf);
 }
 
 void
-DrawTargetCairo::PrepareForDrawing(cairo_t* aContext)
+DrawTargetCairo::PrepareForDrawing(cairo_t* aContext, const Path* aPath /* = NULL */)
 {
-  MarkChanged();
-
-  cairo_matrix_t mat;
-  GfxMatrixToCairoMatrix(mTransform, mat);
-  cairo_set_matrix(aContext, &mat);
+  WillChange(aPath);
 }
 
 void
 DrawTargetCairo::DrawSurface(SourceSurface *aSurface,
                              const Rect &aDest,
                              const Rect &aSource,
                              const DrawSurfaceOptions &aSurfOptions,
                              const DrawOptions &aOptions)
 {
-  PrepareForDrawing(mContext);
+  AutoPrepareForDrawing prep(this, 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());
 
@@ -450,17 +328,17 @@ DrawTargetCairo::DrawSurface(SourceSurfa
 void
 DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface *aSurface,
                                        const Point &aDest,
                                        const Color &aColor,
                                        const Point &aOffset,
                                        Float aSigma,
                                        CompositionOp aOperator)
 {
-  MarkChanged();
+  WillChange();
 
   if (aSurface->GetType() != SURFACE_CAIRO) {
     return;
   }
 
   SourceSurfaceCairo* sourcesurf = static_cast<SourceSurfaceCairo*>(aSurface);
 
   Float width = aSurface->GetSize().width,
@@ -525,113 +403,81 @@ DrawTargetCairo::DrawSurfaceWithShadow(S
   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,
+DrawTargetCairo::DrawPattern(const Pattern& aPattern,
                              const StrokeOptions& aStrokeOptions,
                              const DrawOptions& aOptions,
                              DrawPatternType aDrawType)
 {
-  cairo_save(mContext);
+  cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha);
+  cairo_set_source(mContext, pat);
 
-  cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
-
-  bool needIntermediate = NeedIntermediateSurface(aPattern, aOptions);
-  if (needIntermediate) {
+  if (NeedIntermediateSurface(aPattern, aOptions)) {
     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());
+    cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
 
     if (aDrawType == DRAW_STROKE) {
-      SetStrokeOptions(mContext, aStrokeOptions);
-      cairo_stroke(mContext);
+      SetCairoStrokeOptions(mContext, aStrokeOptions);
+      cairo_stroke_preserve(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_fill_preserve(mContext);
     }
 
-    cairo_pattern_destroy(pat);
+    cairo_pop_group_to_source(mContext);
+
+    // Now draw the content using the desired operator
+    cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
+    cairo_paint_with_alpha(mContext, aOptions.mAlpha);
+  } else {
+    cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
+
+    if (aDrawType == DRAW_STROKE) {
+      SetCairoStrokeOptions(mContext, aStrokeOptions);
+      cairo_stroke_preserve(mContext);
+    } else {
+      cairo_fill_preserve(mContext);
+    }
   }
 
-  if (needIntermediate) {
-    cairo_pop_group_to_source(mContext);
-
-    cairo_paint_with_alpha(mContext, aOptions.mAlpha);
-  }
-
-  cairo_restore(mContext);
+  cairo_pattern_destroy(pat);
 }
 
 void
 DrawTargetCairo::FillRect(const Rect &aRect,
                           const Pattern &aPattern,
                           const DrawOptions &aOptions)
 {
-  PrepareForDrawing(mContext);
+  AutoPrepareForDrawing prep(this, mContext);
 
-  DrawPattern(aRect, aPattern, StrokeOptions(), aOptions, DRAW_FILL);
+  cairo_new_path(mContext);
+  cairo_rectangle(mContext, aRect.x, aRect.y, aRect.Width(), aRect.Height());
+
+  DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL);
 }
 
 void
 DrawTargetCairo::CopySurface(SourceSurface *aSurface,
                              const IntRect &aSourceRect,
                              const IntPoint &aDestination)
 {
-  PrepareForDrawing(mContext);
+  AutoPrepareForDrawing prep(this, mContext);
 }
 
 void
 DrawTargetCairo::ClearRect(const Rect& aRect)
 {
-  PrepareForDrawing(mContext);
+  AutoPrepareForDrawing prep(this, mContext);
 
   cairo_save(mContext);
 
   cairo_new_path(mContext);
   cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR);
   cairo_rectangle(mContext, aRect.X(), aRect.Y(),
                   aRect.Width(), aRect.Height());
   cairo_fill(mContext);
@@ -640,91 +486,118 @@ DrawTargetCairo::ClearRect(const Rect& a
 }
 
 void
 DrawTargetCairo::StrokeRect(const Rect &aRect,
                             const Pattern &aPattern,
                             const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
                             const DrawOptions &aOptions /* = DrawOptions() */)
 {
-  PrepareForDrawing(mContext);
+  AutoPrepareForDrawing prep(this, mContext);
 
-  DrawPattern(aRect, aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
+  cairo_new_path(mContext);
+  cairo_rectangle(mContext, aRect.x, aRect.y, aRect.Width(), aRect.Height());
+
+  DrawPattern(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);
+  AutoPrepareForDrawing prep(this, mContext);
 
-    cairo_new_path(mContext);
-    cairo_move_to(mContext, aStart.x, aStart.y);
-    cairo_line_to(mContext, aEnd.x, aEnd.y);
+  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_restore(mContext);
+  DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
 }
 
 void
 DrawTargetCairo::Stroke(const Path *aPath,
                         const Pattern &aPattern,
                         const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
                         const DrawOptions &aOptions /* = DrawOptions() */)
 {
-  PrepareForDrawing(mContext);
+  AutoPrepareForDrawing prep(this, mContext, aPath);
+
+  if (aPath->GetBackendType() != BACKEND_CAIRO)
+    return;
+
+  PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
+  path->CopyPathTo(mContext, this);
+
+  DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
 }
 
 void
 DrawTargetCairo::Fill(const Path *aPath,
                       const Pattern &aPattern,
                       const DrawOptions &aOptions /* = DrawOptions() */)
 {
-  PrepareForDrawing(mContext);
+  AutoPrepareForDrawing prep(this, mContext, aPath);
+
+  if (aPath->GetBackendType() != BACKEND_CAIRO)
+    return;
+
+  PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
+  path->CopyPathTo(mContext, this);
+
+  DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL);
 }
 
 void
 DrawTargetCairo::FillGlyphs(ScaledFont *aFont,
                             const GlyphBuffer &aBuffer,
                             const Pattern &aPattern,
                             const DrawOptions &aOptions)
 {
-  PrepareForDrawing(mContext);
+  AutoPrepareForDrawing prep(this, mContext);
+}
+
+void
+DrawTargetCairo::Mask(const Pattern &aSource,
+                      const Pattern &aMask,
+                      const DrawOptions &aOptions /* = DrawOptions() */)
+{
+  AutoPrepareForDrawing prep(this, mContext);
 }
 
 void
 DrawTargetCairo::PushClip(const Path *aPath)
 {
 }
 
 void
+DrawTargetCairo::PushClipRect(const Rect& aRect)
+{
+}
+
+void
 DrawTargetCairo::PopClip()
 {
 }
 
 TemporaryRef<PathBuilder>
 DrawTargetCairo::CreatePathBuilder(FillRule aFillRule /* = FILL_WINDING */) const
 {
-  return NULL;
+  RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(mContext,
+                                                          const_cast<DrawTargetCairo*>(this),
+                                                          aFillRule);
+
+  // Creating a PathBuilder implicitly resets our mPathObserver, as it calls
+  // SetPathObserver() on us. Since this guarantees our old path is saved off,
+  // it's safe to reset the path here.
+  cairo_new_path(mContext);
+
+  return builder;
 }
 
 TemporaryRef<GradientStops>
 DrawTargetCairo::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, ExtendMode aExtendMode) const
 {
   RefPtr<GradientStopsCairo> stops = new GradientStopsCairo(aStops, aNumStops);
   return stops;
 }
@@ -826,23 +699,52 @@ DrawTargetCairo::RemoveSnapshot(SourceSu
                                                               mSnapshots.end(),
                                                               aSnapshot);
   if (iter != mSnapshots.end()) {
     mSnapshots.erase(iter);
   }
 }
 
 void
-DrawTargetCairo::MarkChanged()
+DrawTargetCairo::WillChange(const Path* aPath /* = NULL */)
 {
   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();
   }
+
+  if (aPath && mPathObserver && !mPathObserver->ContainsPath(aPath)) {
+    mPathObserver->PathWillChange();
+    mPathObserver = NULL;
+  }
+}
+
+void
+DrawTargetCairo::SetPathObserver(CairoPathContext* aPathObserver)
+{
+  if (mPathObserver && mPathObserver != aPathObserver) {
+    mPathObserver->PathWillChange();
+  }
+  mPathObserver = aPathObserver;
 }
 
+void
+DrawTargetCairo::SetTransform(const Matrix& aTransform)
+{
+  // We're about to logically change our transformation. Our current path will
+  // need to change, because Cairo stores paths in device space.
+  if (mPathObserver) {
+    mPathObserver->MatrixWillChange(aTransform);
+  }
+
+  mTransform = aTransform;
+
+  cairo_matrix_t mat;
+  GfxMatrixToCairoMatrix(mTransform, mat);
+  cairo_set_matrix(mContext, &mat);
+}
 
 }
 }
--- a/gfx/2d/DrawTargetCairo.h
+++ b/gfx/2d/DrawTargetCairo.h
@@ -34,16 +34,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _MOZILLA_GFX_DRAWTARGET_CAIRO_H_
 #define _MOZILLA_GFX_DRAWTARGET_CAIRO_H_
 
 #include "2D.h"
 #include "cairo.h"
+#include "PathCairo.h"
 
 #include <vector>
 
 namespace mozilla {
 namespace gfx {
 
 class SourceSurfaceCairo;
 
@@ -122,22 +123,20 @@ public:
                     const DrawOptions &aOptions = DrawOptions());
 
   virtual void FillGlyphs(ScaledFont *aFont,
                           const GlyphBuffer &aBuffer,
                           const Pattern &aPattern,
                           const DrawOptions &aOptions);
   virtual void Mask(const Pattern &aSource,
                     const Pattern &aMask,
-                    const DrawOptions &aOptions = DrawOptions())
-  { }
+                    const DrawOptions &aOptions = DrawOptions());
 
   virtual void PushClip(const Path *aPath);
-  virtual void PushClipRect(const Rect &aRect)
-  { }
+  virtual void PushClipRect(const Rect &aRect);
   virtual void PopClip();
 
   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;
@@ -151,40 +150,48 @@ public:
     CreateGradientStops(GradientStop *aStops,
                         uint32_t aNumStops,
                         ExtendMode aExtendMode = EXTEND_CLAMP) const;
 
   virtual void *GetNativeSurface(NativeSurfaceType aType);
 
   bool Init(cairo_surface_t* aSurface);
 
-private: // methods
-  void PrepareForDrawing(cairo_t* aContext);
+  void SetPathObserver(CairoPathContext* aPathObserver);
+
+  virtual void SetTransform(const Matrix& aTransform);
 
+  // Call to set up aContext for drawing (with the current transform, etc).
+  // Pass the path you're going to be using if you have one.
+  // Implicitly calls WillChange(aPath).
+  void PrepareForDrawing(cairo_t* aContext, const Path* aPath = NULL);
+
+private: // methods
   enum DrawPatternType { DRAW_FILL, DRAW_STROKE };
-  void DrawPattern(const Rect& aRect,
-                   const Pattern& aPattern,
+  void DrawPattern(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();
+  // context is associated. Pass the path you're going to be using if you have
+  // one.
+  void WillChange(const Path* aPath = NULL);
 
   // 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;
+  mutable RefPtr<CairoPathContext> mPathObserver;
 };
 
 }
 }
 
 #endif // _MOZILLA_GFX_DRAWTARGET_CAIRO_H_
new file mode 100644
--- /dev/null
+++ b/gfx/2d/HelpersCairo.h
@@ -0,0 +1,237 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Corporation code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef MOZILLA_GFX_HELPERSCAIRO_H_
+#define MOZILLA_GFX_HELPERSCAIRO_H_
+
+#include "2D.h"
+#include "cairo.h"
+
+namespace mozilla {
+namespace gfx {
+
+static inline cairo_operator_t
+GfxOpToCairoOp(CompositionOp op)
+{
+  switch (op)
+  {
+    case OP_OVER:
+      return CAIRO_OPERATOR_OVER;
+    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;
+}
+
+static inline 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;
+}
+
+static inline 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 inline 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 inline 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 inline 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 inline 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 inline 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 inline 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);
+}
+
+static inline void
+SetCairoStrokeOptions(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));
+}
+
+static inline cairo_fill_rule_t
+GfxFillRuleToCairoFillRule(FillRule rule)
+{
+  switch (rule)
+  {
+    case FILL_WINDING:
+      return CAIRO_FILL_RULE_WINDING;
+    case FILL_EVEN_ODD:
+      return CAIRO_FILL_RULE_EVEN_ODD;
+  }
+
+  return CAIRO_FILL_RULE_WINDING;
+}
+
+}
+}
+
+#endif /* MOZILLA_GFX_HELPERSCAIRO_H_ */
--- a/gfx/2d/Makefile.in
+++ b/gfx/2d/Makefile.in
@@ -64,16 +64,17 @@ EXPORTS_mozilla/gfx	= \
         Types.h \
 	$(NULL)
 
 CPPSRCS	= \
 	Factory.cpp \
         Matrix.cpp \
         DrawTargetCairo.cpp \
         SourceSurfaceCairo.cpp \
+        PathCairo.cpp \
         Blur.cpp \
         $(NULL)
 
 
 DEFINES += -DMOZ_GFX -DUSE_CAIRO
 
 ifdef MOZ_ENABLE_SKIA
 CPPSRCS	+= \
new file mode 100644
--- /dev/null
+++ b/gfx/2d/PathCairo.cpp
@@ -0,0 +1,363 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Corporation code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "PathCairo.h"
+#include <math.h>
+#include "DrawTargetCairo.h"
+#include "Logging.h"
+#include "PathHelpers.h"
+#include "HelpersCairo.h"
+
+namespace mozilla {
+namespace gfx {
+
+CairoPathContext::CairoPathContext(cairo_t* aCtx, DrawTargetCairo* aDrawTarget,
+                                   FillRule aFillRule,
+                                   const Matrix& aTransform /* = Matrix() */)
+ : mTransform(aTransform)
+ , mContext(aCtx)
+ , mDrawTarget(aDrawTarget)
+ , mFillRule(aFillRule)
+{
+  cairo_reference(mContext);
+  cairo_set_fill_rule(mContext, GfxFillRuleToCairoFillRule(mFillRule));
+
+  // If we don't have an identity transformation, we need to have a separate
+  // context from the draw target, because we can't set a transformation on its
+  // context.
+  if (mDrawTarget && !mTransform.IsIdentity()) {
+    DuplicateContextAndPath(mTransform);
+
+    ForgetDrawTarget();
+  } else if (mDrawTarget) {
+    mDrawTarget->SetPathObserver(this);
+  }
+}
+
+CairoPathContext::~CairoPathContext()
+{
+  if (mDrawTarget) {
+    mDrawTarget->SetPathObserver(NULL);
+  }
+  cairo_destroy(mContext);
+}
+
+void
+CairoPathContext::DuplicateContextAndPath(const Matrix& aMatrix /* = Matrix() */)
+{
+  // Duplicate the path.
+  cairo_path_t* path = cairo_copy_path(mContext);
+  cairo_fill_rule_t rule = cairo_get_fill_rule(mContext);
+
+  // Duplicate the context.
+  cairo_surface_t* surf = cairo_get_target(mContext);
+  cairo_destroy(mContext);
+  mContext = cairo_create(surf);
+
+  // Transform the context.
+  cairo_matrix_t matrix;
+  GfxMatrixToCairoMatrix(aMatrix, matrix);
+  cairo_transform(mContext, &matrix);
+
+  // Add the path, and throw away our duplicate.
+  cairo_append_path(mContext, path);
+  cairo_set_fill_rule(mContext, rule);
+  cairo_path_destroy(path);
+}
+
+void
+CairoPathContext::PathWillChange()
+{
+  // Once we've copied out the context's path, there's no use to holding on to
+  // the draw target. Thus, there's nothing for us to do if we're independent
+  // of the draw target, since we'll have already copied out the context's
+  // path.
+  if (mDrawTarget) {
+    // The context we point to is going to change from under us. To continue
+    // using this path, we need to copy it to a new context.
+    DuplicateContextAndPath();
+
+    ForgetDrawTarget();
+  }
+}
+
+void
+CairoPathContext::MatrixWillChange(const Matrix& aNewMatrix)
+{
+  // Cairo paths are stored in device space. Since we logically operate in user
+  // space, we want to make it so our path will be in the same location if and
+  // when our path is copied out.
+  // To effect this, we copy out our path (which, in Cairo, implicitly converts
+  // to user space), then temporarily set the context to have the new
+  // transform. We then set the path, which ensures that the points are all
+  // transformed correctly. Finally, we set the matrix back to its original
+  // value.
+  cairo_path_t* path = cairo_copy_path(mContext);
+
+  cairo_matrix_t origMatrix;
+  cairo_get_matrix(mContext, &origMatrix);
+
+  cairo_matrix_t newMatrix;
+  GfxMatrixToCairoMatrix(aNewMatrix, newMatrix);
+  cairo_set_matrix(mContext, &newMatrix);
+
+  cairo_new_path(mContext);
+  cairo_append_path(mContext, path);
+  cairo_path_destroy(path);
+
+  cairo_set_matrix(mContext, &origMatrix);
+}
+
+void
+CairoPathContext::CopyPathTo(cairo_t* aToContext)
+{
+  if (aToContext != mContext) {
+    cairo_set_fill_rule(aToContext, GfxFillRuleToCairoFillRule(mFillRule));
+
+    cairo_matrix_t origMat;
+    cairo_get_matrix(aToContext, &origMat);
+
+    cairo_matrix_t mat;
+    GfxMatrixToCairoMatrix(mTransform, mat);
+    cairo_transform(aToContext, &mat);
+
+    // cairo_copy_path gives us a user-space copy of the path, so we don't have
+    // to worry about transformations here.
+    cairo_path_t* path = cairo_copy_path(mContext);
+    cairo_new_path(aToContext);
+    cairo_append_path(aToContext, path);
+    cairo_path_destroy(path);
+
+    cairo_set_matrix(aToContext, &origMat);
+  }
+}
+
+void
+CairoPathContext::ForgetDrawTarget()
+{
+  mDrawTarget = NULL;
+}
+
+bool
+CairoPathContext::ContainsPath(const Path* aPath)
+{
+  if (aPath->GetBackendType() != BACKEND_CAIRO) {
+    return false;
+  }
+
+  const PathCairo* path = static_cast<const PathCairo*>(aPath);
+  RefPtr<CairoPathContext> ctx = const_cast<PathCairo*>(path)->GetPathContext();
+  return ctx == this;
+}
+
+PathBuilderCairo::PathBuilderCairo(CairoPathContext* aPathContext,
+                                   const Matrix& aTransform /* = Matrix() */)
+ : mFillRule(aPathContext->GetFillRule())
+{
+  RefPtr<DrawTargetCairo> drawTarget = aPathContext->GetDrawTarget();
+  mPathContext = new CairoPathContext(*aPathContext, drawTarget, mFillRule,
+                                      aPathContext->GetTransform() * aTransform);
+
+  // We need to ensure that we are allowed to modify the path currently set on
+  // aPathContext. If we don't have a draw target, CairoPathContext's
+  // constructor has no way to make aPathContext duplicate its path (normally,
+  // calling drawTarget->SetPathObserver() would do so). In this case, we
+  // explicitly make aPathContext copy out its context and path, leaving our
+  // path alone.
+  if (!drawTarget) {
+    aPathContext->DuplicateContextAndPath();
+  }
+}
+
+PathBuilderCairo::PathBuilderCairo(cairo_t* aCtx, DrawTargetCairo* aDrawTarget, FillRule aFillRule)
+ : mPathContext(new CairoPathContext(aCtx, aDrawTarget, aFillRule))
+ , mFillRule(aFillRule)
+{}
+
+void
+PathBuilderCairo::MoveTo(const Point &aPoint)
+{
+  cairo_move_to(*mPathContext, aPoint.x, aPoint.y);
+}
+
+void
+PathBuilderCairo::LineTo(const Point &aPoint)
+{
+  cairo_line_to(*mPathContext, aPoint.x, aPoint.y);
+}
+
+void
+PathBuilderCairo::BezierTo(const Point &aCP1,
+                           const Point &aCP2,
+                           const Point &aCP3)
+{
+  cairo_curve_to(*mPathContext, aCP1.x, aCP1.y, aCP2.x, aCP2.y, aCP3.x, aCP3.y);
+}
+
+void
+PathBuilderCairo::QuadraticBezierTo(const Point &aCP1,
+                                    const Point &aCP2)
+{
+  // We need to elevate the degree of this quadratic B├ęzier to cubic, so we're
+  // going to add an intermediate control point, and recompute control point 1.
+  // The first and last control points remain the same.
+  // This formula can be found on http://fontforge.sourceforge.net/bezier.html
+  Point CP0 = CurrentPoint();
+  Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
+  Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
+  Point CP3 = aCP2;
+
+  cairo_curve_to(*mPathContext, CP1.x, CP1.y, CP2.x, CP2.y, CP3.x, CP3.y);
+}
+
+void
+PathBuilderCairo::Close()
+{
+  cairo_close_path(*mPathContext);
+}
+
+void
+PathBuilderCairo::Arc(const Point &aOrigin, float aRadius, float aStartAngle,
+                     float aEndAngle, bool aAntiClockwise)
+{
+  ArcToBezier(this, aOrigin, aRadius, aStartAngle, aEndAngle, aAntiClockwise);
+}
+
+Point
+PathBuilderCairo::CurrentPoint() const
+{
+  double x, y;
+  cairo_get_current_point(*mPathContext, &x, &y);
+  return Point(x, y);
+}
+
+TemporaryRef<Path>
+PathBuilderCairo::Finish()
+{
+  RefPtr<PathCairo> path = new PathCairo(*mPathContext,
+                                         mPathContext->GetDrawTarget(),
+                                         mFillRule,
+                                         mPathContext->GetTransform());
+  return path;
+}
+
+TemporaryRef<CairoPathContext>
+PathBuilderCairo::GetPathContext()
+{
+  return mPathContext;
+}
+
+PathCairo::PathCairo(cairo_t* aCtx, DrawTargetCairo* aDrawTarget, FillRule aFillRule, const Matrix& aTransform)
+ : mPathContext(new CairoPathContext(aCtx, aDrawTarget, aFillRule, aTransform))
+ , mFillRule(aFillRule)
+{}
+
+TemporaryRef<PathBuilder>
+PathCairo::CopyToBuilder(FillRule aFillRule) const
+{
+  // Note: This PathBuilderCairo constructor causes our mPathContext to copy
+  // out the path, since the path builder is going to change the path on us.
+  RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(mPathContext);
+  return builder;
+}
+
+TemporaryRef<PathBuilder>
+PathCairo::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const
+{
+  // Note: This PathBuilderCairo constructor causes our mPathContext to copy
+  // out the path, since the path builder is going to change the path on us.
+  RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(mPathContext,
+                                                          aTransform);
+  return builder;
+}
+
+bool
+PathCairo::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const
+{
+  Matrix inverse = aTransform;
+  inverse.Invert();
+  Point transformed = inverse * aPoint;
+
+  return cairo_in_fill(*mPathContext, transformed.x, transformed.y);
+}
+
+Rect
+PathCairo::GetBounds(const Matrix &aTransform) const
+{
+  double x1, y1, x2, y2;
+
+  cairo_path_extents(*mPathContext, &x1, &y1, &x2, &y2);
+  Rect bounds(x1, y1, x2 - x1, y2 - y1);
+  return aTransform.TransformBounds(bounds);
+}
+
+Rect
+PathCairo::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
+                            const Matrix &aTransform) const
+{
+  double x1, y1, x2, y2;
+
+  SetCairoStrokeOptions(*mPathContext, aStrokeOptions);
+
+  cairo_stroke_extents(*mPathContext, &x1, &y1, &x2, &y2);
+  Rect bounds(x1, y1, x2 - x1, y2 - y1);
+  return aTransform.TransformBounds(bounds);
+}
+
+TemporaryRef<CairoPathContext>
+PathCairo::GetPathContext()
+{
+  return mPathContext;
+}
+
+void
+PathCairo::CopyPathTo(cairo_t* aContext, DrawTargetCairo* aDrawTarget)
+{
+  if (mPathContext->GetContext() != aContext) {
+    mPathContext->CopyPathTo(aContext);
+
+    // Since aDrawTarget wants us to be the current path on its context, we
+    // should also listen to it for updates to that path (as an optimization).
+    // The easiest way to do this is to just recreate mPathContext, since it
+    // registers with aDrawTarget for updates.
+    mPathContext = new CairoPathContext(aContext, aDrawTarget,
+                                        mPathContext->GetFillRule(),
+                                        mPathContext->GetTransform());
+  }
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/gfx/2d/PathCairo.h
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Corporation code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef MOZILLA_GFX_PATH_CAIRO_H_
+#define MOZILLA_GFX_PATH_CAIRO_H_
+
+#include "2D.h"
+#include "cairo.h"
+
+namespace mozilla {
+namespace gfx {
+
+class DrawTargetCairo;
+
+// A reference to a cairo context that can maintain and set a path.
+//
+// This class exists to make it possible for us to not construct paths manually
+// using cairo_path_t, which in the common case is a speed and memory
+// optimization (as the cairo_t maintains the path for us, and we don't have to
+// use cairo_append_path). Instead, we can share a cairo_t with a DrawTarget,
+// and have it inform us when we need to make a copy of the path.
+//
+// Exactly one Path* object represents the current path on a given DrawTarget's
+// context. That Path* object registers its CairoPathContext with the
+// DrawTarget it's associated with. If that DrawTarget is going to change its
+// path, it has to tell the CairoPathContext beforehand so the path can be
+// saved off.
+// The path ownership is transferred to every new instance of CairoPathContext
+// in the constructor. We inform the draw target of the new context object,
+// which causes us to save off a copy of the path, as we're not going to be
+// informed upon changes any more.
+// Any transformation on aCtx is not applied to this path, though a path can be
+// transformed separately from its context by passing a matrix to the
+// constructor.
+class CairoPathContext : public RefCounted<CairoPathContext>
+{
+public:
+  // Construct a CairoPathContext and set it to be the path observer of
+  // aDrawTarget. Optionally, this path can be transformed by aMatrix.
+  CairoPathContext(cairo_t* aCtx, DrawTargetCairo* aDrawTarget,
+                   FillRule aFillRule,
+                   const Matrix& aMatrix = Matrix());
+  ~CairoPathContext();
+
+  // Copy the path on mContext to be the path on aToContext, if they aren't the
+  // same.
+  void CopyPathTo(cairo_t* aToContext);
+
+  // This method must be called by the draw target before it changes the path
+  // currently on the cairo context.
+  void PathWillChange();
+
+  // This method must be called by the draw target whenever it is going to
+  // change the current transformation on mContext.
+  void MatrixWillChange(const Matrix& aMatrix);
+
+  // This method must be called as the draw target is dying. In this case, we
+  // forget our reference to the draw target, and become the only reference to
+  // our context.
+  void ForgetDrawTarget();
+
+  // Create a duplicate context, and copy this path to that context. Optionally,
+  // the new context can be transformed.
+  void DuplicateContextAndPath(const Matrix& aMatrix = Matrix());
+
+  // Returns true if this CairoPathContext represents path.
+  bool ContainsPath(const Path* path);
+
+  cairo_t* GetContext() const { return mContext; }
+  DrawTargetCairo* GetDrawTarget() const { return mDrawTarget; }
+  Matrix GetTransform() const { return mTransform; }
+  FillRule GetFillRule() const { return mFillRule; }
+
+  operator cairo_t* () const { return mContext; }
+
+private: // methods
+  CairoPathContext(const CairoPathContext&) MOZ_DELETE;
+
+private: // data
+  Matrix mTransform;
+  cairo_t* mContext;
+  // Not a RefPtr to avoid cycles.
+  DrawTargetCairo* mDrawTarget;
+  FillRule mFillRule;
+};
+
+class PathBuilderCairo : public PathBuilder
+{
+public:
+  // This constructor implicitly takes ownership of aCtx by calling
+  // aDrawTarget->SetPathObserver(). Therefore, if the draw target has a path
+  // observer, this constructor will cause it to copy out its path.
+  // The path currently set on aCtx is not changed.
+  PathBuilderCairo(cairo_t* aCtx, DrawTargetCairo* aDrawTarget, FillRule aFillRule);
+
+  // This constructor, called with a CairoPathContext*, implicitly takes
+  // ownership of the path, and therefore makes aPathContext copy out its path
+  // regardless of whether it has a pointer to a DrawTargetCairo.
+  // The path currently set on aPathContext is not changed.
+  explicit PathBuilderCairo(CairoPathContext* aPathContext,
+                            const Matrix& aTransform = Matrix());
+
+  virtual void MoveTo(const Point &aPoint);
+  virtual void LineTo(const Point &aPoint);
+  virtual void BezierTo(const Point &aCP1,
+                        const Point &aCP2,
+                        const Point &aCP3);
+  virtual void QuadraticBezierTo(const Point &aCP1,
+                                 const Point &aCP2);
+  virtual void Close();
+  virtual void Arc(const Point &aOrigin, float aRadius, float aStartAngle,
+                   float aEndAngle, bool aAntiClockwise = false);
+  virtual Point CurrentPoint() const;
+  virtual TemporaryRef<Path> Finish();
+
+  TemporaryRef<CairoPathContext> GetPathContext();
+
+private: // methods
+  void SetFillRule(FillRule aFillRule);
+
+private: // data
+  RefPtr<CairoPathContext> mPathContext;
+  FillRule mFillRule;
+};
+
+class PathCairo : public Path
+{
+public:
+  PathCairo(cairo_t* aCtx, DrawTargetCairo* aDrawTarget, FillRule aFillRule, const Matrix& aTransform);
+
+  virtual BackendType GetBackendType() const { return BACKEND_CAIRO; }
+
+  virtual TemporaryRef<PathBuilder> CopyToBuilder(FillRule aFillRule = FILL_WINDING) const;
+  virtual TemporaryRef<PathBuilder> TransformedCopyToBuilder(const Matrix &aTransform,
+                                                             FillRule aFillRule = FILL_WINDING) const;
+
+  virtual bool ContainsPoint(const Point &aPoint, const Matrix &aTransform) const;
+
+  virtual Rect GetBounds(const Matrix &aTransform = Matrix()) const;
+
+  virtual Rect GetStrokedBounds(const StrokeOptions &aStrokeOptions,
+                                const Matrix &aTransform = Matrix()) const;
+
+  virtual FillRule GetFillRule() const { return mFillRule; }
+
+  TemporaryRef<CairoPathContext> GetPathContext();
+
+  // Set this path to be the current path for aContext (if it's not already
+  // aContext's path). You must pass the draw target associated with the
+  // context as aDrawTarget.
+  void CopyPathTo(cairo_t* aContext, DrawTargetCairo* aDrawTarget);
+
+private:
+  RefPtr<CairoPathContext> mPathContext;
+  FillRule mFillRule;
+};
+
+}
+}
+
+#endif /* MOZILLA_GFX_PATH_CAIRO_H_ */