diff --git a/CHANGES b/CHANGES
--- a/CHANGES
+++ b/CHANGES
@@ -1,30 +1,37 @@
-0.1: 17 May 2011
-  * initial preview release
+0.5: 9 March 2012
 
-0.1.1: 18 May 2011
-  * fix tic-tac-toe example
+  * New features
 
-0.1.2: 18 May 2011
-  * link to new website
+    - New Diagrams.Backend.Cairo.Text module by Michael Sloan, with
+      functions for creating appropriately sized text objects by
+      querying cairo for the size, and related supporting functions.
 
-0.2: 3 June 2011
+    - Basic support for animation, by generating frames sampled at
+      regular intervals.
 
-  * add Typeable and other instances for Cairo type
+    - Proper vertical alignment of default text based on font
+      parameters (Michael Sloan).
 
-  * generalize Result type to (IO (), Render ()), so programs that
-    don't want to generate a file but just want a Render operation
-    (e.g. to use to paint a gtk window) can use the second component.
+    - Requesting just a width or height now causes the other to be
+      computed appropriately.
 
-  * add support for opacity attribute and path clipping
+  * API changes
 
-0.3: 18 June 2011
+    - Move "Diagrams.Backend.Cairo" to
+      "Diagrams.Backend.Cairo.Internal" and export everything.
+      "Diagrams.Backend.Cairo" now just re-exports selected functions
+      from Internal.  This allows anyone who wants access to the
+      helper/utility functions to import Internal.
 
-  * Some new/improved examples
+  * Dependency/version changes
 
-  * New features:
-    + simple text support
-    + simple support for external PNG images
+    - relax cmdargs upper bound
+    - GHC 7.4.1 compatibility: update base, filepath, old-time upper bounds
 
+  * Bug fixes
+
+    - #54: Generate warning for missing image files (Ian Ross).
+
 0.4: 22 October 2011
 
   * New features:
