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 84236 14930a83054b9cfda27d7e60f54680215fd8ac63
parent 84235 4b4d5cd00d43f83f097b2b7a0b7d1e407b5e4081
child 84237 7349c6b4ac7f856f12b606894872c526e8ffae28
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
bugs707848
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 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_ */