{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# OPTIONS -Wall #-}

--------------------------------------------------------------------------------
-- |
-- Module      :  Wumpus.Core.PictureInternal
-- Copyright   :  (c) Stephen Tetley 2009-2011
-- License     :  BSD3
--
-- Maintainer  :  stephen.tetley@gmail.com
-- Stability   :  unstable
-- Portability :  GHC
--
-- Internal representation of Pictures.
--
--------------------------------------------------------------------------------


module Wumpus.Core.PictureInternal
  ( 

    Picture(..)
  , Locale
  , FontCtx(..)

  , Primitive(..)
  , SvgAnno(..)
  , XLink(..)
  , SvgAttr(..)

  , PrimPath(..)
  , PrimPathSegment(..)
  , AbsPathSegment(..)
  , PrimLabel(..)
  , LabelBody(..)
  , KerningChar
  , PrimEllipse(..)

  , GraphicsState(..)

  , mapLocale

  -- * Additional operations
  , concatTrafos
  , deconsMatrix
  , repositionDeltas
  , extractRelPath

  , zeroGS
  , isEmptyPath
  , isEmptyLabel

  ) where

import Wumpus.Core.AffineTrans
import Wumpus.Core.BoundingBox
import Wumpus.Core.Colour
import Wumpus.Core.FontSize
import Wumpus.Core.Geometry
import Wumpus.Core.GraphicProps
import Wumpus.Core.Text.Base
import Wumpus.Core.TrafoInternal
import Wumpus.Core.Utils.FormatCombinators
import Wumpus.Core.Utils.JoinList


import Data.AffineSpace                         -- package: vector-space

import qualified Data.Foldable                  as F
import qualified Data.IntMap                    as IntMap


-- | Picture is a rose tree. Leaves themselves are attributed
-- with colour, line-width etc. The /unit/ of a Picture is 
-- fixed to Double representing PostScript\'s /Point/ unit. 
-- Output is always gewnerated with PostScript points - other
-- units are converted to PostScript points before building the 
-- Picture.
-- 
-- By attributing leaves with their drawing properties, Wumpus\'s 
-- picture representaion is not directly matched to PostScript.
-- PostScript has a global graphics state (that allows local 
-- modifaction) from where drawing properties are inherited.
-- Wumpus has no attribute inheritance.
--
-- Omitting some details of the list representation, Picture is a 
-- simple non-empty rose tree via:
-- 
-- > tree = Leaf [primitive] | Picture [tree]
--
data Picture = Leaf     Locale  (JoinList Primitive)
             | Picture  Locale  (JoinList Picture)
  deriving (Show)

type instance DUnit Picture = Double

-- | Locale = (bounding box * current translation matrix)
-- 
-- Pictures (and sub-pictures) are located frame consisting of a 
-- bounding box and a translation matrix (represented as a list 
-- of affine transformations). So that pictures can be arranged 
-- via vertical and horizontal composition their bounding box is 
-- cached.
--
-- In Wumpus, affine transformations (scalings, rotations...)
-- transform the CTM rather than the constituent points of 
-- the primitives. Changes of CTM are transmitted to PostScript
-- as @concat@ commands (and matrix transforms in SVG).
--  
-- So that picture composition is remains stable under affine
-- transformation, the corners of bounding boxes are transformed
-- pointwise when the picture is scaled, rotated etc.
--
type Locale = (BoundingBox Double, [AffineTrafo])



-- | Wumpus\'s drawings are built from two fundamental 
-- primitives: paths (straight line segments and Bezier curves) 
-- and labels (single lines of text). 
-- 
-- Ellipses are a included as a primitive only for optimization 
-- - drawing a reasonable circle with Bezier curves needs at 
-- least eight curves. This is inconvenient for drawing dots 
-- which can otherwise be drawn with a single @arc@ command.
-- 
-- Wumpus does not follow PostScript employing arc as a general 
-- path primitive - arcs are used only to draw ellipses. This 
-- is because arcs do not enjoy the nice properties of Bezier 
-- curves, whereby the affine transformation of a Bezier curve 
-- can simply be achieved by the affine transformation of it\'s 
-- control points.
--
-- Ellipses are represented by their center, half-width and 
-- half-height. Half-width and half-height are used so the 
-- bounding box can be calculated using only multiplication, and 
-- thus initially only obliging a Num constraint on the unit.
-- Though typically for affine transformations a Fractional 
-- constraint is also obliged.
--
-- Clipping is represented by a pair of the clipping path and
-- the primitive embedded within the path.
--
-- To represent XLink hyperlinks, Primitives can be annotated 
-- with some a hyperlink (likewise a /passive/ font change for 
-- better SVG code generation) and grouped - a hyperlinked arrow 
-- would want the tip and the arrow body both to be incorporated 
-- in thelink even though they are two drawing primitives. 
--
-- This means that Primitives aren\'t strictly /primitive/ as 
-- the actual implementation is a tree.
-- 
data Primitive = PPath    PathProps         PrimPath
               | PLabel   LabelProps        PrimLabel
               | PEllipse EllipseProps      PrimEllipse
               | PContext FontCtx           Primitive
               | PSVG     SvgAnno           Primitive
               | PGroup   (JoinList Primitive)
               | PClip    PrimPath          Primitive
  deriving (Eq,Show)

type instance DUnit Primitive = Double


-- | Set the font /delta/ for SVG rendering. 
-- 
-- Note - this does not change the default colour or font style. 
-- It is solely a backdoor into the SVG renderer to potential 
-- allow some code size reductions.
--
newtype FontCtx = FontCtx { getFontCtx :: FontAttr }
  deriving (Eq,Show)


-- | SVG annotations - annotations can be: 
-- 
-- A hyperlink inside @<a ...> ... </a>@ .
--
-- A group - @<g ...> ... </g>@
--
-- A group inside a hyperlink.
--
data SvgAnno = ALink XLink
             | GAnno [SvgAttr]
             | SvgAG XLink [SvgAttr]
   deriving (Eq,Show)

-- | Primitives can be grouped with hyperlinks in SVG output.
--
-- Note - this is always printed as @xlink:href="..."@. Other
-- types of xlink can be modelled with the unrestrained 
-- SvgAnno type.
--
newtype XLink = XLink { getXLink :: String }
  deriving (Eq,Show)


-- | Primitives can be labelled with arbitrary SVG properties 
-- (e.g @onmouseover@) within a group element.
--
-- Note - annotations should be used only for non-graphical 
-- properties. Graphical properties (fill_colour, font_size, etc.)
-- should be set through the appropriate Wumpus functions.
--
data SvgAttr = SvgAttr 
      { svg_attr_name   :: String
      , svg_attr_value  :: String 
      }
  deriving (Eq,Show)

-- | PrimPath - a list of path segments and a CTM.
-- 
-- Start point is the dx - dy of the CTM.
--
data PrimPath = PrimPath [PrimPathSegment] PrimCTM
  deriving (Eq,Show)

type instance DUnit PrimPath = Double


-- | PrimPathSegment - either a relative cubic Bezier /curve-to/ 
-- or a relative /line-to/.
--
data PrimPathSegment = RelCurveTo  DVec2 DVec2 DVec2
                     | RelLineTo   DVec2
  deriving (Eq,Show)

type instance DUnit PrimPathSegment = Double


-- | AbsPathSegment - either a cubic Bezier curve or a line.
-- 
-- Note this data type is transitory - it is only used as a 
-- convenience to build relative paths. Hence the unit type is 
-- parametric.
--
data AbsPathSegment = AbsCurveTo  DPoint2 DPoint2 DPoint2
                    | AbsLineTo   DPoint2 
  deriving (Eq,Show)


type instance DUnit AbsPathSegment = Double


-- | Label - represented by baseline-left point and text.
--
-- Baseline-left is the dx * dy of the PrimCTM.
--
--
data PrimLabel = PrimLabel 
      { label_body      :: LabelBody
      , label_ctm       :: PrimCTM
      }
  deriving (Eq,Show)

type instance DUnit PrimLabel = Double


-- | Label can be draw with 3 layouts.
-- 
-- The standard layout uses @show@ for PostScript and a single 
-- initial point for SVG.
--
-- Kerned horizontal layout - each character is encoded with the
-- rightwards horizontal distance from the last charcaters left 
-- base-line.
-- 
-- Kerned vertical layout - each character is encoded with the
-- upwards distance from the last charcaters left base-line.
-- 
data LabelBody = StdLayout EscapedText
               | KernTextH [KerningChar]
               | KernTextV [KerningChar]
  deriving (Eq,Show)

type instance DUnit LabelBody = Double


-- | A Char (possibly escaped) paired with its displacement from 
-- the previous KerningChar.
--
type KerningChar = (Double,EscapedChar) 


-- | Ellipse represented by center and half_width * half_height.
--
-- Center is the dx * dy of the PrimCTM.
--
data PrimEllipse = PrimEllipse 
      { ellipse_half_width    :: !Double
      , ellipse_half_height   :: !Double
      , ellipse_ctm           :: PrimCTM
      } 
  deriving (Eq,Show)

type instance DUnit PrimEllipse = Double

--
-- Design note - the CTM unit type is fixed to Double (PS point) 
-- rather than parametric on unit.
--
-- For the rationale see the PrimLabel design note.
-- 
 

--------------------------------------------------------------------------------
-- Graphics state datatypes

-- | Graphics state used by the rendering monads.
--
-- This type is hidden by the top-level module @Wumpus.Core@.
--
data GraphicsState = GraphicsState
      { gs_draw_colour  :: RGBi
      , gs_font_size    :: Int
      , gs_font_face    :: FontFace
      , gs_stroke_attr  :: StrokeAttr 
      }
  deriving (Eq,Show)


--------------------------------------------------------------------------------
-- instances

-- format

instance Format Picture where
  format (Leaf m prims)     = indent 2 $ vcat [ text "** Leaf-pic **"
                                              , fmtLocale m 
                                              , fmtPrimlist prims ]

  format (Picture m pics)   = indent 2 $ vcat [ text "** Tree-pic **"
                                              , fmtLocale m
                                              , fmtPics pics ]
 

fmtPics :: JoinList Picture -> Doc
fmtPics ones = snd $ F.foldl' fn (0,empty) ones
  where
    fn (n,acc) e = (n+1, vcat [ acc, text "-- " <+> int n, format e, line])


fmtLocale :: Locale -> Doc
fmtLocale (bb,_) = format bb


instance Format Primitive where
  format (PPath props p)    = 
      indent 2 $ vcat [ text "path:" <+> format props, format p ]

  format (PLabel props l)   =
      indent 2 $ vcat [ text "label:" <+> format props, format l ]

  format (PEllipse props e) = 
      indent 2 $ vcat [ text "ellipse:" <+> format props, format e ]

  format (PContext _ a)     = 
      vcat [ text "-- svg ctx change " , format a ]

  format (PSVG _ a)       = 
      vcat [ text "-- svg:", format  a ]

  format (PGroup ones)      = 
      vcat [ text "-- group ", fmtPrimlist ones  ]

  format (PClip path pic)  = 
      vcat [ text "-- clip-path ", format path, format pic  ]



fmtPrimlist :: JoinList Primitive -> Doc
fmtPrimlist ones = snd $ F.foldl' fn (0,empty) ones
  where
    fn (n,acc) e = (n+1, vcat [ acc, text "-- leaf" <+> int n, format e, line])


instance Format PrimPath where
   format (PrimPath vs ctm) = vcat [ hcat $ map format vs
                                   , text "ctm=" <> format ctm ]

instance Format PrimPathSegment where
  format (RelCurveTo p1 p2 p3)  =
    text "rel_curve_to " <> format p1 <+> format p2 <+> format p3

  format (RelLineTo pt)         = text "rel_line_to  " <> format pt

instance Format PrimLabel where
  format (PrimLabel s ctm) = 
     vcat [ dquotes (format s)
          , text "ctm="           <> format ctm
          ]

instance Format LabelBody where
  format (StdLayout enctext) = format enctext
  format (KernTextH xs)      = text "(KernH)" <+> hcat (map (format .snd) xs)
  format (KernTextV xs)      = text "(KernV)" <+> hcat (map (format .snd) xs)


instance Format PrimEllipse where
  format (PrimEllipse hw hh ctm) =  text "hw="       <> format hw
                                <+> text "hh="       <> format hh
                                <+> text "ctm="      <> format ctm
  

instance Format XLink where
  format (XLink ss) = text "xlink:href" <+> text ss


--------------------------------------------------------------------------------

instance Boundary Picture where
  boundary = boundaryPicture

boundaryPicture :: Picture -> BoundingBox Double
boundaryPicture (Leaf    (bb,_) _)   = bb
boundaryPicture (Picture (bb,_) _)   = bb


instance Boundary Primitive where
  boundary = boundaryPrimitive

boundaryPrimitive :: Primitive -> BoundingBox Double
boundaryPrimitive (PPath _ p)      = boundaryPrimPath p
boundaryPrimitive (PLabel a l)     = labelBoundary (label_font a) l
boundaryPrimitive (PEllipse _ e)   = ellipseBoundary e
boundaryPrimitive (PContext _ a)   = boundaryPrimitive a
boundaryPrimitive (PSVG _ a)       = boundaryPrimitive a
boundaryPrimitive (PClip p _)      = boundaryPrimPath p
boundaryPrimitive (PGroup ones)    = outer $ viewl ones 
  where
    outer (OneL a)     = boundaryPrimitive a
    outer (a :< as)    = inner (boundaryPrimitive a) (viewl as)

    inner bb (OneL a)  = bb `boundaryUnion` boundaryPrimitive a
    inner bb (a :< as) = inner (bb `boundaryUnion` boundaryPrimitive a) 
                               (viewl as)


instance Boundary PrimPath where
  boundary = boundaryPrimPath


boundaryPrimPath :: PrimPath -> BoundingBox Double
boundaryPrimPath (PrimPath vs ctm) = 
    retraceBoundary (m33 *#) $ step zeroPt (zeroPt,zeroPt) vs
  where
    m33         = matrixRepCTM ctm

    step _  (lo,hi) []                         = BBox lo hi 

    step pt (lo,hi) (RelLineTo v1:rest)        = 
        let p1 = pt .+^ v1
        in step p1 (lo2 lo p1, hi2 hi p1) rest

    step pt (lo,hi) (RelCurveTo v1 v2 v3:rest) = 
        let p1  = pt .+^ v1
            p2  = p1 .+^ v2
            p3  = p2 .+^ v3
            lo' = lo4 lo p1 p2 p3 
            hi' = hi4 hi p1 p2 p3
        in step p3 (lo',hi') rest 

    lo2 (P2 x1 y1) (P2 x2 y2) = P2 (min x1 x2) (min y1 y2)

    hi2 (P2 x1 y1) (P2 x2 y2) = P2 (max x1 x2) (max y1 y2)

    lo4 (P2 x1 y1) (P2 x2 y2) (P2 x3 y3) (P2 x4 y4) = 
        P2 (min x1 $ min x2 $ min x3 x4) (min y1 $ min y2 $ min y3 y4) 

    hi4 (P2 x1 y1) (P2 x2 y2) (P2 x3 y3) (P2 x4 y4) = 
        P2 (max x1 $ max x2 $ max x3 x4) (max y1 $ max y2 $ max y3 y4) 
 


labelBoundary :: FontAttr -> PrimLabel -> BoundingBox Double
labelBoundary attr (PrimLabel body ctm) = 
    retraceBoundary (m33 *#) untraf_bbox
  where
    m33         = matrixRepCTM ctm
    untraf_bbox = labelBodyBoundary (font_size attr) body

labelBodyBoundary :: FontSize -> LabelBody -> BoundingBox Double
labelBodyBoundary sz (StdLayout etxt) = stdLayoutBB sz etxt
labelBodyBoundary sz (KernTextH xs)   = hKerningBB sz xs
labelBodyBoundary sz (KernTextV xs)   = vKerningBB sz xs


stdLayoutBB :: FontSize -> EscapedText -> BoundingBox Double
stdLayoutBB sz etxt = textBoundsEsc sz zeroPt etxt


-- Note - this assumes positive deltas (and a nonempty list)...
--
-- Kern deltas are relative to the left basepoint, so they are
-- irrespective of the actual charater width. Thus to calculate
-- the bounding box Wumpus calculates the bounds of one character
-- then expands the right edge with the sum of the (rightwards)
-- displacements.
-- 
hKerningBB :: FontSize -> [(Double,EscapedChar)] -> BoundingBox Double
hKerningBB sz xs = rightGrow (sumDiffs xs) $ textBounds sz zeroPt "A"
  where
    sumDiffs                          = foldr (\(u,_) i -> i+u)  0
    rightGrow u (BBox ll (P2 x1 y1))  = BBox ll (P2 (x1+u) y1)


-- Note - likewise same assumptions as horizontal version.
-- (A postive distance represents a move downwards)...
--
-- The kern delta is the distance between baselines of successive
-- characters, so character height is irrespective when summing 
-- the deltas.
-- 
-- Also note, that the Label /grows/ downwards...
--
vKerningBB :: FontSize -> [(Double,EscapedChar)] -> BoundingBox Double
vKerningBB sz xs = downGrow (sumDiffs xs) $ textBounds sz zeroPt "A"
  where
    sumDiffs                                = foldr (\(u,_) i -> i+u)  0
    downGrow u (BBox (P2 x0 y0) (P2 x1 y1)) = BBox (P2 x0 (y0-u)) (P2 x1 y1)


-- | Ellipse bbox is the bounding rectangle, rotated as necessary 
-- then retraced.
--
ellipseBoundary :: PrimEllipse -> BoundingBox Double
ellipseBoundary (PrimEllipse hw hh ctm) = 
    traceBoundary $ map (m33 *#) [sw,se,ne,nw]
  where
    sw   = P2 (-hw) (-hh) 
    se   = P2   hw  (-hh) 
    ne   = P2   hw    hh 
    nw   = P2 (-hw)   hh 
    m33  = matrixRepCTM ctm


--------------------------------------------------------------------------------
-- Affine transformations

-- Affine transformation of Pictures only transforms the 
-- BoundingBox, the primitives within the picture are untouched.
-- The transformation is transmitted to PostScript as a matrix 
-- update (frame change).
--

instance Transform Picture where
  transform mtrx = 
    mapLocale $ \(bb,xs) -> let cmd = Matrix mtrx
                            in (transform mtrx bb, cmd : xs)

instance Rotate Picture where
  rotate theta = 
    mapLocale $ \(bb,xs) -> (rotate theta bb, Rotate theta:xs)


instance RotateAbout Picture where
  rotateAbout theta pt = 
    mapLocale $ \(bb,xs) -> let cmd = RotAbout theta pt
                            in (rotateAbout theta pt bb, cmd : xs)

instance Scale Picture where
  scale sx sy = 
    mapLocale $ \(bb,xs) -> let cmd = Scale sx sy
                            in (scale sx sy bb, cmd : xs)

instance Translate Picture where
  translate dx dy = 
    mapLocale $ \(bb,xs) -> let cmd = Translate dx dy
                            in (translate dx dy bb, cmd : xs)
                     


mapLocale :: (Locale -> Locale) -> Picture -> Picture
mapLocale f (Leaf lc ones)     = Leaf (f lc) ones
mapLocale f (Picture lc ones)  = Picture (f lc) ones


--------------------------------------------------------------------------------
-- Transform primitives


-- Note - Primitives are not instances of transform
--
-- (ShapeCTM is not a real matrix).
-- 

instance Rotate Primitive where
  rotate r (PPath a path)   = PPath a    $ rotatePath r path
  rotate r (PLabel a lbl)   = PLabel a   $ rotateLabel r lbl
  rotate r (PEllipse a ell) = PEllipse a $ rotateEllipse r ell
  rotate r (PContext a chi) = PContext a $ rotate r chi 
  rotate r (PSVG a chi)     = PSVG a     $ rotate r chi 
  rotate r (PGroup xs)      = PGroup     $ fmap (rotate r) xs
  rotate r (PClip p chi)    = PClip (rotatePath r p) (rotate r chi)

instance RotateAbout Primitive where
  rotateAbout ang p0 (PPath a path)   = 
      PPath a    $ rotateAboutPath ang p0 path

  rotateAbout ang  p0 (PLabel a lbl)   = 
      PLabel a   $ rotateAboutLabel ang p0 lbl

  rotateAbout ang p0 (PEllipse a ell) = 
      PEllipse a $ rotateAboutEllipse ang p0 ell

  rotateAbout ang p0 (PContext a chi) = 
      PContext a $ rotateAbout ang p0 chi

  rotateAbout ang p0 (PSVG a chi)     = 
      PSVG a     $ rotateAbout ang p0 chi

  rotateAbout ang p0 (PGroup xs)      = 
      PGroup     $ fmap (rotateAbout ang p0) xs

  rotateAbout ang p0 (PClip p chi)    = 
      PClip (rotateAboutPath ang p0 p) (rotateAbout ang p0 chi)


instance Scale Primitive where
  scale sx sy (PPath a path)    = PPath a    $ scalePath sx sy path
  scale sx sy (PLabel a lbl)    = PLabel a   $ scaleLabel sx sy lbl
  scale sx sy (PEllipse a ell)  = PEllipse a $ scaleEllipse sx sy ell
  scale sx sy (PContext a chi)  = PContext a $ scale sx sy chi
  scale sx sy (PSVG a chi)      = PSVG a     $ scale sx sy chi
  scale sx sy (PGroup xs)       = PGroup     $ fmap (scale sx sy) xs
  scale sx sy (PClip p chi)     = PClip (scalePath sx sy p) (scale sx sy chi)


instance Translate Primitive where
  translate dx dy (PPath a path)   = 
      PPath a    $ translatePath dx dy path

  translate dx dy (PLabel a lbl)   = 
      PLabel a   $ translateLabel dx dy lbl

  translate dx dy (PEllipse a ell) = 
      PEllipse a $ translateEllipse dx dy ell

  translate dx dy (PContext a chi) = 
      PContext a $ translate dx dy chi

  translate dx dy (PSVG a chi)     = 
      PSVG a     $ translate dx dy chi

  translate dx dy (PGroup xs)      = 
      PGroup     $ fmap (translate dx dy) xs

  translate dx dy (PClip p chi)    = 
      PClip (translatePath dx dy p) (translate dx dy chi)


--------------------------------------------------------------------------------
-- Paths

-- Affine transformations on paths are applied to their control
-- points. 

rotatePath :: Radian -> PrimPath -> PrimPath
rotatePath ang (PrimPath vs ctm) = PrimPath vs (rotateCTM ang ctm)


rotateAboutPath :: Radian -> DPoint2 -> PrimPath -> PrimPath
rotateAboutPath ang (P2 x y) (PrimPath vs ctm) = 
    PrimPath vs (rotateAboutCTM ang (P2 x y) ctm)


scalePath :: Double -> Double -> PrimPath -> PrimPath
scalePath sx sy (PrimPath vs ctm) = PrimPath vs (scaleCTM sx sy ctm)


-- Note - translate only needs change the start point /because/ 
-- the path represented as a relative path.
-- 
translatePath :: Double -> Double -> PrimPath -> PrimPath
translatePath dx dy (PrimPath vs ctm) = 
    PrimPath vs (translateCTM dx dy ctm)


--------------------------------------------------------------------------------
-- Labels



-- Rotate the baseline-left start point _AND_ the CTM of the 
-- label.
--
rotateLabel :: Radian -> PrimLabel -> PrimLabel
rotateLabel ang (PrimLabel txt ctm) = PrimLabel txt (rotateCTM ang ctm)


-- /rotateAbout/ the start-point, /rotate/ the the CTM.
--
rotateAboutLabel :: Radian -> DPoint2 -> PrimLabel -> PrimLabel
rotateAboutLabel ang (P2 x y) (PrimLabel txt ctm) = 
    PrimLabel txt (rotateAboutCTM ang (P2 x y) ctm)


scaleLabel :: Double -> Double -> PrimLabel -> PrimLabel
scaleLabel sx sy (PrimLabel txt ctm) = 
    PrimLabel txt (scaleCTM sx sy ctm)


-- Change the bottom-left corner.
--
translateLabel :: Double -> Double -> PrimLabel -> PrimLabel
translateLabel dx dy (PrimLabel txt ctm) = 
    PrimLabel txt (translateCTM dx dy ctm)

--------------------------------------------------------------------------------
-- Ellipse


rotateEllipse :: Radian -> PrimEllipse -> PrimEllipse
rotateEllipse ang (PrimEllipse hw hh ctm) = 
    PrimEllipse hw hh (rotateCTM ang ctm)
    

rotateAboutEllipse :: Radian -> DPoint2 -> PrimEllipse -> PrimEllipse
rotateAboutEllipse ang (P2 x y) (PrimEllipse hw hh ctm) = 
    PrimEllipse hw hh (rotateAboutCTM ang (P2 x y) ctm)


scaleEllipse :: Double -> Double -> PrimEllipse -> PrimEllipse
scaleEllipse sx sy (PrimEllipse hw hh ctm) = 
    PrimEllipse hw hh (scaleCTM sx sy ctm)
    


-- Change the point
--
translateEllipse :: Double -> Double -> PrimEllipse -> PrimEllipse
translateEllipse dx dy (PrimEllipse hw hh ctm) = 
    PrimEllipse hw hh (translateCTM dx dy ctm)



--------------------------------------------------------------------------------
-- Additional operations


-- | Destructor for Matrix3'3.
-- 
-- Pattern matching on 6-tuple may be more convenient than using 
-- the Matrix3'3 directly.
--
-- > (M3'3 e0x e1x ox  
-- >       e0y e1y oy  
-- >       _   _   _  ) = (e0x,e0y,  e1x,e1y,  ox,oy)
--  
deconsMatrix :: Matrix3'3 u -> (u,u,u,u,u,u)
deconsMatrix (M3'3 e0x e1x ox  
                   e0y e1y oy  
                   _   _   _  ) = (e0x,e0y,  e1x,e1y,  ox,oy)



-- If a picture has coordinates smaller than (P2 4 4) especially
-- negative ones then it needs repositioning before it is drawn 
-- to PostScript or SVG.
-- 
-- (P2 4 4) gives a 4 pt margin - maybe it sould be (0,0) or 
-- user defined.
--
repositionDeltas :: Picture -> (BoundingBox Double, Maybe DVec2)
repositionDeltas = step . boundaryPicture
  where
    step bb@(BBox (P2 llx lly) (P2 urx ury))
        | llx < 4 || lly < 4  = (BBox ll ur, Just $ V2 x y)
        | otherwise           = (bb, Nothing)
      where 
        x  = 4 - llx
        y  = 4 - lly
        ll = P2 (llx+x) (lly+y)
        ur = P2 (urx+x) (ury+y) 


extractRelPath :: PrimPath -> (DPoint2, [PrimPathSegment])
extractRelPath (PrimPath ss ctm) = (start, usegs)
  where 
    (start,dctm)  = unCTM ctm
    mtrafo        = transform (matrixRepCTM dctm)
    usegs         = map fn ss
    
    fn (RelCurveTo v1 v2 v3) = RelCurveTo (mtrafo v1) (mtrafo v2) (mtrafo v3)
    fn (RelLineTo v1)        = RelLineTo  (mtrafo v1)



--------------------------------------------------------------------------------

-- | The initial graphics state.
--
-- PostScript has no default font so we always want the first 
-- /delta/ operation not to find a match and cause a @findfont@
-- command to be generated (PostScript @findfont@ commands are 
-- only written in the output on /deltas/ to reduce the 
-- output size).
--
zeroGS ::  GraphicsState 
zeroGS = GraphicsState { gs_draw_colour  = black
                       , gs_font_size    = (-1)
                       , gs_font_face    = unmatchable_face
                       , gs_stroke_attr  = default_stroke_attr
                       }
  where
    unmatchable_face = FontFace "DONT_MATCH"     "" 
                                SVG_BOLD_OBLIQUE no_encoding

    no_encoding      = IntMap.empty 


-- | Is the path empty - if so we might want to avoid printing it.
--
isEmptyPath :: PrimPath -> Bool
isEmptyPath (PrimPath xs _) = null xs

-- | Is the label empty - if so we might want to avoid printing it.
--
isEmptyLabel :: PrimLabel -> Bool
isEmptyLabel (PrimLabel txt _) = body txt
   where
     body (StdLayout esc) = destrEscapedText null esc
     body (KernTextH xs)  = null xs
     body (KernTextV xs)  = null xs