@@ -37,3 +44,31 @@
 
   * Bug fixes:
     + Warning for unsupported image types (#41)
+
+0.3: 18 June 2011
+
+  * Some new/improved examples
+
+  * New features:
+    + simple text support
+    + simple support for external PNG images
+
+0.2: 3 June 2011
+
+  * add Typeable and other instances for Cairo type
+
+  * generalize Result type to (IO (), Render ()), so programs that
+    don't want to generate a file but just want a Render operation
+    (e.g. to use to paint a gtk window) can use the second component.
+
+  * add support for opacity attribute and path clipping
+
+0.1.2: 18 May 2011
+  * link to new website
+
+0.1.1: 18 May 2011
+  * fix tic-tac-toe example
+
+0.1: 17 May 2011
+  * initial preview release
+
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,9 @@
-Copyright 2011 diagrams-cairo team:
+Copyright 2011-2012 diagrams-cairo team:
 
   Sam Griffin <sam.griffin@gmail.com>
   John Lato <jwlato@gmail.com>
+  Ian Ross <ian@skybluetrades.net>
+  Michael Sloan <mgsloan@gmail.com>
   Luite Stegeman <stegeman@gmail.com>
   Kanchalai Suveepattananont <ksuvee@seas.upenn.edu>
   Ryan Yates <fryguybob@gmail.com>
diff --git a/README b/README
--- a/README
+++ b/README
@@ -7,11 +7,3 @@
 instructions, tutorials, a user manual, a gallery of example images,
 and links to the mailing list, IRC channel, developer wiki and bug
 tracker.
-
-The source repository is mirrored on both patch-tag (darcs) and github
-(git):
-
-  http://patch-tag.com/r/byorgey/diagrams-cairo
-  https://github.com/byorgey/diagrams-cairo
-
-Patches/pull requests welcome in either place.
diff --git a/diagrams-cairo.cabal b/diagrams-cairo.cabal
--- a/diagrams-cairo.cabal
+++ b/diagrams-cairo.cabal
@@ -1,9 +1,15 @@
 Name:                diagrams-cairo
-Version:             0.4
+Version:             0.5
 Synopsis:            Cairo backend for diagrams drawing EDSL
 Description:         A full-featured backend for rendering
                      diagrams using the cairo rendering engine.
                      To get started, see "Diagrams.Backend.Cairo.CmdLine".
+                     .
+                     NOTE: the most recent release of gtk2hs does
+                     not build under GHC 7.4.1. To get diagrams-cairo
+                     to work under GHC 7.4.1 you'll have to install
+                     the latest development version of gtk2hs
+                     (<http://projects.haskell.org/gtk2hs/development/#development>).
 Homepage:            http://projects.haskell.org/diagrams
 License:             BSD3
 License-file:        LICENSE
@@ -13,8 +19,7 @@
 Build-type:          Simple
 Cabal-version:       >=1.6
 Extra-source-files:  CHANGES, README
-Tested-with:         GHC == 6.12.3, GHC >= 7.0.2 && <= 7.0.4, GHC == 7.2.1
-Extra-source-files:  example/*.hs, example/tic-tac-toe/*.hs
+Tested-with:         GHC == 6.12.3, GHC == 7.0.4, GHC == 7.2.1, GHC == 7.4.1
 Source-repository head
   type:     darcs
   location: http://patch-tag.com/r/byorgey/diagrams-cairo
@@ -23,22 +28,29 @@
   Exposed-modules:     Diagrams.Backend.Cairo
                        Diagrams.Backend.Cairo.CmdLine
                        Diagrams.Backend.Cairo.Gtk
+                       Diagrams.Backend.Cairo.Internal
+                       Diagrams.Backend.Cairo.Text
   Hs-source-dirs:      src
-  Build-depends:       base >= 4.2 && < 4.5,
+  Build-depends:       base >= 4.2 && < 4.6,
                        mtl >= 2.0 && < 2.1,
                        process >= 1.0 && < 1.2,
                        directory >= 1.0 && < 1.2,
-                       old-time >= 1.0 && < 1.1,
-                       diagrams-core >= 0.4 && < 0.5,
-                       diagrams-lib >= 0.4 && < 0.5,
+                       filepath >= 1.1 && < 1.4,
+                       old-time >= 1.0 && < 1.2,
+                       diagrams-core >= 0.5 && < 0.6,
+                       diagrams-lib >= 0.5 && < 0.6,
                        cairo >= 0.10.1 && < 0.13,
-                       cmdargs >= 0.6 && < 0.9,
+                       cmdargs >= 0.6 && < 0.10,
                        gtk >= 0.12.0  && < 0.13.0,
                        split >= 0.1.2 && < 0.2
   if !os(windows)
     cpp-options: -DCMDLINELOOP
     Build-depends:     unix >= 2.4 && < 2.6
 
-  if impl(ghc >= 7.2.1)
+  if impl(ghc >= 7.2.1 && < 7.4)
     Build-depends:     cairo >= 0.12.1,
                        gtk   >= 0.12.1
+
+  if impl(ghc >= 7.4.1)
+    Build-depends:     cairo >= 0.12.3,
+                       gtk   >= 0.12.3
diff --git a/example/Apollonius.hs b/example/Apollonius.hs
deleted file mode 100644
--- a/example/Apollonius.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-import Diagrams.Prelude
-import Diagrams.Backend.Cairo.CmdLine
-
-import Test.QuickCheck
-
--- Center and signed radius
-data Circle = Circle { center :: P2, sRadius :: Double }
-
-descartesR sgn r1 r2 r3 =
-  (r1 * r2 * r3) /
-    (r1*r2 + r1*r3 + r2*r3 + sgn * 2 * sqrt(r1*r2*r3*(r1+r2+r3)))
-
-prop_descartes (NonZero r1) (NonZero r2) (NonZero r3) = abs (2*(e1*e1 + e2*e2 + e3*e3 + e4*e4) - (e1+e2+e3+e4)^2) < 1e-10
-  where [e1,e2,e3,e4] = map (1/) [r1,r2,r3,r4]
-        r4 = descartesInR r1 r2 r3
-
-descartesOutR = descartesR (-1)
-descartesInR  = descartesR 1
-
-main = defaultMain (circle (descartesInR 1 1 1) <> circle (descartesOutR 1 1 1) <> circles)
-
-circles = decoratePath (polygon with {sides=3} # scale (2/sqrt 3)) (repeat unitCircle)
diff --git a/example/Fractal.hs b/example/Fractal.hs
deleted file mode 100644
--- a/example/Fractal.hs
+++ /dev/null
@@ -1,33 +0,0 @@
-import Diagrams.Prelude
-
-import qualified Data.Colour as C
-import Diagrams.Backend.Cairo.CmdLine
-
-type D = Diagram Cairo R2
-
-sierpinski :: Int -> D
-sierpinski 1 = t
-sierpinski n =     s
-                  ===
-               (s ||| s)
-  where s = sierpinski (n-1)
-
-t = regPoly 3 1
-    # lw 0
-    # fc black
-
-colors = iterate (C.blend 0.1 white) blue
-
-pentaflake :: Int -> D
-pentaflake 0 = p
-pentaflake n = appends (p' # fc (colors !! (n-1)))
-                       (zip vs (repeat (rotateBy (1/2) p')))
-  where vs = take 5 . iterate (rotateBy (1/5))
-                    . (if odd n then negateV else id) $ unitY
-        p' = pentaflake (n-1)
-
-pentaflake' n = pentaflake n # fc (colors !! n)
-
-p = regPoly 5 1 # lw 0
-
-main = defaultMain (pad 1.1 $ pentaflake' 4)
diff --git a/example/Fun.hs b/example/Fun.hs
deleted file mode 100644
--- a/example/Fun.hs
+++ /dev/null
@@ -1,25 +0,0 @@
-{-# LANGUAGE NoMonomorphismRestriction #-}
-
-import Diagrams.Prelude
-import Diagrams.Backend.Cairo.CmdLine
-
-{-
-pts = [origin, P (1,0), P (1,1), P (2,1), P (2,2), P (3,2), P (3,3)]
-
-curve = cubicSpline False pts
-
-dot = circle 0.05 # fc black
-dots = position (zip pts (repeat dot))
--}
-
-circleP = arc (0::Rad) (tau::Rad)
-
-d = stroke $ circleP <> reversePath circleP # scale 0.8
-
-infVertices = map P [ (0,0), (0.3, 0.2), (0.6, 0), (0.3, -0.2)
-                    , (0,0), (-0.3, 0.2), (-0.6, 0), (-0.3, -0.2)
-                    ]
-
-inf = cubicSpline True infVertices # lw 0.05
-
-main = defaultMain ((d # fc grey) <> inf)
diff --git a/example/Hanoi.hs b/example/Hanoi.hs
deleted file mode 100644
--- a/example/Hanoi.hs
+++ /dev/null
@@ -1,55 +0,0 @@
-import Diagrams.Prelude
-
-import Diagrams.Backend.Cairo.CmdLine
-
-import Data.List
-
-type D = Diagram Cairo R2
-
-colors = [blue, green, red, yellow, purple]
-
-type Disk  = Int
-type Stack = [Disk]
-type Hanoi = [Stack]
-type Move  = (Int,Int)
-
-renderDisk :: Disk -> D
-renderDisk n = rect (fromIntegral n + 2) 1
-               # lc black
-               # fc (colors !! n)
-               # lw 0.1
-
-renderStack :: Stack -> D
-renderStack s = disks `atop` post
-  where disks = (vcat . map renderDisk $ s)
-                # alignB
-        post  = rect 0.8 6
-                # lw 0
-                # fc saddlebrown
-                # alignB
-
-renderHanoi :: Hanoi -> D
-renderHanoi = hcat' with {catMethod = Distrib, sep = 7} . map renderStack
-
-solveHanoi :: Int -> [Move]
-solveHanoi n = solveHanoi' n 0 1 2
-  where solveHanoi' 0 _ _ _ = []
-        solveHanoi' n a b c = solveHanoi' (n-1) a c b ++ [(a,c)]
-                              ++ solveHanoi' (n-1) b a c
-
-doMove :: Move -> Hanoi -> Hanoi
-doMove (x,y) h = h''
-  where (d,h')         = removeDisk x h
-        h''            = addDisk y d h'
-        removeDisk x h = (head (h!!x), modList x tail h)
-        addDisk y d    = modList y (d:)
-
-modList i f l  = let (xs,(y:ys)) = splitAt i l in xs ++ (f y : ys)
-
-hanoiSequence :: Int -> [Hanoi]
-hanoiSequence n = scanl (flip ($)) [[0..n-1], [], []] (map doMove (solveHanoi n))
-
-renderHanoiSeq :: [Hanoi] -> D
-renderHanoiSeq = vcat' with { sep = 2 } . map renderHanoi
-
-main = defaultMain (pad 1.1 $ renderHanoiSeq (hanoiSequence 4) # centerY)
diff --git a/example/Hasse.hs b/example/Hasse.hs
deleted file mode 100644
--- a/example/Hasse.hs
+++ /dev/null
@@ -1,54 +0,0 @@
-{-# LANGUAGE NoMonomorphismRestriction #-}
-
-import Diagrams.Prelude
-import Diagrams.Backend.Cairo.CmdLine
-
-import Data.List
-import Data.Ord (comparing)
-import Data.Function (on)
-
-type D = Diagram Cairo R2
-
-colors = [black, blue, red, yellow, green, orange, purple, brown]
-
-data Subset = Subset Int [Int]
-
-(Subset _ elts1) `isSubset` (Subset _ elts2) = all (`elem` elts2) elts1
-
-subsetsBySize :: Int -> [[Subset]]
-subsetsBySize n = map (map (Subset n))
-                . groupBy ((==) `on` length)
-                . sortBy (comparing length)
-                . subsequences
-                $ [1..n]
-
-drawElts n elts = hcat . map (\i -> if i `elem` elts then drawElt i else strutX 1) $ [1..n]
-drawElt e = unitSquare # fc (colors !! e) # lw 0.05 # freeze
-
-drawSet :: Subset -> D
-drawSet (Subset n elts) = (    drawElts n elts # centerXY
-                            <> rect (fromIntegral n + 0.5) 1.5
-                                 # dashing [0.2,0.2] 0
-                                 # lw 0.03
-                                 # named elts
-                          )
-                          # freeze
-
-hasseRow = centerX . hcat' with {sep = 2} . map drawSet
-
-hasseDiagram n = setsD # drawConnections
-  where setsD = vcat' with {sep = fromIntegral n} . map hasseRow . reverse $ subsets
-        drawConnections = applyAll connections
-        connections = concat $ zipWith connectSome subsets (tail subsets)
-        connectSome subs1 subs2 = [ connect s1 s2 | s1 <- subs1
-                                                  , s2 <- subs2
-                                                  , s1 `isSubset` s2 ]
-        connect (Subset _ elts1) (Subset _ elts2) =
-          withNames [elts1, elts2] $ \[(p1,b1), (p2,b2)] ->
-            (<> (boundaryFrom p1 unitY b1 ~~ boundaryFrom p2 unit_Y b2) # lw 0.03)
-        subsets = subsetsBySize n
-
-d1 =|= d2 = d1 === strutY 3 === d2
-
--- main = defaultMain (pad 1.1 $ foldr1 (=|=) (map hasseDiagram [1..4]))
-main = defaultMain (pad 1.1 $ hasseDiagram 5)
diff --git a/example/Hilbert.hs b/example/Hilbert.hs
deleted file mode 100644
--- a/example/Hilbert.hs
+++ /dev/null
@@ -1,25 +0,0 @@
-{-# LANGUAGE NoMonomorphismRestriction #-}
-
-import Diagrams.Prelude
-import Diagrams.Backend.Cairo.CmdLine
-
-hilbert :: [Trail R2]
-hilbert = iterate genHilbert mempty
-  where genHilbert t = let t' = reverseTrail t
-                       in  mconcat [ rotateBy (-1/4) t'
-                                   , fromOffsets [unitY]
-                                   , t
-                                   , fromOffsets [unitX]
-                                   , t
-                                   , fromOffsets [negateV unitY]
-                                   , rotateBy (1/4) t'
-                                   ]
-
-d = (hilbert !! 6)
-  # strokeT
-  # lw 0.2
-  # fc red
-  # centerXY
-  # pad 1.1
-
-main = defaultMain d
diff --git a/example/Logo.hs b/example/Logo.hs
deleted file mode 100644
--- a/example/Logo.hs
+++ /dev/null
@@ -1,112 +0,0 @@
-{-# LANGUAGE NoMonomorphismRestriction #-}
-
-import Diagrams.Prelude
-import Diagrams.Backend.Cairo.CmdLine
-import Diagrams.TwoD.Path
-
-import qualified Data.Colour as C
-
-d = (stroke $
-   circlePath 2 # alignBR # translateX (-0.5)
-   <> (hcat' with { sep = 0.2 } . map (vcat' with {sep = 0.2})
-        $ (replicate 2 (replicate 9 (reversePath $ circlePath 0.3)))) # alignBR)
-    # fc red
-    # lw 0
-
-{-
-dAngle :: Rad
-dAngle = tau / 6
-
-dHeight = 5
-dWidth  = 1
-
-dRad = 1.5
-
-dPath = pathFromTrail . close $
-        arc dAngle (tau - dAngle) # scale dRad
-     <> fromOffsets [ (0, -(1 - cos (tau/4 - getRad dAngle)) * dRad)
-                    , (dWidth, 0)
-                    , (0, dHeight)
-                    , (-dWidth, 0)
-                    ]
-
-dotMatrix d path
-  = mconcat [ if (isInsideWinding p path)
-                 then circle (d / 2 - d/10) # fc red # lw 0 # moveTo p
-                 else mempty
-            | x <- [ xlo, xlo + d .. xhi ]
-            , y <- [ ylo, ylo + d .. yhi ]
-            , let p = P (x,y)
-            ]
-  where (xlo, xhi) = extentX path
-        (ylo, yhi) = extentY path
-
-matrixAng = 1/20
-
-d = dotMatrix 0.09 (dPath # rotateBy matrixAng) # rotateBy (-matrixAng)
--}
-
-{-
-d = mconcat . map alignBR
-  $ [ dBody
-    , rect 0.5 6.5 # fc black
-    ]
-
-blues = iterate (C.blend 0.1 white) blue
-      # take 11 # reverse
-
-dBody = mconcat . reverse . zipWith fc blues . take 11 . iterate (rotateBy (-1/20)) . rotateBy (-1/4) $ halfC
-
-halfC = arc 0 (1/2 :: CircleFrac)
-      # scale 1.8
-      # stroke
-      # lw 0
--}
-
-i = (circle 1 === strutY 0.5 === roundedRect (2,4) 0.4)
-    # lw 0.05
-    # lc blue
-    # fc yellow
-
-sierpinski 1 = polygon with { polyType = PolyRegular 3 1 }
-sierpinski n = s === (s ||| s)
-  where s = sierpinski (n-1)
-
-a1 = sierpinski 4
-     # fc black
-     # scale (1/2)
-
-grid = verts # centerXY <> horiz # centerXY
-  where verts = hcat' with {sep=0.5} $ replicate 20 (vrule 10)
-        horiz = rotateBy (1/4) verts
-
-gbkg = grid
-    # lc gray
-    # rotateBy (-1/20)
-    # clipBy p
-    # withBounds (p :: Path R2)
-  where p = square 5
-
-g = (text "G" # fontSize 4 # rotateBy (-1/20)) <> gbkg
-
-r = text "r" # fontSize 5
-  <> square 1 # lw 0 # scale 5
-
-a2 = text "a" # fontSize 5
-  <> square 1 # lw 0 # scale 5
-
-m = square 5.5 <>
-    text "m"
-      # fontSize 6 # italic # font "freeserif" # fc green
-
-vs = [(5,5), (3,6), (1,5), (1,4), (3,3), (5,2), (4,0), (0,0.5)]
-s = (mconcat (map (\v -> translate v (dot blue)) vs) <>
-    cubicSpline False (map P vs) # lw 0.20)
-    # scale 0.8
-
-dot c = circle 0.4 # fc c # lw 0
-
-logo = (hcat' with {sep = 0.5} . map alignB $ [ d, i, a1, g, r, a2, m, s ])
-       # centerXY
-
-main = defaultMain (pad 1.1 logo)
diff --git a/example/Star.hs b/example/Star.hs
deleted file mode 100644
--- a/example/Star.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-{-# LANGUAGE NoMonomorphismRestriction, TupleSections #-}
-
-import Diagrams.Prelude
-import Diagrams.Backend.Cairo.CmdLine
-
-axes n = h <> v
-  where p = fromOffsets . replicate n
-        h = stroke' with {vertexNames = [map ("x",) [0..n]]} (p unitX)
-        v = stroke' with {vertexNames = [map ("y",) [0..n]]} (p unitY)
-
-connect n i = withNames [("x",i), ("y", n - i)] $ \[(x,_), (y,_)] ->
-                drawConnect [x,y]
-  where drawConnect = atop . fromVertices
-
-pic n = applyAll (map (connect n) [0..n]) (axes n) # centerXY # lw 0.05
-
-d n = half === rotateBy (1/2) half
-  where half = rotateBy (1/4) (pic n) ||| pic n
-
-main = defaultMain (pad 1.1 $ d 20 # lc blue)
diff --git a/example/cubicSpline.hs b/example/cubicSpline.hs
deleted file mode 100644
--- a/example/cubicSpline.hs
+++ /dev/null
@@ -1,25 +0,0 @@
-import Diagrams.Prelude
-import Diagrams.Backend.Cairo.CmdLine
-
-import Diagrams.CubicSpline.Internal
-
-import Control.Newtype
-
-
-poly cs t = foldr mul 0 cs
-  where mul a b = a + b * t
-
--- Interpolate directly on the spline coefficents
-interpolate closed vs = concatMap (\c -> map (poly c) (map fromRational [0.0,0.01..1.0])) cs
-  where
-    cs = solveCubicSplineCoefficients closed vs
-
-
-main = defaultMain (pad 1.1 (mconcat [a,b,c]))
-  where 
-    vs = map P [(0,10),(2,2),(3.5,8),(4.5,6)]
-    closed = True
-    
-    a = (lc blue  . lw 0.05 . stroke . fromVertices) (overF P (interpolate closed) vs)
-    b = (lc red   . lw 0.07 . stroke)                (cubicSpline closed vs)
-    c = (lc green . lw 0.1  . stroke . fromVertices) vs
diff --git a/example/golden.hs b/example/golden.hs
deleted file mode 100644
--- a/example/golden.hs
+++ /dev/null
@@ -1,31 +0,0 @@
-{-# LANGUAGE NoMonomorphismRestriction #-}
-import Diagrams.Prelude
-import Diagrams.Backend.Cairo.CmdLine
-
-import Data.VectorSpace
-
-type D = Diagram Cairo R2
-
-sq :: D
-sq = (lw 1 . stroke $ arc (pi / 2 :: Rad) (pi :: Rad))
-     `atop` (translate (-0.5, 0.5) . lw 0.5 $ unitSquare)
-
-phi :: Double
-phi = (1 + sqrt 5) / 2
-
-step :: D -> D -> D
-step a b = besideAlign (-1,0) (0,1) a b'
-  where b' = rotate (-pi/2 :: Rad) . scale (1/phi) $ b
-
-besideAlign :: R2 -> R2 -> D -> D -> D
-besideAlign u v a b = beside u b' a
-  where b' = translate (v ^* d) b
-        (Bounds bb) = bounds b
-        (Bounds ba) = bounds a
-        d = (ba v - bb v)
-
-golden :: D
-golden = pad 1.05 . freeze . scale 100 $ foldr1 step (replicate 10 sq)
-
-main :: IO ()
-main = defaultMain golden
diff --git a/example/gray.hs b/example/gray.hs
deleted file mode 100644
--- a/example/gray.hs
+++ /dev/null
@@ -1,33 +0,0 @@
-{-# LANGUAGE NoMonomorphismRestriction #-}
-
-import Diagrams.Prelude hiding (gray)
-import Diagrams.Backend.Cairo.CmdLine
-
-import Data.List.Split      (chunk)
-import Data.Maybe           (catMaybes)
-import Control.Applicative
-import Data.Monoid          (mconcat)
-import Data.List            (transpose)
-
-type D = Diagram Cairo R2
-
-gray 0 = [[]]
-gray n = map (False:) g ++ map (True:) (reverse g)
-  where g = gray (n-1)
-
-rings n = mkRingsDia . map ringOffsets . transpose . gray $ n
-  where ringOffsets :: [Bool] -> [(CircleFrac, CircleFrac)]
-        ringOffsets = map l2t . chunk 2 . findEdges . zip [0,1/(2^n)..1]
-        l2t [x,y] = (x,y)
-        l2t [x]   = (x,2*pi)
-
-findEdges :: Eq a => [(CircleFrac, a)] -> [CircleFrac]
-findEdges = catMaybes . (zipWith edge <*> tail)
-  where edge (_,c1) (a,c2) | c1 /= c2  = Just a
-                           | otherwise = Nothing
-
-mkRingsDia :: [[(CircleFrac, CircleFrac)]] -> D
-mkRingsDia = freeze . mconcat . zipWith mkRingDia [2,3..]
-  where mkRingDia r = lw 1.05 . mconcat . map (stroke . scale r . uncurry arc)
-
-main = defaultMain $ pad 1.1 (rings 10)
diff --git a/example/hslogo.hs b/example/hslogo.hs
deleted file mode 100644
--- a/example/hslogo.hs
+++ /dev/null
@@ -1,116 +0,0 @@
-{-# LANGUAGE TypeFamilies #-}
-{- Haskell logo in Diagrams by Ryan Yates.
-   Based on Metapost version Brian Sniffen.
-   This was based on a public domain PNG by Jeff Wheeler,
-   and on logo contest entries by George Pollard, Darrin Thompson, and others.
-
-   (c) 2009 Brian Sniffen, All rights reserved.
-
-   Redistribution and use in source and binary forms, with or without
-   modification, are permitted provided that the following conditions
-   are met:
-
-   1. Redistributions of source code must retain the above copyright
-   notice, this list of conditions and the following disclaimer.
-
-   2. Redistributions in binary form must reproduce the above copyright
-   notice, this list of conditions and the following disclaimer in the
-   documentation and/or other materials provided with the distribution.
-
-   3. Neither the name of the author nor the names of his contributors
-   may be used to endorse or promote products derived from this software
-   without specific prior written permission.
-
-   THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
-   OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-   DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
-   ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-   OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-   POSSIBILITY OF SUCH DAMAGE. -}
-
-import Diagrams.Prelude hiding (intersection)
-
-import Diagrams.Backend.Cairo
-import Diagrams.Backend.Cairo.CmdLine
-
-import Data.VectorSpace
-import Data.Colour.SRGB
-
-type D = Diagram Cairo R2
-
-mkPath :: [R2] -> D
-mkPath = stroke . close . fromVertices . map P
-
-rangle, lambdabody, lambdaleg, lambda :: Double -> Double -> D
-
-rangle h m = mkPath
-  [ zeroV,       (4*h,4*m*h), (0,8*m*h)
-  , (3*h,8*m*h), (7*h,4*m*h), (3*h,0)
-  ]
-lambdabody h m = translate (4*h,0) (rangle h m)
-lambdaleg h m = translate (4*h,0) path
-  where path = mkPath [(11*h,0), (8*h,0), (0,8*m*h), (3*h,8*m*h)]
-
-lambda h m = mkPath
-  [ (4*h,0),  (8*h,4*m*h), (4*h,8*m*h),     (7*h,8*m*h)
-  , (15*h,0), (12*h,0),    (9.5*h,2.5*m*h), (7*h,0)
-  ]
-
--- The equal sign is 5 units high: two units thick in each block,
--- with one unit thickness of white between.  Being centered, that
--- puts its outer corners at 3.5, 5.5, 6.5, and 8.5.  The right edge
--- is at 17 units.  The left edge is the standard 1-unit horizontal
--- gap from the lambda.
--- These gaps are all by horizontal measure---the components are
--- closer than 1 unit to each other.
-equalsign :: Double -> Double -> Double -> Double -> D
-equalsign h m maxx miny = mkPath q
-  where
-    maxy = miny + 4/3;
-    cutoff = ((16*h,0*h), (8*h,8*m*h))
-    p1 = ((maxx*h,miny*m*h), (0*h,miny*m*h)) `intersection` cutoff;
-    p2 = ((maxx*h,maxy*m*h), (0*h,maxy*m*h)) `intersection` cutoff;
-    q  = [p1, p2, (maxx*h,maxy*m*h), (maxx*h,miny*m*h)]
-
-stdrangle, stdlambda, stdlambdabody, stdlambdaleg, lowerequal, upperequal :: D
-
-stdrangle     = rangle     1 (3/2)
-stdlambda     = lambda     1 (3/2)
-stdlambdabody = lambdabody 1 (3/2)
-stdlambdaleg  = lambdaleg  1 (3/2)
-lowerequal    = equalsign  1 (3/2) 17 (7/3)
-upperequal    = equalsign  1 (3/2) 17 (13/3)
-
-logo :: D
-logo =     light   stdrangle
-    `atop` lighter stdlambda
-    `atop` light   lowerequal
-    `atop` light   upperequal
-  where light   = fc (sRGB 0.4 0.4 0.4)
-        lighter = fc (sRGB 0.6 0.6 0.6)
-
-main :: IO ()
-main = defaultMain logo
-
--- If we take the given vectors and put them in R^3 as (x,y,0),
--- we are resulting in the z component of their cross product.
-cross3Z :: (Num t) => (t, t) -> (t, t) -> t
-cross3Z (x0,y0) (x1,y1) = x0 * y1 - x1 * y0
-
--- This is just a one off version.  A more robust version would
--- included data indicating if the lines were parallel or colinear
--- among other things.
-intersection :: (t ~ Scalar t, Fractional t, VectorSpace t) =>
-     ((t, t), (t, t)) -> ((t, t), (t, t)) -> (t, t)
-intersection (a0,a1) (b0,b1) = a0 ^+^ (va ^* t)
-  where vb = b1 ^-^ b0
-        va = a1 ^-^ a0
-        v  = a0 ^-^ b0
-        d  = cross3Z va vb
-        n  = cross3Z vb v
-        t  = n / d
diff --git a/example/paradox.hs b/example/paradox.hs
deleted file mode 100644
--- a/example/paradox.hs
+++ /dev/null
@@ -1,73 +0,0 @@
-{-# LANGUAGE NoMonomorphismRestriction #-}
-import Diagrams.Prelude
-import Diagrams.Backend.Cairo.CmdLine
-
-fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
-
-thick = 0.15
-
-grid x y = frame <> lattice
-  where s       = unitSquare # lw 0.02 # freeze
-        frame   = rect (fromIntegral x) (fromIntegral y)
-                # lw thick # freeze
-        lattice = centerXY . vcat . map hcat . replicate y . replicate x $ s
-
-trap s1 s2 = fromOffsets [(0,-s2), (s2,0), (0,s1)]
-             # close # stroke # lw 0
-tri s1 s2  = fromOffsets [(s1,0), (0,s1+s2)]
-             # close # stroke # lw 0
-
-paradox n drawDiags = sq ||| strutX s2 ||| rect
-  where f1 = fibs !! n
-        f2 = fibs !! (n+1)
-        s1 = fromIntegral f1
-        s2 = fromIntegral f2
-
-        trap1 = trap s1 s2 # fc yellow
-        trap2 = trap s1 s2 # fc green
-                           # rotateBy (1/2)
-
-        tri1  = tri s1 s2  # fc red
-        tri2  = tri s1 s2  # fc blue
-
-        sq = (if drawDiags then sqDiags else mempty)
-             <> grid (f1+f2) (f1+f2)
-             <> sqShapes
-        sqDiags = (P (0,s2) ~~ P (s2,s1)       <>
-                   P (s2,0) ~~ P (s2,s1+s2)    <>
-                   P (s2,0) ~~ P (s1+s2,s1+s2))
-                # stroke
-                # lw thick
-                # freeze
-                # centerXY
-
-        sqShapes = (traps # centerY ||| tris # centerY)
-                 # centerXY
-        traps = trap2 # alignL
-                      # translateY (s1 - s2)
-             <> trap1
-        tris  = tri1 # alignBL
-             <> tri2 # rotateBy (1/2)
-                     # alignBL
-
-        rect = (if drawDiags then rDiags else mempty)
-               <> grid (2*f2 + f1) f2
-               <> rShapes
-
-        rShapes = (bot # alignTL <> top # alignTL) # centerXY
-        bot = trap1 # alignB ||| rotateBy (-1/4) tri1 # alignB
-        top = rotateBy (1/4) tri2 # alignT ||| trap2 # alignT
-
-        rDiags = (P (0,s2)        ~~ P (2*s2+s1, 0)      <>
-                  P (s2,0)        ~~ P (s2,s1)           <>
-                  P (s1+s2,s2-s1) ~~ P (s1+s2,s2)
-                  )
-                 # stroke
-                 # lw thick
-                 # lineCap LineCapRound
-                 # freeze
-                 # centerXY
-
-dia = paradox 4 True
-
-main = defaultMain (pad 1.1 dia)
diff --git a/example/tic-tac-toe/Maps.hs b/example/tic-tac-toe/Maps.hs
deleted file mode 100644
--- a/example/tic-tac-toe/Maps.hs
+++ /dev/null
@@ -1,98 +0,0 @@
-{-# LANGUAGE NoMonomorphismRestriction #-}
-
--- Maps of optimal tic-tac-toe play, inspired by similar maps created
--- by Randall Munroe, http://xkcd.com/832/
-
-import Diagrams.Prelude hiding (Result)
-import Diagrams.Backend.Cairo.CmdLine
-
-import Data.List.Split (chunk)                   -- cabal install split
-import Data.Maybe (fromMaybe, catMaybes)
-import qualified Data.Map as M
-import Data.Tree
-import Control.Arrow (second, (&&&), (***))
-import Data.Array (assocs)
-
-import Solve
-
-type D = Diagram Cairo R2
-
-x, o :: D
-x = (P (-1,1) ~~ P (1,-1) <> P (1,1) ~~ P (-1,-1))
-  # lw 0.05
-  # lineCap LineCapRound
-  # scale 0.4
-  # freeze
-  # centerXY
-o = circle 0.4
-  # lw 0.05
-  # freeze
-
--- | Render a list of lists of diagrams in a grid.
-grid :: Double -> [[D]] -> D
-grid s = centerXY
-       . vcat' with {catMethod = Distrib, sep = s}
-       . map (hcat' with {catMethod = Distrib, sep = s})
-
--- | Given a mapping from (r,c) locations (in a 3x3 grid) to diagrams,
---   render them in a grid, surrounded by a square.
-renderGrid :: M.Map Loc D -> D
-renderGrid g
-  = (grid 1
-  . chunk 3
-  . map (fromMaybe (phantom x) . flip M.lookup g)
-  $ [ (r,c) | r <- [0..2], c <- [0..2] ])
-
-    `atop`
-    square 3 # lw 0.02 # freeze
-
--- | Given a solved game tree, where the first move is being made by
---   the player for whom the tree is solved, render a map of optimal play.
-renderSolvedP :: Tree (Game, Result) -> D
-renderSolvedP (Node (Game _ p _, _) [])   -- cats game, this player does not
-    = renderPlayer (next p) # scale 3     -- get another move; instead of
-                                          -- recursively rendering this game
-                                          -- just render an X or an O
-renderSolvedP (Node (Game board player1 _, _)
-                    [g'@(Node (Game _ _ (Move _ loc : _), res) conts)])
-    = renderResult res <>    -- Draw a line through a win
-      renderGrid cur   <>    -- Draw the optimal move + current moves
-      renderOtherP g'        -- Recursively render responses to other moves
-
-  where cur = M.singleton loc (renderPlayer player1 # lc red)  -- the optimal move
-              <> curMoves board                                -- current moves
-
-renderSolvedP _ = error "renderSolvedP should be called on solved trees only"
-
--- | Given a solved game tree, where the first move is being made by the
---   opponent of the player for whom the tree is solved, render a map of optimal
---   play.
-renderOtherP :: Tree (Game, Result) -> D
-renderOtherP (Node _ conts)
-    -- just recursively render each game arising from an opponent's move in a grid.
-  = renderGrid . M.fromList . map (getMove &&& (scale (1/3) . renderSolvedP)) $ conts
-  where getMove (Node (Game _ _ (Move _ m : _), _) _) = m
-
--- | Extract the current moves from a board.
-curMoves :: Board -> M.Map Loc D
-curMoves = M.fromList . (map . second) renderPlayer . catMaybes . map strength . assocs
-
--- | Render a line through a win.
-renderResult :: Result -> D
-renderResult (Win _ 0 ls) = winLine # freeze
-  where winLine :: D
-        winLine = fromVertices (map (P . conv) ls)
-                  # lw 0.2
-                  # lc blue
-                  # lineCap LineCapRound
-        conv (r,c) = (fromIntegral $ c - 1, fromIntegral $ 1 - r)
-renderResult _ = mempty
-
-renderPlayer X = x
-renderPlayer O = o
-
-xMap = renderSolvedP . solveFor X $ gameTree
-oMap = renderOtherP  . solveFor O $ gameTree
-
-main = defaultMain (pad 1.1 xMap)
-       -- defaultMain (pad 1.1 oMap)
diff --git a/example/tic-tac-toe/Solve.hs b/example/tic-tac-toe/Solve.hs
deleted file mode 100644
--- a/example/tic-tac-toe/Solve.hs
+++ /dev/null
@@ -1,143 +0,0 @@
-{-# LANGUAGE PatternGuards, ViewPatterns #-}
-
-module Solve where
-
-import Data.Array
-import Data.Tree
-import Data.Function (on)
-import Data.List (groupBy, maximumBy)
-import Data.Maybe (isNothing, isJust)
-import Data.Monoid
-import Control.Applicative (liftA2)
-import Control.Arrow ((&&&))
-import Control.Monad (guard)
-
-data Player = X | O
-  deriving (Show, Eq, Ord)
-
-next X = O
-next O = X
-
-data Result = Win Player Int [Loc] -- ^ This player can win in n moves
-            | Cats                 -- ^ Tie game
-  deriving (Show, Eq)
-
-compareResultsFor :: Player -> (Result -> Result -> Ordering)
-compareResultsFor X = compare `on` resultToScore
-    where resultToScore (Win X n _) = (1/(1+fromIntegral n))
-          resultToScore Cats        = 0
-          resultToScore (Win O n _) = (-1/(1+fromIntegral n))
-compareResultsFor O = flip (compareResultsFor X)
-
-type Loc = (Int,Int)
-type Board = Array Loc (Maybe Player)
-
-emptyBoard :: Board
-emptyBoard = listArray ((0,0), (2,2)) (repeat Nothing)
-
-showBoard :: Board -> String
-showBoard = unlines . map showRow . groupBy ((==) `on` (fst . fst)) . assocs
-  where showRow = concatMap (showPiece . snd)
-        showPiece Nothing  = " "
-        showPiece (Just p) = show p
-
-data Move = Move Player Loc
-  deriving Show
-
-makeMove :: Move -> Board -> Board
-makeMove (Move p l) b = b // [(l, Just p)]
-
-data Game = Game Board           -- ^ The current board state.
-                 Player          -- ^ Whose turn is it?
-                 [Move]          -- ^ The list of moves so far (most
-                                 --   recent first).
-  deriving Show
-
-initialGame = Game emptyBoard X []
-
--- | The full game tree for tic-tac-toe.
-gameTree :: Tree Game
-gameTree = unfoldTree (id &&& genMoves) initialGame
-
--- | Generate all possible successor games from the given game.
-genMoves :: Game -> [Game]
-genMoves (Game board player moves) = newGames
-  where validLocs = map fst . filter (isNothing . snd) . assocs $ board
-        newGames  = [Game (makeMove m board) (next player) (m:moves)
-                      | p <- validLocs
-                      , let m = Move player p
-                    ]
-
--- | Simple fold for Trees.  The Data.Tree module does not provide
---   this.
-foldTree :: (a -> [b] -> b) -> Tree a -> b
-foldTree f (Node a ts) = f a (map (foldTree f) ts)
-
--- | Solve the game for player @p@: prune all but the optimal moves
---   for player @p@, and annotate each game with its result (given
---   best play).
-solveFor :: Player -> Tree Game -> Tree (Game, Result)
-solveFor p = foldTree (solveStep p)
-
--- | Given a game and its continuations (including their results),
---   solve the game for player p.  If it is player p's turn, prune all
---   continuations except the optimal one for p. Otherwise, leave all
---   continuations.  The result of this game is the result of the
---   optimal choice if it is p's turn, otherwise the worst possible
---   outcome for p.
-solveStep :: Player -> Game -> [Tree (Game, Result)] -> Tree (Game, Result)
-solveStep p g@(Game brd curPlayer moves) conts
-  | Just res <- gameOver g = Node (g, res) []
-
-  | curPlayer == p = let c   = bestContFor p conts
-                         res = inc . snd . rootLabel $ c
-                     in  Node (g, res) [c]
-  | otherwise      = Node (g, bestResultFor (next p) conts) conts
-
-bestContFor :: Player -> [Tree (Game, Result)] -> Tree (Game, Result)
-bestContFor p = maximumBy (compareResultsFor p `on` (snd . rootLabel))
-
-bestResultFor :: Player -> [Tree (Game, Result)] -> Result
-bestResultFor p = inc . snd . rootLabel . bestContFor p
-
-inc :: Result -> Result
-inc (Win p n ls) = Win p (n+1) ls
-inc Cats         = Cats
-
--- | Check whether the game is over, returning the result if it is.
-gameOver :: Game -> Maybe Result
-gameOver (Game board _ _)
-  = getFirst $ mconcat (map (checkWin board) threes) `mappend` checkCats board
-
-checkWin :: Board -> [Loc] -> First Result
-checkWin board = First
-               . (>>= winAsResult)      -- Maybe Result
-               . mapM strength          -- Maybe [(Loc, Player)]
-               . map (id &&& (board!))  -- [(Loc, Maybe Player)]
-
-winAsResult :: [(Loc, Player)] -> Maybe Result
-winAsResult (unzip -> (ls,ps))
-  | Just p <- allEqual ps = Just (Win p 0 ls)
-winAsResult _ = Nothing
-
-checkCats :: Board -> First Result
-checkCats b | all isJust (elems b) = First (Just Cats)
-            | otherwise            = First Nothing
-
-allEqual :: Eq a => [a] -> Maybe a
-allEqual = foldr1 combine . map Just
-  where combine (Just x) (Just y) | x == y = Just x
-                                  | otherwise = Nothing
-        combine Nothing _         = Nothing
-        combine _ Nothing         = Nothing
-
-strength :: Functor f => (a, f b) -> f (a,b)
-strength (a, f) = fmap ((,) a) f
-
-threes :: [[Loc]]
-threes = rows ++ cols ++ diags
-  where rows     = [ [ (r,c) | c <- [0..2] ] | r <- [0..2] ]
-        cols     = [ [ (r,c) | r <- [0..2] ] | c <- [0..2] ]
-        diags    = [ [ (i,i) | i <- [0..2] ]
-                   , [ (i,2-i) | i <- [0..2] ]
-                   ]
diff --git a/example/triangular-numbers.hs b/example/triangular-numbers.hs
deleted file mode 100644
--- a/example/triangular-numbers.hs
+++ /dev/null
@@ -1,79 +0,0 @@
-{-# LANGUAGE NoMonomorphismRestriction #-}
-
--- Diagrams created for blog post at
---   http://mathlesstraveled.com/2011/04/14/triangular-number-equations-via-pictures-solutions/
-
-import Diagrams.Prelude
-import Diagrams.Backend.Cairo.CmdLine
-
-import Data.Colour hiding (atop)
-
-type D = Diagram Cairo R2
-
-tri c n = dots `atop` (strokeT edges # lc c # lw 0.2 # fcA (c `withOpacity` 0.5))
-  where rows = map (hcat' with { sep = 1 })
-             . zipWith replicate [n,n-1..1]
-             . repeat
-             $ dot c
-        dots = decorateTrail (rotateBy (1/6) edge) rows
-        edge = fromOffsets . replicate (n-1) $ unitX # scale 3
-        edges = close (edge <> rotateBy (1/3) edge <> rotateBy (2/3) edge)
-
-dot c = unitCircle
-      # lw 0
-      # fc c
-
-rowSpc = height (rotateBy (1/6) $ strutY 1 :: D)
-
--- @row k n s c@ draws a row of k size-n triangles with color c,
--- separated by enough space for @s@ dots.
-row k n s c = hcat' with {sep = 1 + 3*s} (replicate k (tri c n))
-
--- 3 T(n) + T(n-1) = T(2n)
-law1 n c1 c2 = law3 1 n c1 c2
-
--- 3T(n) + T(n+1) = T(2n+1)
-law2 n c1 c2 = top === strutY rowSpc === (base `atop` mid)
-  where base = row 2 n 1 c1
-             # centerX
-             # alignB
-        mid  = row 1 n 0 c1
-             # reflectY
-             # centerX
-             # alignB
-        top  = tri c2 (n+1)
-             # centerX
-
--- (2k+1)T(n) + T(kn - 1) = T((k+1)n)
-law3 k n c1 c2 = top === strutY rowSpc === (mid `atop` base)
-  where base = row (k+1) n 0 c1 # centerX
-                                # alignB
-        mid  = row k n 0 c1 # reflectY
-                            # centerX
-                            # alignB
-                            # translateY (2 + rowSpc)
-        top  = tri c2 (k*n - 1) # centerX
-
--- T(n) T(k) + T(n-1) T(k-1) = T(nk)
-law4 k n c1 c2 = vcat' with {sep = rowSpc} (map tRow [1..k])
-  where tRow k = (row k n 0 c1 # centerX # alignT)
-                `atop`
-                 (row (k-1) (n-1) 1 c2 # reflectY # centerX # alignT)
-
-exampleRow f = hcat' with {sep = 4} . map (alignB . f)
-
-law1Dia = exampleRow law1' [2..4]
-  where law1' n = law1 n blue red
-
-law2Dia = exampleRow law2' [1..3]
-  where law2' n = law2 n green orange
-
-law3Dia = exampleRow law3' [1..3]
-  where law3' k = law3 k 2 saddlebrown gray
-
-law4Dia = exampleRow law4' [2..4]
-  where law4' k = law4 k 3 purple gold
-
-main = defaultMain (pad 1.05 $ vcat' with {sep=5} . map centerXY $
-                             [law1Dia -- , law2Dia, law3Dia, law4Dia
-                             ])
diff --git a/src/Diagrams/Backend/Cairo.hs b/src/Diagrams/Backend/Cairo.hs
--- a/src/Diagrams/Backend/Cairo.hs
+++ b/src/Diagrams/Backend/Cairo.hs
@@ -1,16 +1,8 @@
-{-# LANGUAGE TypeFamilies
-           , MultiParamTypeClasses
-           , FlexibleInstances
-           , FlexibleContexts
-           , ExistentialQuantification
-           , TypeSynonymInstances
-           , DeriveDataTypeable
-           , ViewPatterns
-  #-}
+{-# LANGUAGE TypeFamilies, CPP #-}
 
 -----------------------------------------------------------------------------
 -- |
--- Module      :  Diagrams.Backend.Cairo.CmdLine
+-- Module      :  Diagrams.Backend.Cairo
 -- Copyright   :  (c) 2011 Diagrams-cairo team (see LICENSE)
 -- License     :  BSD-style (see LICENSE)
 -- Maintainer  :  diagrams-discuss@googlegroups.com
@@ -22,90 +14,29 @@
 module Diagrams.Backend.Cairo
 
   ( -- * Cairo-supported output formats
-    OutputFormat(..)
+    OutputType(..)
 
     -- * Cairo-specific options
     -- $CairoOptions
+
+-- The below CPP hack is needed because GHC 7.0.x has a bug regarding
+-- (re?)export of data family constructors; in particular the below
+-- export causes the error "Not in scope: type constructor or class
+-- `Options'" even though
+-- http://www.haskell.org/haskellwiki/GHC/Type_families#Import_and_export
+-- seems to indicate it should be supported.  When using 7.0.x one
+-- must import Diagrams.Backend.Cairo.Internal in order to bring
+-- CairoOptions into scope.
+-- GHC 7.4.0 regression?
+#if __GLASGOW_HASKELL__ >= 702 && __GLASGOW_HASKELL__ < 704
   , Options(..)
+#endif
 
     -- * Backend token
   , Cairo(..)
   ) where
 
-import Graphics.Rendering.Diagrams.Transform
-
-import Diagrams.Prelude
-import Diagrams.TwoD.Ellipse
-import Diagrams.TwoD.Path (Clip(..), getFillRule)
-import Diagrams.TwoD.Text
-import Diagrams.TwoD.Image
-import Diagrams.TwoD.Adjust (adjustDia2D, adjustSize)
-
-import Graphics.UI.Gtk (DrawableClass)
-import qualified Graphics.UI.Gtk.Cairo as CG
-
-import qualified Graphics.Rendering.Cairo as C
-import qualified Graphics.Rendering.Cairo.Matrix as CM
-
-import Control.Applicative ((<$>))
-import Control.Monad.State
-import Data.Maybe (catMaybes, fromMaybe)
-import Data.List (isSuffixOf)
-
-import qualified Data.Foldable as F
-
-import Data.Typeable
-
--- | This data declaration is simply used as a token to distinguish
---   this rendering engine.
-data Cairo = Cairo
-  deriving (Eq,Ord,Read,Show,Typeable)
-
--- | Cairo is able to output to several file formats, which each have
---   their own associated properties that affect the output.
-data OutputFormat =
-    -- | Output directly to a Gtk window.  See "Diagrams.Backends.Cairo.Gtk".
-    forall dw. (DrawableClass dw) =>
-    GTK { gtkWindow :: dw
-          -- ^ The window on which to draw.
-
-        , gtkSize   :: Maybe (Int, Int)
-          -- ^ The size of the output is given in pixels. If @Nothing@,
-          --   rescaling should not be performed.
-
-        , gtkBypass :: Bool
-          -- ^ Should the 'adjustDia' step be bypassed during rendering?
-        }
-    -- | PNG is unique, in that it is not a vector format.
-  | PNG { pngSize :: (Int, Int)       -- ^ the size of the output is given in pixels
-        }
-    -- | PostScript output.
-  | PS  { psSize  :: (Double, Double) -- ^ the size of the output is given in points
-        }
-    -- | Portable Document Format (PDF) output.
-  | PDF { pdfSize :: (Double, Double) -- ^ the size of the output is given in points
-        }
-    -- | Scalable Vector Graphics (SVG) output.
-  | SVG { svgSize :: (Double, Double) -- ^ the size of the output is given in points
-        }
-
-instance Monoid (Render Cairo R2) where
-  mempty  = C $ return ()
-  (C r1) `mappend` (C r2) = C (r1 >> r2)
-
-type RenderM a = StateT () C.Render a  -- no state for now
-
--- simple, stupid implementations of save and restore for now, since
--- it suffices to just reset the text alignment to "centered" on
--- restore.  But if need be we can switch to a more sophisticated
--- implementation using an "undoable state" monad which lets you save
--- (push state onto a stack) and restore (pop from the stack).
-
-save :: RenderM ()
-save = lift C.save
-
-restore :: RenderM ()
-restore = lift C.restore
+import Diagrams.Backend.Cairo.Internal
 
 -- $CairoOptions
 --
@@ -114,240 +45,16 @@
 -- This module defines
 --
 -- > data family Options Cairo R2 = CairoOptions
--- >               { fileName     :: String       -- ^ the name of the file you want generated
--- >               , outputFormat :: OutputFormat -- ^ the output format and associated options
--- >               }
+-- >           { cairoFileName   :: String     -- ^ The name of the file you want generated
+-- >           , cairoSizeSpec   :: SizeSpec2D -- ^ The requested size of the output
+-- >           , cairoOutputType :: OutputType -- ^ the output format and associated options
+-- >           }
 --
 -- So, for example, you could call the 'renderDia' function (from
 -- "Graphics.Rendering.Diagrams.Core") like this:
 --
--- > renderDia Cairo (CairoOptions "foo.png" (PNG (100,100))) myDiagram
-
-instance Backend Cairo R2 where
-  data Render  Cairo R2 = C (RenderM ())
-  type Result  Cairo R2 = (IO (), C.Render ())
-  data Options Cairo R2 = CairoOptions
-          { fileName     :: String       -- ^ the name of the file you want generated
-          , outputFormat :: OutputFormat -- ^ the output format and associated options
-          }
-
-  withStyle _ s t (C r) = C $ do
-    save
-    cairoMiscStyle s
-    r
-    lift $ do
-      cairoTransf t
-      cairoStrokeStyle s
-      C.stroke
-    restore
-
-  doRender _ options (C r) = (renderIO, r')
-    where r' = evalStateT r ()
-          renderIO = do
-            let surfaceF s = C.renderWith s r'
-                file = fileName options
-            case outputFormat options of
-              GTK win _ _ -> CG.renderWithDrawable win r'
-              PNG (w,h) ->
-                C.withImageSurface C.FormatARGB32 w h $ \surface -> do
-                  surfaceF surface
-                  C.surfaceWriteToPNG surface file
-              PS  (w,h) -> C.withPSSurface  file w h surfaceF
-              PDF (w,h) -> C.withPDFSurface file w h surfaceF
-              SVG (w,h) -> C.withSVGSurface file w h surfaceF
-
-  adjustDia c opts d = if bypass (outputFormat opts)
-                         then d
-                         else adjustDia2D (getSize . outputFormat) c opts (d # reflectY)
-    where getSize (GTK _ (Just (pw,ph)) _) = (fromIntegral pw, fromIntegral ph)
-          getSize (GTK _ Nothing _)        = size2D d
-          getSize (PNG (pw,ph)) = (fromIntegral pw, fromIntegral ph)
-          getSize (PS  sz) = sz
-          getSize (PDF sz) = sz
-          getSize (SVG sz) = sz
-          bypass  (GTK _ _ x)   = x
-          bypass  _             = False
-
-renderC :: (Renderable a Cairo, V a ~ R2) => a -> RenderM ()
-renderC a = case (render Cairo a) of C r -> r
-
-cairoMiscStyle :: Style v -> RenderM ()
-cairoMiscStyle s =
-  sequence_
-  . catMaybes $ [ handle clip
-                , handle fSize
-                , handleFontFace
-                , handle fColor
-                , handle lFillRule
-                ]
-  where handle :: AttributeClass a => (a -> RenderM ()) -> Maybe (RenderM ())
-        handle f = f `fmap` getAttr s
-        clip     = mapM_ (\p -> renderC p >> lift C.clip) . getClip
-        fSize    = lift . C.setFontSize . getFontSize
-        fFace    = fromMaybe "" $ getFont <$> getAttr s
-        fSlant   = fromFontSlant  . fromMaybe FontSlantNormal
-                 $ getFontSlant  <$> getAttr s
-        fWeight  = fromFontWeight . fromMaybe FontWeightNormal
-                 $ getFontWeight <$> getAttr s
-        handleFontFace = Just . lift $ C.selectFontFace fFace fSlant fWeight
-        fColor c = lift $ setSource (getFillColor c) s
-        lFillRule = lift . C.setFillRule . fromFillRule . getFillRule
-
-fromFontSlant :: FontSlant -> C.FontSlant
-fromFontSlant FontSlantNormal   = C.FontSlantNormal
-fromFontSlant FontSlantItalic   = C.FontSlantItalic
-fromFontSlant FontSlantOblique  = C.FontSlantOblique
-
-fromFontWeight :: FontWeight -> C.FontWeight
-fromFontWeight FontWeightNormal = C.FontWeightNormal
-fromFontWeight FontWeightBold   = C.FontWeightBold
-
-cairoStrokeStyle :: Style v -> C.Render ()
-cairoStrokeStyle s =
-  sequence_
-  . catMaybes $ [ handle fColor
-                , handle lColor  -- see Note [color order]
-                , handle lWidth
-                , handle lCap
-                , handle lJoin
-                , handle lDashing
-                ]
-  where handle :: (AttributeClass a) => (a -> C.Render ()) -> Maybe (C.Render ())
-        handle f = f `fmap` getAttr s
-        fColor c = setSource (getFillColor c) s >> C.fillPreserve
-        lColor c = setSource (getLineColor c) s
-        lWidth   = C.setLineWidth . getLineWidth
-        lCap     = C.setLineCap . fromLineCap . getLineCap
-        lJoin    = C.setLineJoin . fromLineJoin . getLineJoin
-        lDashing (getDashing -> Dashing ds offs) =
-          C.setDash ds offs
-
-setSource :: Color c => c -> Style v -> C.Render ()
-setSource c s = C.setSourceRGBA r g b a'
-  where (r,g,b,a) = colorToRGBA c
-        a'        = case getOpacity <$> getAttr s of
-                      Nothing -> a
-                      Just d  -> a * d
-
-
-cairoTransf :: T2 -> C.Render ()
-cairoTransf t = C.transform m
-  where m = CM.Matrix a1 a2 b1 b2 c1 c2
-        (a1,a2) = apply t (1,0)
-        (b1,b2) = apply t (0,1)
-        (c1,c2) = transl t
-
-{- ~~~~ Note [color order]
-
-   It's important for the line and fill colors to be handled in the
-   given order (fill color first, then line color) because of the way
-   Cairo handles them (both are taken from the sourceRGBA).
--}
-
-fromLineCap :: LineCap -> C.LineCap
-fromLineCap LineCapButt   = C.LineCapButt
-fromLineCap LineCapRound  = C.LineCapRound
-fromLineCap LineCapSquare = C.LineCapSquare
-
-fromLineJoin :: LineJoin -> C.LineJoin
-fromLineJoin LineJoinMiter = C.LineJoinMiter
-fromLineJoin LineJoinRound = C.LineJoinRound
-fromLineJoin LineJoinBevel = C.LineJoinBevel
-
-fromFillRule :: FillRule -> C.FillRule
-fromFillRule Winding = C.FillRuleWinding
-fromFillRule EvenOdd = C.FillRuleEvenOdd
-
-instance Renderable Ellipse Cairo where
-  render _ ell = C . lift $ do
-    let P (xc,yc) = ellipseCenter ell
-        (xs,ys)   = ellipseScale ell
-        Rad th    = ellipseAngle ell
-    C.newPath
-    C.save
-    C.translate xc yc
-    C.rotate th
-    C.scale xs ys
-    C.arc 0 0 1 0 tau
-    C.closePath
-    C.restore
-
-instance Renderable (Segment R2) Cairo where
-  render _ (Linear v) = C . lift $ uncurry C.relLineTo v
-  render _ (Cubic (x1,y1) (x2,y2) (x3,y3)) = C . lift $ C.relCurveTo x1 y1 x2 y2 x3 y3
-
-instance Renderable (Trail R2) Cairo where
-  render _ (Trail segs c) = C $ do
-    mapM_ renderC segs
-    lift $ when c C.closePath
-
-instance Renderable (Path R2) Cairo where
-  render _ (Path trs) = C $ lift C.newPath >> F.mapM_ renderTrail trs
-    where renderTrail (P p, tr) = do
-            lift $ uncurry C.moveTo p
-            renderC tr
-
-
--- Can only do PNG files at the moment...
-instance Renderable Image Cairo where
-  render _ (Image file sz tr) = C . lift $ do
-    if ".png" `isSuffixOf` file
-      then do
-        C.save
-        cairoTransf (tr <> reflectionY)
-        pngSurf <- liftIO $ C.imageSurfaceCreateFromPNG file
-        w <- C.imageSurfaceGetWidth pngSurf
-        h <- C.imageSurfaceGetHeight pngSurf
-        cairoTransf $ adjustSize sz (fromIntegral w, fromIntegral h)
-        C.setSourceSurface pngSurf (-fromIntegral w / 2)
-                                   (-fromIntegral h / 2)
-        C.paint
-        C.restore
-      else
-        liftIO . putStr . unlines $
-          [ "Warning: Cairo backend can currently only render embedded"
-          , "  images in .png format.  Ignoring <" ++ file ++ ">."
-          ]
-
--- see http://www.cairographics.org/tutorial/#L1understandingtext
-instance Renderable Text Cairo where
-  render _ (Text tr str) = C $ do
-    lift $ do
-      C.save
-      -- XXX should use reflection font matrix here instead?
-      cairoTransf (tr <> reflectionY)
-      tExt <- C.textExtents str
-      let w    = C.textExtentsWidth tExt
-          h    = C.textExtentsHeight tExt
-          refX = -w/2 - C.textExtentsXbearing tExt
-          refY = -h/2 - C.textExtentsYbearing tExt
-          P (newX, newY) = origin
-      cairoTransf (moveOriginBy (-newX - refX, newY - refY) mempty)
-      C.showText str
-      C.restore
-
-{-
--- | A @TextRect@ is a utility object which acts like a rectangle for
---   the purposes of its bounding function and transformations, but
---   also accumulates any transformations performed on it in a
---   separate field.  This is useful for learning what transformation
---   we should do to some text in order to align it as requested,
---   since for the purposes of alignment we think of the text as
---   occupying a rectangular region.
-data TextRect = TextRect (Bounds R2) T2
-
-mkTextRect :: Double -> Double -> TextRect
-mkTextRect w h = TextRect (bounds (rect w h :: Diagram Cairo R2)) mempty
-
-type instance V TextRect = R2
-
-instance Boundable TextRect where
-  getBounds (TextRect b _) = b
-
-instance HasOrigin TextRect where
-  moveOriginTo p (TextRect b t) = TextRect (moveOriginTo p b)
-                                           (moveOriginTo p t)
-
-instance Transformable TextRect where
-  transform t1 (TextRect b t2) = TextRect (transform t1 b) (t1 <> t2)
--}
+-- > renderDia Cairo (CairoOptions "foo.png" (Width 250) PNG) myDiagram
+--
+-- /Important note/: a bug in GHC 7.0.x prevents re-exporting this
+-- data family.  To bring CairoOptions into scope when using GHC 7.0.x
+-- you must import "Diagrams.Backend.Cairo.Internal".
diff --git a/src/Diagrams/Backend/Cairo/CmdLine.hs b/src/Diagrams/Backend/Cairo/CmdLine.hs
--- a/src/Diagrams/Backend/Cairo/CmdLine.hs
+++ b/src/Diagrams/Backend/Cairo/CmdLine.hs
@@ -14,24 +14,33 @@
 module Diagrams.Backend.Cairo.CmdLine
        ( defaultMain
        , multiMain
+       , animMain
 
        , Cairo
        ) where
 
-import Diagrams.Prelude hiding (width, height)
+import Diagrams.Prelude hiding (width, height, interval)
 import Diagrams.Backend.Cairo
 
+-- Below hack is needed because GHC 7.0.x has a bug regarding export
+-- of data family constructors; see comments in Diagrams.Backend.Cairo
+#if __GLASGOW_HASKELL__ < 702 || __GLASGOW_HASKELL__ >= 704
+import Diagrams.Backend.Cairo.Internal
+#endif
+
 import System.Console.CmdArgs.Implicit hiding (args)
 
 import Prelude hiding      (catch)
 
 import Data.Maybe          (fromMaybe)
-import Control.Applicative ((<$>))
-import Control.Monad       (when)
+import Control.Monad       (when, forM_)
 import Data.List.Split
 
+import Text.Printf
+
 import System.Environment  (getArgs, getProgName)
 import System.Directory    (getModificationTime)
+import System.FilePath     (addExtension, splitExtension)
 import System.Process      (runProcess, waitForProcess)
 import System.IO           (openFile, hClose, IOMode(..),
                             hSetBuffering, BufferMode(..), stdout)
@@ -45,10 +54,11 @@
 #endif
 
 data DiagramOpts = DiagramOpts
-                   { width     :: Int
-                   , height    :: Int
+                   { width     :: Maybe Int
+                   , height    :: Maybe Int
                    , output    :: FilePath
                    , selection :: Maybe String
+                   , fpu       :: Double
 #ifdef CMDLINELOOP
                    , loop      :: Bool
                    , src       :: Maybe String
@@ -59,13 +69,13 @@
 
 diagramOpts :: String -> Bool -> DiagramOpts
 diagramOpts prog sel = DiagramOpts
-  { width =  400
+  { width =  def
              &= typ "INT"
-             &= help "Desired width of the output image (default 400)"
+             &= help "Desired width of the output image"
 
-  , height = 400
-              &= typ "INT"
-              &= help "Desired height of the output image (default 400)"
+  , height = def
+             &= typ "INT"
+             &= help "Desired height of the output image"
 
   , output = def
            &= typFile
@@ -74,6 +84,10 @@
   , selection = def
               &= help "Name of the diagram to render"
               &= (if sel then typ "NAME" else ignore)
+
+  , fpu = 30
+          &= typ "FLOAT"
+          &= help "Frames per unit time (for animations)"
 #ifdef CMDLINELOOP
   , loop = False
             &= help "Run in a self-recompiling loop"
@@ -120,13 +134,24 @@
   case splitOn "." (output opts) of
     [""] -> putStrLn "No output file given."
     ps | last ps `elem` ["png", "ps", "pdf", "svg"] -> do
-           let outfmt = case last ps of
-                 "png" -> PNG (width opts, height opts)
-                 "ps"  -> PS  (fromIntegral $ width opts, fromIntegral $ height opts)
-                 "pdf" -> PDF (fromIntegral $ width opts, fromIntegral $ height opts)
-                 "svg" -> SVG (fromIntegral $ width opts, fromIntegral $ height opts)
-                 _     -> PDF (fromIntegral $ width opts, fromIntegral $ height opts)
-           fst $ renderDia Cairo (CairoOptions (output opts) outfmt) d
+           let outTy = case last ps of
+                 "png" -> PNG
+                 "ps"  -> PS
+                 "pdf" -> PDF
+                 "svg" -> SVG
+                 _     -> PDF
+               sizeSpec = case (width opts, height opts) of
+                            (Nothing, Nothing) -> Absolute
+                            (Just w, Nothing)  -> Width (fromIntegral w)
+                            (Nothing, Just h)  -> Height (fromIntegral h)
+                            (Just w, Just h)   -> Dims (fromIntegral w)
+                                                       (fromIntegral h)
+           fst $ renderDia Cairo (CairoOptions
+                                    (output opts)
+                                    sizeSpec
+                                    outTy
+                                 )
+                           d
        | otherwise -> putStrLn $ "Unknown file type: " ++ last ps
 
 -- | @multiMain@ is like 'defaultMain', except instead of a single
@@ -145,6 +170,36 @@
     Just sel -> case lookup sel ds of
       Nothing -> putStrLn $ "Unknown diagram: " ++ sel
       Just d  -> chooseRender opts d
+
+-- | @animMain@ takes an animation and produces a command-line program
+--   which will crudely \"render\" the animation by rendering one image
+--   for each frame, named by extending the given output file name by
+--   consecutive integers.  For example if the given output file name
+--   is @foo\/blah.png@, the frames will be saved in @foo\/blah001.png@,
+--   @foo\/blah002.png@, and so on (the number of padding digits used
+--   depends on the total number of frames).  It is up to the user to
+--   take these images and stitch them together into an actual
+--   animation format (using, /e.g./ @ffmpeg@).
+--
+--   Of course, this is a rather crude method of rendering animations;
+--   more sophisticated methods will likely be added in the future.
+animMain :: Animation Cairo R2 -> IO ()
+animMain anim = do
+  prog <- getProgName
+  opts <- cmdArgs (diagramOpts prog False)
+  let frames  = simulate (toRational $ fpu opts) anim
+      nDigits = length . show . length $ frames
+  forM_ (zip [1..] frames) $ \(i,d) ->
+    chooseRender (indexize nDigits i opts) d
+
+-- | @indexize d n@ adds the integer index @n@ to the end of the
+--   output file name, padding with zeros if necessary so that it uses
+--   at least @d@ digits.
+indexize :: Int -> Integer -> DiagramOpts -> DiagramOpts
+indexize nDigits i opts = opts { output = output' }
+  where fmt         = "%0" ++ show nDigits ++ "d"
+        output'     = addExtension (base ++ printf fmt (i::Integer)) ext
+        (base, ext) = splitExtension (output opts)
 
 #ifdef CMDLINELOOP
 waitForChange :: Maybe ClockTime -> DiagramOpts -> String -> [String] -> IO ()
diff --git a/src/Diagrams/Backend/Cairo/Gtk.hs b/src/Diagrams/Backend/Cairo/Gtk.hs
--- a/src/Diagrams/Backend/Cairo/Gtk.hs
+++ b/src/Diagrams/Backend/Cairo/Gtk.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Diagrams.Backend.Cairo.Gtk
@@ -19,42 +20,51 @@
 import Diagrams.Prelude hiding (width, height)
 import Diagrams.Backend.Cairo
 
+-- Below hack is needed because GHC 7.0.x has a bug regarding export
+-- of data family constructors; see comments in Diagrams.Backend.Cairo
+#if __GLASGOW_HASKELL__ < 702 || __GLASGOW_HASKELL__ >= 704
+import Diagrams.Backend.Cairo.Internal
+#endif
+
 import Graphics.UI.Gtk
 
 -- | Convert a Diagram to the backend coordinates.
--- 
+--
 -- Provided to Query the diagram with coordinates from a mouse click
 -- event.
--- 
+--
 -- > widget `on` buttonPressEvent $ tryEvent $ do
 -- >   click <- eventClick
 -- >   (x,y) <- eventCoordinates
 -- >   let result = runQuery (query $ toGtkCoords myDiagram) (P (x,y))
 -- >   do_something_with result
--- 
+--
 -- `toGtkCoords` does no rescaling of the diagram, however it is centered in
 -- the window.
-toGtkCoords :: Monoid m => AnnDiagram Cairo R2 m -> AnnDiagram Cairo R2 m
-toGtkCoords d =
+toGtkCoords :: Monoid' m => QDiagram Cairo R2 m -> QDiagram Cairo R2 m
+toGtkCoords d = snd $
   adjustDia Cairo
-            (CairoOptions "" (GTK (undefined :: DrawWindow) Nothing False))
+            (CairoOptions "" Absolute (GTK (undefined :: DrawWindow) False))
             d
 
 -- | Render a diagram to a DrawingArea, rescaling to fit the full area.
-defaultRender :: Monoid m => DrawingArea -> AnnDiagram Cairo R2 m -> IO ()
+defaultRender :: Monoid' m => DrawingArea -> QDiagram Cairo R2 m -> IO ()
 defaultRender da d = do
-  sz <- widgetGetSize da
+  (w,h) <- widgetGetSize da
   dw <- widgetGetDrawWindow da
-  fst $ renderDia Cairo (CairoOptions "" (GTK dw (Just sz) False)) d
+  fst $ renderDia Cairo (CairoOptions "" (Dims (fromIntegral w) (fromIntegral h))
+                                         (GTK dw False)
+                        )
+        d
 
 -- | Render a diagram to a DrawableClass.  No rescaling or transformations
 -- will be performed.
--- 
+--
 -- Typically the diagram will already have been transformed by `toGtkCoords`.
 renderToGtk ::
-  (DrawableClass dc, Monoid m)
+  (DrawableClass dc, Monoid' m)
   => dc                     -- ^ widget to render onto
-  -> AnnDiagram Cairo R2 m  -- ^ Diagram
+  -> QDiagram Cairo R2 m  -- ^ Diagram
   -> IO ()
 renderToGtk dc d =
-  fst $ renderDia Cairo (CairoOptions "" (GTK dc Nothing True)) d
+  fst $ renderDia Cairo (CairoOptions "" Absolute (GTK dc True)) d
diff --git a/src/Diagrams/Backend/Cairo/Internal.hs b/src/Diagrams/Backend/Cairo/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Diagrams/Backend/Cairo/Internal.hs
@@ -0,0 +1,296 @@
+{-# LANGUAGE TypeFamilies
+           , MultiParamTypeClasses
+           , FlexibleInstances
+           , FlexibleContexts
+           , ExistentialQuantification
+           , TypeSynonymInstances
+           , DeriveDataTypeable
+           , ViewPatterns
+  #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Diagrams.Backend.Cairo.Internal
+-- Copyright   :  (c) 2011 Diagrams-cairo team (see LICENSE)
+-- License     :  BSD-style (see LICENSE)
+-- Maintainer  :  diagrams-discuss@googlegroups.com
+--
+-- Implementation of the diagrams Cairo backend.  This module exports the
+-- internal helper functions, for usage elsewhere.
+--
+-----------------------------------------------------------------------------
+module Diagrams.Backend.Cairo.Internal where
+
+import Graphics.Rendering.Diagrams.Transform
+
+import Diagrams.Prelude
+import Diagrams.TwoD.Path (Clip(..), getFillRule)
+import Diagrams.TwoD.Text
+import Diagrams.TwoD.Image
+import Diagrams.TwoD.Adjust (adjustDia2D, adjustSize)
+
+import Graphics.UI.Gtk (DrawableClass)
+import qualified Graphics.UI.Gtk.Cairo as CG
+
+import qualified Graphics.Rendering.Cairo as C
+import qualified Graphics.Rendering.Cairo.Matrix as CM
+
+import Control.Monad.State
+import Data.Maybe (catMaybes, fromMaybe)
+import Data.List (isSuffixOf)
+
+import Control.Exception (try)
+
+import qualified Data.Foldable as F
+
+import Data.Typeable
+
+-- | This data declaration is simply used as a token to distinguish
+--   this rendering engine.
+data Cairo = Cairo
+  deriving (Eq,Ord,Read,Show,Typeable)
+
+-- | Output types supported by cairo, including four different file
+--   types (PNG, PS, PDF, SVG) as well as Gtk windows.
+data OutputType =
+    -- | Output directly to a Gtk window.  See "Diagrams.Backends.Cairo.Gtk".
+    forall dw. (DrawableClass dw) =>
+    GTK { gtkWindow :: dw
+          -- ^ The window on which to draw.
+
+        , gtkBypass :: Bool
+          -- ^ Should the 'adjustDia' step be bypassed during rendering?
+        }
+
+  | PNG      -- ^ Portable Network Graphics output.
+  | PS       -- ^ PostScript output
+  | PDF      -- ^ Portable Document Format output.
+  | SVG      -- ^ Scalable Vector Graphics output.
+
+instance Monoid (Render Cairo R2) where
+  mempty  = C $ return ()
+  (C rd1) `mappend` (C rd2) = C (rd1 >> rd2)
+
+type RenderM a = StateT () C.Render a  -- no state for now
+
+-- simple, stupid implementations of save and restore for now, since
+-- it suffices to just reset the text alignment to "centered" on
+-- restore.  But if need be we can switch to a more sophisticated
+-- implementation using an "undoable state" monad which lets you save
+-- (push state onto a stack) and restore (pop from the stack).
+
+save :: RenderM ()
+save = lift C.save
+
+restore :: RenderM ()
+restore = lift C.restore
+
+instance Backend Cairo R2 where
+  data Render  Cairo R2 = C (RenderM ())
+  type Result  Cairo R2 = (IO (), C.Render ())
+  data Options Cairo R2 = CairoOptions
+          { cairoFileName   :: String     -- ^ The name of the file you want generated
+          , cairoSizeSpec   :: SizeSpec2D -- ^ The requested size of the output
+          , cairoOutputType :: OutputType -- ^ the output format and associated options
+          }
+
+  withStyle _ s t (C r) = C $ do
+    save
+    cairoMiscStyle s
+    r
+    lift $ do
+      cairoTransf t
+      cairoStrokeStyle s
+      C.stroke
+    restore
+
+  doRender _ (CairoOptions file size out) (C r) = (renderIO, r')
+    where r' = evalStateT r ()
+          renderIO = do
+            let surfaceF s = C.renderWith s r'
+
+                -- Everything except Dims is arbitrary. The backend
+                -- should have first run 'adjustDia' to update the
+                -- final size of the diagram with explicit dimensions,
+                -- so normally we would only expect to get Dims anyway.
+                (w,h) = case size of
+                          Width w'   -> (w',w')
+                          Height h'  -> (h',h')
+                          Dims w' h' -> (w',h')
+                          Absolute   -> (100,100)
+
+            case out of
+              GTK win _ -> CG.renderWithDrawable win r'
+              PNG ->
+                C.withImageSurface C.FormatARGB32 (round w) (round h) $ \surface -> do
+                  surfaceF surface
+                  C.surfaceWriteToPNG surface file
+              PS  -> C.withPSSurface  file w h surfaceF
+              PDF -> C.withPDFSurface file w h surfaceF
+              SVG -> C.withSVGSurface file w h surfaceF
+
+  adjustDia c opts d = if bypass (cairoOutputType opts)
+                         then (opts,d)
+                         else adjustDia2D cairoSizeSpec
+                                          setCairoSizeSpec
+                                          c opts (d # reflectY)
+    where setCairoSizeSpec sz o = o { cairoSizeSpec = sz }
+          bypass  (GTK _ x) = x
+          bypass  _         = False
+
+renderC :: (Renderable a Cairo, V a ~ R2) => a -> RenderM ()
+renderC a = case (render Cairo a) of C r -> r
+
+cairoMiscStyle :: Style v -> RenderM ()
+cairoMiscStyle s =
+  sequence_
+  . catMaybes $ [ handle clip
+                , handle fSize
+                , handleFontFace
+                , handle fColor
+                , handle lFillRule
+                ]
+  where handle :: AttributeClass a => (a -> RenderM ()) -> Maybe (RenderM ())
+        handle f = f `fmap` getAttr s
+        clip     = mapM_ (\p -> renderC p >> lift C.clip) . getClip
+        fSize    = lift . C.setFontSize . getFontSize
+        fFace    = fromMaybe "" $ getFont <$> getAttr s
+        fSlant   = fromFontSlant  . fromMaybe FontSlantNormal
+                 $ getFontSlant  <$> getAttr s
+        fWeight  = fromFontWeight . fromMaybe FontWeightNormal
+                 $ getFontWeight <$> getAttr s
+        handleFontFace = Just . lift $ C.selectFontFace fFace fSlant fWeight
+        fColor c = lift $ setSource (getFillColor c) s
+        lFillRule = lift . C.setFillRule . fromFillRule . getFillRule
+
+fromFontSlant :: FontSlant -> C.FontSlant
+fromFontSlant FontSlantNormal   = C.FontSlantNormal
+fromFontSlant FontSlantItalic   = C.FontSlantItalic
+fromFontSlant FontSlantOblique  = C.FontSlantOblique
+
+fromFontWeight :: FontWeight -> C.FontWeight
+fromFontWeight FontWeightNormal = C.FontWeightNormal
+fromFontWeight FontWeightBold   = C.FontWeightBold
+
+cairoStrokeStyle :: Style v -> C.Render ()
+cairoStrokeStyle s =
+  sequence_
+  . catMaybes $ [ handle fColor
+                , handle lColor  -- see Note [color order]
+                , handle lWidth
+                , handle lCap
+                , handle lJoin
+                , handle lDashing
+                ]
+  where handle :: (AttributeClass a) => (a -> C.Render ()) -> Maybe (C.Render ())
+        handle f = f `fmap` getAttr s
+        fColor c = setSource (getFillColor c) s >> C.fillPreserve
+        lColor c = setSource (getLineColor c) s
+        lWidth   = C.setLineWidth . getLineWidth
+        lCap     = C.setLineCap . fromLineCap . getLineCap
+        lJoin    = C.setLineJoin . fromLineJoin . getLineJoin
+        lDashing (getDashing -> Dashing ds offs) =
+          C.setDash ds offs
+
+setSource :: Color c => c -> Style v -> C.Render ()
+setSource c s = C.setSourceRGBA r g b a'
+  where (r,g,b,a) = colorToRGBA c
+        a'        = case getOpacity <$> getAttr s of
+                      Nothing -> a
+                      Just d  -> a * d
+
+
+cairoTransf :: T2 -> C.Render ()
+cairoTransf t = C.transform m
+  where m = CM.Matrix a1 a2 b1 b2 c1 c2
+        (unr2 -> (a1,a2)) = apply t unitX
+        (unr2 -> (b1,b2)) = apply t unitY
+        (unr2 -> (c1,c2)) = transl t
+
+{- ~~~~ Note [color order]
+
+   It's important for the line and fill colors to be handled in the
+   given order (fill color first, then line color) because of the way
+   Cairo handles them (both are taken from the sourceRGBA).
+-}
+
+fromLineCap :: LineCap -> C.LineCap
+fromLineCap LineCapButt   = C.LineCapButt
+fromLineCap LineCapRound  = C.LineCapRound
+fromLineCap LineCapSquare = C.LineCapSquare
+
+fromLineJoin :: LineJoin -> C.LineJoin
+fromLineJoin LineJoinMiter = C.LineJoinMiter
+fromLineJoin LineJoinRound = C.LineJoinRound
+fromLineJoin LineJoinBevel = C.LineJoinBevel
+
+fromFillRule :: FillRule -> C.FillRule
+fromFillRule Winding = C.FillRuleWinding
+fromFillRule EvenOdd = C.FillRuleEvenOdd
+
+instance Renderable (Segment R2) Cairo where
+  render _ (Linear v) = C . lift $ uncurry C.relLineTo (unr2 v)
+  render _ (Cubic (unr2 -> (x1,y1))
+                  (unr2 -> (x2,y2))
+                  (unr2 -> (x3,y3)))
+    = C . lift $ C.relCurveTo x1 y1 x2 y2 x3 y3
+
+instance Renderable (Trail R2) Cairo where
+  render _ (Trail segs c) = C $ do
+    mapM_ renderC segs
+    lift $ when c C.closePath
+
+instance Renderable (Path R2) Cairo where
+  render _ (Path trs) = C $ lift C.newPath >> F.mapM_ renderTrail trs
+    where renderTrail (unp2 -> p, tr) = do
+            lift $ uncurry C.moveTo p
+            renderC tr
+
+
+-- Can only do PNG files at the moment...
+instance Renderable Image Cairo where
+  render _ (Image file sz tr) = C . lift $ do
+    if ".png" `isSuffixOf` file
+      then do
+        C.save
+        cairoTransf (tr <> reflectionY)
+        pngSurfChk <- liftIO (try $ C.imageSurfaceCreateFromPNG file
+                              :: IO (Either IOError C.Surface))
+        case pngSurfChk of
+          Right pngSurf -> do
+            w <- C.imageSurfaceGetWidth pngSurf
+            h <- C.imageSurfaceGetHeight pngSurf
+            cairoTransf $ adjustSize sz (fromIntegral w, fromIntegral h)
+            C.setSourceSurface pngSurf (-fromIntegral w / 2)
+                                       (-fromIntegral h / 2)
+          Left _ ->
+            liftIO . putStrLn $
+              "Warning: can't read image file <" ++ file ++ ">"
+        C.paint
+        C.restore
+      else
+        liftIO . putStr . unlines $
+          [ "Warning: Cairo backend can currently only render embedded"
+          , "  images in .png format.  Ignoring <" ++ file ++ ">."
+          ]
+
+-- see http://www.cairographics.org/tutorial/#L1understandingtext
+instance Renderable Text Cairo where
+  render _ (Text tr al str) = C $ do
+    lift $ do
+      C.save
+      -- XXX should use reflection font matrix here instead?
+      cairoTransf (tr <> reflectionY)
+      (refX, refY) <- case al of
+        BoxAlignedText xt yt -> do
+          tExt <- C.textExtents str
+          fExt <- C.fontExtents
+          let l = C.textExtentsXbearing tExt
+              r = C.textExtentsXadvance tExt
+              b = C.fontExtentsDescent  fExt
+              t = C.fontExtentsAscent   fExt
+          return (lerp l r xt, lerp (-b) t yt)
+        BaselineText -> return (0, 0)
+      cairoTransf (moveOriginBy (r2 (refX, -refY)) mempty)
+      C.showText str
+      C.restore
diff --git a/src/Diagrams/Backend/Cairo/Text.hs b/src/Diagrams/Backend/Cairo/Text.hs
new file mode 100644
--- /dev/null
+++ b/src/Diagrams/Backend/Cairo/Text.hs
@@ -0,0 +1,153 @@
+{-# LANGUAGE RankNTypes #-}
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Diagrams.Backend.Cairo.Text
+-- Copyright   :  (c) 2011 Diagrams-cairo team (see LICENSE)
+-- License     :  BSD-style (see LICENSE)
+-- Maintainer  :  diagrams-discuss@googlegroups.com
+--
+-- This module provides convenience functions for querying information from
+-- Cairo.  In particular, this provides utilities for information about fonts,
+-- and creating text primitives with bounds based on the font being used.
+--
+-----------------------------------------------------------------------------
+module Diagrams.Backend.Cairo.Text
+  (
+    -- * Cairo Utilities
+    queryCairo, unsafeCairo
+  , StyleParam, cairoWithStyle
+
+    -- * Extents
+
+    -- ** Data Structures
+
+  , TextExtents(..), FontExtents(..)
+
+    -- ** Queries
+
+  , getTextExtents, getFontExtents, getExtents
+  , kerningCorrectionIO
+
+    -- * Primitives
+
+    -- | These create diagrams instantiated with extents-based envelopes
+  , textLineBoundedIO, textVisualBoundedIO
+
+    -- * Unsafe
+
+    -- | These are convenient unsafe variants of the above operations
+    --   postfixed with \"IO\". They should be pretty well-behaved as the
+    --   results just depend on the parameters and the font information
+    --   (which ought to stay the same during a given execution).
+
+  , kerningCorrection, textLineBounded, textVisualBounded
+  ) where
+
+import Diagrams.Backend.Cairo.Internal
+import Diagrams.Prelude
+
+import Control.Monad.State
+import System.IO.Unsafe
+
+import qualified Graphics.Rendering.Cairo as C
+
+-- | Executes a cairo action on a dummy, zero-size image surface, in order to
+--   query things like font information.
+queryCairo :: C.Render a -> IO a
+queryCairo c = C.withImageSurface C.FormatA1 0 0 (`C.renderWith` c)
+
+-- | Unsafely invokes @queryCairo@.
+unsafeCairo :: C.Render a -> a
+unsafeCairo = unsafePerformIO . queryCairo
+
+-- | Existential type for mutations on objects that \"have style\". This is
+--   used as a parameter to @getTextExtents@ and @getFontExtents@ in order to
+--   set font-size and font-face.
+type StyleParam = forall a. HasStyle a => a -> a
+
+-- | Executes the given cairo action, with styling applied.
+--   This does not do all styling - just attributes that are processed by
+--   \"cairoMiscStyle\", which does clip, fill color, fill rule, and,
+--   importantly for this module, font face, style, and weight.
+cairoWithStyle :: C.Render a -> StyleParam -> C.Render a
+cairoWithStyle f style = do
+  C.save
+  evalStateT (cairoMiscStyle (style mempty)) ()
+  result <- f
+  C.restore
+  return result
+
+-- | A more convenient data structure for the results of a text-extents query.
+data TextExtents = TextExtents
+  { bearing, textSize, advance :: R2 }
+
+processTextExtents :: C.TextExtents -> TextExtents
+processTextExtents (C.TextExtents  xb yb  w h  xa ya)
+                    = TextExtents (r2 (xb,yb)) (r2 (w,h)) (r2 (xa,ya))
+
+-- | Get the extents of a string of text, given a style to render it with.
+getTextExtents :: StyleParam -> String -> C.Render TextExtents
+getTextExtents style txt
+  = cairoWithStyle (processTextExtents <$> C.textExtents txt) style
+
+-- | A more convenient data structure for the results of a font-extents query.
+data FontExtents = FontExtents
+  { ascent, descent, height :: Double
+  , maxAdvance :: R2
+  }
+
+processFontExtents :: C.FontExtents -> FontExtents
+processFontExtents (C.FontExtents a d h  mx my)
+                    = FontExtents a d h (r2 (mx,my))
+
+-- | Gets the intrinsic extents of a font.
+getFontExtents :: StyleParam -> C.Render FontExtents
+getFontExtents style
+  = cairoWithStyle (processFontExtents <$> C.fontExtents) style
+
+-- | Gets both the "FontExtents" and "TextExtents" of the string with the a
+--   particular style applied.  This is more efficient than calling both
+--   @getFontExtents@ and @getTextExtents@.
+getExtents :: StyleParam -> String -> C.Render (FontExtents, TextExtents)
+getExtents style str = cairoWithStyle (do
+    fe <- processFontExtents <$> C.fontExtents
+    te <- processTextExtents <$> C.textExtents str
+    return (fe, te)
+  ) style
+
+-- | Queries the amount of horizontal offset that needs to be applied in order to
+--   position the second character properly, in the event that it is @hcat@-ed
+--   @baselineText@.
+kerningCorrectionIO :: StyleParam -> Char -> Char -> IO Double
+kerningCorrectionIO style a b = do
+  let ax t = fst . unr2 . advance <$> queryCairo (getTextExtents style t)
+  l  <- ax [a, b]
+  la <- ax [a]
+  lb <- ax [b]
+  return $ l - la - lb
+
+-- | Creates text diagrams with their envelopes set such that using
+--   @vcat . map (textLineBounded style)@ stacks them in the way that
+--   the font designer intended.
+textLineBoundedIO :: StyleParam -> String -> IO (Diagram Cairo R2)
+textLineBoundedIO style str = do
+  (fe, te) <- queryCairo $ getExtents style str
+  let box = fromCorners (p2 (0,      negate $ descent fe))
+                        (p2 (fst . unr2 $ advance te, ascent fe))
+  return . setEnvelope (getEnvelope box) $ style (baselineText str)
+
+-- | Creates a text diagram with its envelope set to enclose the glyphs of the text,
+--   including leading (though not trailing) whitespace.
+textVisualBoundedIO :: StyleParam -> String -> IO (Diagram Cairo R2)
+textVisualBoundedIO style str = do
+  te <- queryCairo $ getTextExtents style str
+  let box = fromCorners (origin .+^ bearing te)
+                        (origin .+^ bearing te ^+^ (textSize te))
+  return . setEnvelope (getEnvelope box) $ style (baselineText str)
+
+kerningCorrection :: StyleParam -> Char -> Char -> Double
+kerningCorrection style a = unsafePerformIO . kerningCorrectionIO style a
+
+textLineBounded, textVisualBounded :: StyleParam -> String -> Diagram Cairo R2
+textLineBounded style   = unsafePerformIO . textLineBoundedIO   style
+textVisualBounded style = unsafePerformIO . textVisualBoundedIO style
