{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# OPTIONS -Wall #-} -------------------------------------------------------------------------------- -- | -- Module : Wumpus.Core.PictureInternal -- Copyright : (c) Stephen Tetley 2009-2012 -- 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 , pushXIdAnno ) 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 group - @ ... @ -- -- * A group of annotations inside a hyperlink. -- -- * An @id@. -- data SvgAnno = ALink XLink | GAnno [SvgAttr] | SvgAG XLink [SvgAttr] | SvgId String 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. -- -- Also note, this functionality is has not been widely used. It -- might be something of a white elephant. -- data SvgAttr = SvgAttr { svg_attr_name :: String , svg_attr_value :: String } deriving (Eq,Show) -- | PrimPath - a list of path segments and a CTM (translation -- matrix). -- -- The start point of the path forms the (dx,dy) of the CTM. The -- CTM is otherwise hidden from the public constructors of this -- data type. -- -- Note - the PrimPath type does not support concatenation. -- It is expected that all PrimPaths will be created /in one go/, -- and client code defines a higher-level path type that supports -- concatenation, splitting etc. -- -- Primitively paths can be built like this: -- -- > -- > path1 :: PrimPath -- > path1 = absPrimPath zeroPt [ absLineTo (P2 0 60) -- > , absLineTo (P2 40 100) -- > , absLineTo (P2 80 60) -- > , absLineTo (P2 80 0) -- > , absLineTo (P2 60 0) -- > , absLineTo (P2 60 30) -- > , absCurveTo (P2 60 50) (P2 50 60) (P2 40 60) -- > , absCurveTo (P2 30 60) (P2 20 50) (P2 20 30) -- > , absLineTo (P2 20 0) -- > ] -- > -- -- Although it\'s generally expected that PrimPaths will be -- constructed by traversing a higher-level path object and -- collecting calls to the @absCurevTo@ and @absLineTo@ functions -- in a list. -- 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_opt_id :: Maybe String , 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 opt_id ctm) = vcat [ dquotes (format s) , maybe (char '_') text $ opt_id , 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 opt_id ctm) = PrimLabel txt opt_id (rotateCTM ang ctm) -- /rotateAbout/ the start-point, /rotate/ the the CTM. -- rotateAboutLabel :: Radian -> DPoint2 -> PrimLabel -> PrimLabel rotateAboutLabel ang (P2 x y) (PrimLabel txt opt_id ctm) = PrimLabel txt opt_id (rotateAboutCTM ang (P2 x y) ctm) scaleLabel :: Double -> Double -> PrimLabel -> PrimLabel scaleLabel sx sy (PrimLabel txt opt_id ctm) = PrimLabel txt opt_id (scaleCTM sx sy ctm) -- Change the bottom-left corner. -- translateLabel :: Double -> Double -> PrimLabel -> PrimLabel translateLabel dx dy (PrimLabel txt opt_id ctm) = PrimLabel txt opt_id (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 -- | Annotate a Primitive with an @id@ for SVG. -- -- Note - for @PLabel@ this /pushes/ the id /inside/ the -- constructor, for other elements the the id adds an extra layer -- of nesting via the SVG group \ tag. -- pushXIdAnno :: String -> Primitive -> Primitive pushXIdAnno ss (PLabel props (PrimLabel txt _ ctm )) = PLabel props $ PrimLabel txt (Just ss) ctm pushXIdAnno ss prim = PSVG (SvgId ss) prim