packages feed

diagrams-input-0.1: src/Diagrams/SVG/Tree.hs

{-# LANGUAGE TypeFamilies, OverloadedStrings, FlexibleContexts #-}
--------------------------------------------------------------------
-- |
-- Module    : Diagrams.SVG.Tree
-- Copyright : (c) 2015 Tillmann Vogt <tillk.vogt@googlemail.com>
-- License   : BSD3
--
-- Maintainer: diagrams-discuss@googlegroups.com
-- Stability : stable
-- Portability: portable

module Diagrams.SVG.Tree
    (
    -- * Tree data type
      Tag(..)
    , HashMaps(..)
    -- * Extract data from the tree
    , nodes
    , Attrs(..)
    , NodesMap
    , CSSMap
    , GradientsMap
    , PreserveAR(..)
    , AlignSVG(..)
    , MeetOrSlice(..)
    , Place
    , ViewBox(..)
    , Gr(..)
    , GradientAttributes(..)
    , PresentationAttributes(..)
    , GradRefId
    , expandGradMap
    , insertRefs
    , preserveAspectRatio
    , FontContent(..)
    , FontData(..)
    , FontFace(..)
    , Glyph(..)
    , KernDir(..)
    , KernMaps(..)
    , SvgGlyphs(..)
    , Kern(..)
    )
where
import           Data.Maybe (isJust, fromJust , fromMaybe)
import qualified Data.HashMap.Strict as H
import qualified Data.Text as T
import           Data.Text(Text(..))
import           Data.Vector(Vector)
import           Diagrams.Prelude hiding (Vector)
import           Diagrams.TwoD.Size
-- import           Diagrams.SVG.Fonts.ReadFont
import           Debug.Trace

-- Note: Maybe we could use the Tree from diagrams here but on the other hand this makes diagrams-input 
-- more independent of changes of diagrams' internal structures

-------------------------------------------------------------------------------------
-- | A tree structure is needed to handle refences to parts of the tree itself.
-- The \<defs\>-section contains shapes that can be refered to, but the SVG standard allows to refer to
-- every tag in the SVG-file.
--
data Tag b n = Leaf Id (ViewBox n -> Path V2 n) ((HashMaps b n, ViewBox n) -> Diagram b)-- ^
-- A leaf consists of
--
-- * An Id
--
-- * A path so that this leaf can be used to clip some other part of a tree
--
-- * A diagram (Another option would have been to apply a function to the upper path)
     | Reference Id Id (ViewBox n -> Path V2 n) ((HashMaps b n, ViewBox n) -> Diagram b -> Diagram b)-- ^
--  A reference (\<use\>-tag) consists of:
--
-- * An Id
--
-- * A reference to an Id
--
-- * A viewbox so that percentages are relative to this viewbox
--
-- * Transformations applied to the reference
     | SubTree Bool Id (Double, Double)
                       (Maybe (ViewBox n)) 
                       (Maybe PreserveAR) 
                       (HashMaps b n -> Diagram b -> Diagram b) 
                       [Tag b n]-- ^
-- A subtree consists of:
--
-- * A Bool: Are we in a section that will be rendered directly (not in a \<defs\>-section)
--
-- * An Id of subdiagram
--
-- * A viewbox so that percentages are relative to this viewbox
--
-- * Aspect Ratio
--
-- * A transformation or application of a style to a subdiagram
--
-- * A list of subtrees
     | StyleTag [(Text, [(Text, Text)])] -- ^ A tag that contains CSS styles with selectors and attributes
     | FontTag (FontData b n)
     | Grad Id (Gr n) -- ^ A gradient
     | Stop (HashMaps b n -> [GradientStop n]) -- ^
-- We need to make this part of this data structure because Gradient tags can also contain description tags

type Id        = Maybe Text
type GradRefId = Maybe Text
type Attrs     = [(Text, Text)]

type Nodelist b n = [(Text, Tag b n)]
type CSSlist  = [(Text, Attrs)]
data Gr n = Gr GradRefId
               GradientAttributes
               (Maybe (ViewBox n))
               [CSSMap -> [GradientStop n]]
               (CSSMap -> GradientAttributes -> ViewBox n -> [CSSMap -> [GradientStop n]] -> Texture n)

type Gradlist n = [(Text, Gr n)]
type Fontlist b n = [(Text, FontData b n)]

type HashMaps b n = (NodesMap b n, CSSMap, GradientsMap n)
type NodesMap b n = H.HashMap Text (Tag b n)
type CSSMap = H.HashMap Text Attrs
type GradientsMap n = H.HashMap Text (Gr n)

type ViewBox n = (n,n,n,n) -- (MinX,MinY,Width,Height)

data PreserveAR = PAR AlignSVG MeetOrSlice -- ^ see <http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute>
data AlignSVG = AlignXY Place Place -- ^ alignment in x and y direction
type Place = Double -- ^ A value between 0 and 1, where 0 is the minimal value and 1 the maximal value
data MeetOrSlice = Meet | Slice

instance Show (Tag b n) where
  show (Leaf id1 _ _)  = "Leaf "      ++ (show id1) ++ "\n"
  show (Reference selfid id1 viewbox f) = "Reference " ++ (show id1) ++ "\n"
  show (SubTree b id1 wh viewbox ar f tree) = "Sub " ++ (show id1) ++ concat (map show tree) ++ "\n"
  show (StyleTag _)   = "Style "    ++ "\n"
  show (Grad id1 gr) = "Grad id:" ++ (show id1) -- ++ (show gr) ++ "\n"
  show (Stop _)   = "Stop " ++ "\n"

-- instance Show (Gr n) where show (Gr gradRefId gattr vb stops tex) = "  ref:" ++ (show gradRefId) ++ "viewbox: " ++ (show vb)

----------------------------------------------------------------------------------
-- | Generate elements that can be referenced by their ID.
--   The tree nodes are splitted into 4 groups of lists of (ID,value)-pairs):
--
-- * Nodes that contain elements that can be transformed to a diagram
--
-- * CSS classes with corresponding (attribute,value)-pairs, from the <defs>-tag
--
-- * Gradients
--
-- * Fonts
nodes :: Maybe (ViewBox n) -> (Nodelist b n, CSSlist, Gradlist n, Fontlist b n) -> Tag b n -> 
                              (Nodelist b n, CSSlist, Gradlist n, Fontlist b n)
nodes viewbox (ns,css,grads,fonts) (Leaf id1 path diagram)
  | isJust id1 = (ns ++ [(fromJust id1, Leaf id1 path diagram)],css,grads,fonts)
  | otherwise  = (ns,css,grads,fonts)

-- A Reference element for the <use>-tag
nodes viewbox (ns,css,grads,fonts) (Reference selfId id1 vb f) = (ns,css,grads,fonts)

nodes viewbox (ns,css,grads,fonts)                (SubTree b id1 wh Nothing ar f children)
  | isJust id1 = myconcat [ (ns ++ [(fromJust id1, SubTree b id1 wh viewbox ar f children)],css,grads,fonts) ,
                            (myconcat (map (nodes viewbox (ns,css,grads,fonts)) children))                ]
  | otherwise  = myconcat (map (nodes viewbox (ns,css,grads,fonts)) children)

nodes viewbox (ns,css,grads,fonts)                (SubTree b id1 wh vb ar f children)
  | isJust id1 = myconcat [ (ns ++ [(fromJust id1, SubTree b id1 wh vb ar f children)],css,grads,fonts) ,
                            (myconcat (map (nodes vb (ns,css,grads,fonts)) children))                ]
  | otherwise  = myconcat (map (nodes vb (ns,css,grads,fonts)) children)

nodes viewbox (ns,css,grads,fonts) (Grad id1 (Gr gradRefId gattr vb stops texture))
  | isJust id1 = (ns,css, grads ++ [(fromJust id1, Gr gradRefId gattr vb stops texture)], fonts)
  | otherwise  = (ns,css,grads,fonts)

-- There is a global style tag in the defs section of some svg files
nodes viewbox (ns,css,grads,fonts) (StyleTag styles) = (ns,css ++ styles,grads,fonts)
-- stops are not extracted here but from the gradient parent node
nodes viewbox lists (Stop _) = lists

nodes viewbox (ns,css,grads,fonts) (FontTag fontData) = (ns,css,grads,fonts ++ [(fromMaybe "" (fontId fontData), fontData)])

myconcat :: [(Nodelist b n, CSSlist, Gradlist n, Fontlist b n)] -> (Nodelist b n, CSSlist, Gradlist n, Fontlist b n)
myconcat list = (concat $ map sel1 list, concat $ map sel2 list, concat $ map sel3 list, concat $ map sel4 list)
  where sel1 (a,b,c,d) = a
        sel2 (a,b,c,d) = b
        sel3 (a,b,c,d) = c
        sel4 (a,b,c,d) = d

------------------------------------------------------------------------------------------------------
-- The following code is necessary to handle nested xlink:href in gradients,
-- like in this example (#linearGradient3606 in radialGradient):
--
--    <linearGradient
--       id="linearGradient3606">
--      <stop
--         id="stop3608"
--         style="stop-color:#ff633e;stop-opacity:1"
--         offset="0" />
--      <stop
--         id="stop3610"
--         style="stop-color:#ff8346;stop-opacity:0.78225809"
--         offset="1" />
--    </linearGradient>
--    <radialGradient
--       cx="275.00681"
--       cy="685.96008"
--       r="112.80442"
--       fx="275.00681"
--       fy="685.96008"
--       id="radialGradient3612"
--       xlink:href="#linearGradient3606"
--       gradientUnits="userSpaceOnUse"
--       gradientTransform="matrix(1,0,0,1.049029,-63.38387,-67.864647)" />

-- | Gradients contain references to include attributes/stops from other gradients. 
--   expandGradMap expands the gradient with these attributes and stops

expandGradMap :: GradientsMap n ->  GradientsMap n -- GradientsMap n = H.HashMap Text (Gr n)
expandGradMap gradMap = H.mapWithKey (newGr gradMap) gradMap

newGr grMap key (Gr gradRefId attrs vb stops f) = (Gr gradRefId newAttributes vb newStops f)
  where newStops = stops ++ (gradientStops grMap gradRefId)
        newAttributes = overwriteDefaultAttributes $ gradientAttributes grMap (Just key)

-- | Gradients that reference other gradients form a list of attributes
--   The last element of this list are the default attributes (thats why there is "reverse attrs")
--   Then the second last attributes overwrite these defaults (and so on until the root)
--   The whole idea of this nesting is that Nothing values don't overwrite Just values
overwriteDefaultAttributes :: [GradientAttributes] -> GradientAttributes
overwriteDefaultAttributes [attrs] = attrs
overwriteDefaultAttributes attrs = foldl1 updateRec (reverse attrs)

-- | Every reference is looked up in the gradient map and a record of attributes is added to a list
gradientAttributes :: GradientsMap n -> GradRefId -> [GradientAttributes] -- GradientsMap n = H.HashMap Text (Gr n)
gradientAttributes grMap Nothing = []
gradientAttributes grMap (Just refId) | isJust gr = (attrs $ fromJust gr) : (gradientAttributes grMap (grRef $ fromJust gr))
                                      | otherwise = []
  where gr = H.lookup refId grMap
        grRef   (Gr ref _ _ _ _) = ref

attrs   (Gr _ att _ _ _) = att

-- | Every reference is looked up in the gradient map and the stops are added to a list
gradientStops :: GradientsMap n -> GradRefId -> [CSSMap -> [GradientStop n]]
gradientStops grMap Nothing = []
gradientStops grMap (Just refId) | isJust gr = (stops $ fromJust gr) ++ (gradientStops grMap (grRef $ fromJust gr))
                                 | otherwise = []
  where gr = H.lookup refId grMap
        grRef   (Gr ref _ _ _ _) = ref
        stops   (Gr _  _ _ st _) = st

-- | Update the gradient record. The first argument is the leaf record, the second is the record that overwrites the leaf.
--   The upper example references gradients that have only stops (no overwriting of attributes).
--   See <http://www.w3.org/TR/SVG/pservers.html#RadialGradientElementHrefAttribute>
updateRec :: GradientAttributes -> GradientAttributes -> GradientAttributes
updateRec (GA pa  class_  style  x1  y1  x2  y2  cx  cy  r  fx  fy  gradientUnits  gradientTransform  spreadMethod)
          (GA paN class1N styleN x1N y1N x2N y2N cxN cyN rN fxN fyN gradientUnitsN gradientTransformN spreadMethodN)
  = toGA (paN, (updateList [class_,style,x1,y1,x2,y2,cx,cy,r,fx,fy,gradientUnits,gradientTransform,spreadMethod] -- TODO: update pa
                           [class1N,styleN,x1N,y1N,x2N,y2N,cxN,cyN,rN,fxN,fyN,gradientUnitsN,gradientTransformN,spreadMethodN]))
  where
    updateList :: [Maybe Text] -> [Maybe Text] -> [Maybe Text]
    updateList (defaultt:xs) ((Just t1):ys) = (Just t1) : (updateList xs ys)
    updateList ((Just t0):xs) (Nothing  :ys) = (Just t0) : (updateList xs ys)
    updateList  (Nothing :xs) (Nothing  :ys) =  Nothing  : (updateList xs ys)
    updateList _ _ = []

    toGA (pa, [class_,style,x1,y1,x2,y2,cx,cy,r,fx,fy,gradientUnits,gradientTransform,spreadMethod]) =
       GA pa   class_ style x1 y1 x2 y2 cx cy r fx fy gradientUnits gradientTransform spreadMethod

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

-- | Lookup a diagram and return an empty diagram in case the SVG-file has a wrong reference
lookUp hmap i | (isJust i) && (isJust l) = fromJust l
              | otherwise = Leaf Nothing mempty mempty -- an empty diagram if we can't find the id
  where l = H.lookup (fromJust i) hmap

-- | Evaluate the tree into a diagram by inserting xlink:href references from nodes and gradients, 
--   applying clipping and passing the viewbox to the leafs
insertRefs :: (V b ~ V2, N b ~ n, RealFloat n, Place ~ n) => (HashMaps b n, ViewBox n) -> Tag b n -> Diagram b

insertRefs (maps,viewbox) (Leaf id1 path f) = (f (maps,viewbox)) # (if isJust id1 then named (T.unpack $ fromJust id1) else id)
insertRefs (maps,viewbox) (Grad _ _) = mempty
insertRefs (maps,viewbox) (Stop f) = mempty
insertRefs (maps,viewbox) (Reference selfId id1 path styles)
    | (Diagrams.TwoD.Size.width r) <= 0 || (Diagrams.TwoD.Size.height r) <= 0 = mempty
    | otherwise = referencedDiagram # styles (maps,viewbox)
                                    # cutOutViewBox viewboxPAR
--                                    # stretchViewBox (fromJust w) (fromJust h) viewboxPAR
                                    # (if isJust selfId then named (T.unpack $ fromJust selfId) else id)
  where r = path viewbox
        viewboxPAR = getViewboxPreserveAR subTree
        referencedDiagram = insertRefs (maps,viewbox) (makeSubTreeVisible viewbox subTree)
        subTree = lookUp (sel1 maps) id1
        getViewboxPreserveAR (SubTree _ id1 wh viewbox ar g children) = (viewbox, ar)
        getViewboxPreserveAR _ = (Nothing, Nothing)
        sel1 (a,b,c) = a

insertRefs (maps,viewbox) (SubTree False _ _ _ _ _ _) = mempty
insertRefs (maps,viewbox) (SubTree True id1 (w,h) viewb ar styles children) =
    subdiagram # styles maps
               # cutOutViewBox (viewb, ar)
               # (if (w > 0) && (h > 0) then stretchViewBox w h (viewb, ar) else id)
               # (if isJust id1 then named (T.unpack $ fromJust id1) else id)
  where subdiagram = mconcat (map (insertRefs (maps, fromMaybe viewbox viewb)) children)

insertRefs (maps,viewbox) (StyleTag _) = mempty
-------------------------------------------------------------------------------------------------------------------------------

makeSubTreeVisible viewbox (SubTree _    id1 wh vb ar g children) =
                           (SubTree True id1 wh (Just viewbox) ar g (map (makeSubTreeVisible viewbox) children))
makeSubTreeVisible _ x = x

stretchViewBox w h ((Just (minX,minY,width,height), Just par)) = preserveAspectRatio w h (width - minX) (height - minY) par
stretchViewBox w h ((Just (minX,minY,width,height), Nothing))  = -- Debug.Trace.trace "nothing" $
                                    preserveAspectRatio w h (width - minX) (height - minY) (PAR (AlignXY 0.5 0.5) Meet)
stretchViewBox w h _ = id

cutOutViewBox (Just (minX,minY,width,height), _) = rectEnvelope (p2 (minX, minY)) (r2 ((width - minX), (height - minY)))
                                                 --  (clipBy (rect (width - minX) (height - minY)))
cutOutViewBox _ = id

-------------------------------------------------------------------------------------------------------------------------------
-- | preserveAspectRatio is needed to fit an image into a frame that has a different aspect ratio than the image
--  (e.g. 16:10 against 4:3).
--  SVG embeds images the same way: <http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute>
--
-- > import Graphics.SVGFonts
-- >
-- > portrait preserveAR width height = stroke (readSVGFile preserveAR width height "portrait.svg") # showOrigin
-- > text' t = stroke (textSVG' $ TextOpts t lin INSIDE_H KERN False 1 1 ) # fc back # lc black # fillRule EvenOdd
-- > portraitMeet1 x y = (text' "PAR (AlignXY " ++ show x ++ " " show y ++ ") Meet") ===
-- >                     (portrait (PAR (AlignXY x y) Meet) 200 100 <> rect 200 100)
-- > portraitMeet2 x y = (text' "PAR (AlignXY " ++ show x ++ " " show y ++ ") Meet") ===
-- >                     (portrait (PAR (AlignXY x y) Meet) 100 200 <> rect 100 200)
-- > portraitSlice1 x y = (text' "PAR (AlignXY " ++ show x ++ " " show y ++ ") Slice") ===
-- >                      (portrait (PAR (AlignXY x y) Slice) 100 200 <> rect 100 200)
-- > portraitSlice2 x y = (text' "PAR (AlignXY " ++ show x ++ " " show y ++ ") Slice") ===
-- >                      (portrait (PAR (AlignXY x y) Slice) 200 100 <> rect 200 100)
-- > meetX = (text' "meet") === (portraitMeet1 0 0 ||| portraitMeet1 0.5 0 ||| portraitMeet1 1 0)
-- > meetY = (text' "meet") === (portraitMeet2 0 0 ||| portraitMeet2 0 0.5 ||| portraitMeet2 0 1)
-- > sliceX = (text' "slice") === (portraitSlice1 0 0 ||| portraitSlice1 0.5 0 ||| portraitSlice1 1 0)
-- > sliceY = (text' "slice") === (portraitSlice2 0 0 ||| portraitSlice2 0 0.5 ||| portraitSlice2 0 1)
-- > im = (text' "Image to fit") === (portrait (PAR (AlignXY 0 0) Meet) 123 456)
-- > viewport1 = (text' "Viewport1") === (rect 200 100)
-- > viewport2 = (text' "Viewport2") === (rect 100 200)
-- > imageAndViewports = im === viewport1 === viewport2
-- >
-- > par = imageAndViewports ||| ( ( meetX ||| meetY) === ( sliceX ||| sliceY) )
--
-- <<diagrams/src_Graphics_SVGFonts_ReadFont_textPic0.svg#diagram=par&width=300>>
-- preserveAspectRatio :: Width -> Height -> Width -> Height -> PreserveAR -> Diagram b -> Diagram b
preserveAspectRatio newWidth newHeight oldWidth oldHeight preserveAR image
   | aspectRatio < newAspectRatio = xPlace preserveAR image
   | otherwise                    = yPlace preserveAR image
  where aspectRatio = oldWidth / oldHeight
        newAspectRatio = newWidth / newHeight
        scaX = newHeight / oldHeight
        scaY = newWidth / oldWidth
        xPlace (PAR (AlignXY x y) Meet)  i = i # scale scaX # alignBL # translateX ((newWidth  - oldWidth*scaX)*x)
        xPlace (PAR (AlignXY x y) Slice) i = i # scale scaY # alignBL # translateX ((newWidth  - oldWidth*scaX)*x)
--                                               # view (p2 (0, 0)) (r2 (newWidth, newHeight))

        yPlace (PAR (AlignXY x y) Meet)  i = i # scale scaY # alignBL # translateY ((newHeight - oldHeight*scaY)*y)
        yPlace (PAR (AlignXY x y) Slice) i = i # scale scaX # alignBL # translateY ((newHeight - oldHeight*scaY)*y)
--                                               # view (p2 (0, 0)) (r2 (newWidth, newHeight))


-- a combination of linear- and radial-attributes so that referenced gradients can replace Nothing-attributes
data GradientAttributes =
  GA { presentationAttributes :: PresentationAttributes
     , class_ :: Maybe Text
     , style  :: Maybe Text
     , x1  :: Maybe Text
     , y1  :: Maybe Text
     , x2  :: Maybe Text
     , y2  :: Maybe Text
     , cx  :: Maybe Text
     , cy  :: Maybe Text
     , r   :: Maybe Text
     , fx  :: Maybe Text
     , fy  :: Maybe Text
     , gradientUnits     :: Maybe Text
     , gradientTransform :: Maybe Text
     , spreadMethod      :: Maybe Text
     }

-- GA pa class_ style x1 y1 x2 y2 cx cy r fx fy gradientUnits gradientTransform spreadMethod

data PresentationAttributes =
   PA { alignmentBaseline :: Maybe Text
      , baselineShift :: Maybe Text
      , clip :: Maybe Text
      , clipPath :: Maybe Text
      , clipRule :: Maybe Text
      , color :: Maybe Text
      , colorInterpolation :: Maybe Text
      , colorInterpolationFilters :: Maybe Text
      , colorProfile :: Maybe Text
      , colorRendering :: Maybe Text
      , cursor :: Maybe Text
      , direction :: Maybe Text
      , display :: Maybe Text
      , dominantBaseline :: Maybe Text
      , enableBackground :: Maybe Text
      , fill :: Maybe Text
      , fillOpacity :: Maybe Text
      , fillRuleSVG :: Maybe Text
      , filter :: Maybe Text
      , floodColor :: Maybe Text
      , floodOpacity :: Maybe Text
      , fontFamily :: Maybe Text
      , fntSize :: Maybe Text
      , fontSizeAdjust :: Maybe Text
      , fontStretch :: Maybe Text
      , fontStyle :: Maybe Text
      , fontVariant :: Maybe Text
      , fontWeight :: Maybe Text
      , glyphOrientationHorizontal :: Maybe Text
      , glyphOrientationVertical :: Maybe Text
      , imageRendering :: Maybe Text
      , kerning :: Maybe Text
      , letterSpacing :: Maybe Text
      , lightingColor :: Maybe Text
      , markerEnd :: Maybe Text
      , markerMid :: Maybe Text
      , markerStart :: Maybe Text
      , mask :: Maybe Text
      , opacity :: Maybe Text
      , overflow :: Maybe Text
      , pointerEvents :: Maybe Text
      , shapeRendering :: Maybe Text
      , stopColor :: Maybe Text
      , stopOpacity :: Maybe Text
      , strokeSVG :: Maybe Text
      , strokeDasharray :: Maybe Text
      , strokeDashoffset :: Maybe Text
      , strokeLinecap :: Maybe Text
      , strokeLinejoin :: Maybe Text
      , strokeMiterlimit :: Maybe Text
      , strokeOpacity :: Maybe Text
      , strokeWidth :: Maybe Text
      , textAnchor :: Maybe Text
      , textDecoration :: Maybe Text
      , textRendering :: Maybe Text
      , unicodeBidi :: Maybe Text
      , visibility :: Maybe Text
      , wordSpacing :: Maybe Text
      , writingMode :: Maybe Text
      } deriving Show

type SvgGlyphs n = H.HashMap Text (Maybe Text, n, Maybe Text)
-- ^ \[ (unicode, (glyph_name, horiz_advance, ds)) \]

data Kern n = Kern
  { kernDir :: KernDir
  , kernU1  :: [Text]
  , kernU2  :: [Text]
  , kernG1  :: [Text]
  , kernG2  :: [Text]
  , kernK   :: n
  }

-- | Data from the subtags
data FontContent b n = FF (FontFace n) | GG (Glyph b n) | KK (Kern n)

-- | All data in the \<font\>-tag
data FontData b n = FontData
  {
    fontId                         :: Maybe Text
  , fontDataHorizontalOriginX      :: Maybe Text
  , fontDataHorizontalOriginY      :: Maybe Text
  , fontDataHorizontalAdvance      :: n
  , fontDataVerticalOriginX        :: Maybe Text
  , fontDataVerticalOriginY        :: Maybe Text
  , fontDataVerticalAdvance        :: Maybe Text
  -- ^ data gathered from subtags
  , fontFace                       :: FontFace n
  , fontMissingGlyph               :: Glyph b n
  , fontDataGlyphs                 :: SvgGlyphs n
--  , fontDataRawKernings            :: [(Text, [Text], [Text], [Text], [Text])]
  , fontDataKerning                :: KernMaps n
--  , fontDataFileName               :: Text
}

data FontFace n = FontFace
  { fontDataFamily                 :: Maybe Text
  , fontDataStyle                  :: Maybe Text
  , fontDataVariant                :: Maybe Text
  , fontDataWeight                 :: Maybe Text
  , fontDataStretch                :: Maybe Text
  , fontDataSize                   :: Maybe Text
  , fontDataUnicodeRange           :: Maybe Text
  , fontDataUnitsPerEm             :: Maybe Text
  , fontDataPanose                 :: Maybe Text
  , fontDataVerticalStem           :: Maybe Text
  , fontDataHorizontalStem         :: Maybe Text
  , fontDataSlope                  :: Maybe Text
  , fontDataCapHeight              :: Maybe Text
  , fontDataXHeight                :: Maybe Text
  , fontDataAccentHeight           :: Maybe Text
  , fontDataAscent                 :: Maybe Text
  , fontDataDescent                :: Maybe Text
  , fontDataWidths                 :: Maybe Text
  , fontDataBoundingBox            :: [n]
  , fontDataIdeographicBaseline    :: Maybe Text
  , fontDataAlphabeticBaseline     :: Maybe Text
  , fontDataMathematicalBaseline   :: Maybe Text
  , fontDataHangingBaseline        :: Maybe Text
  , fontDataVIdeographicBaseline   :: Maybe Text
  , fontDataVAlphabeticBaseline    :: Maybe Text
  , fontDataVMathematicalBaseline  :: Maybe Text
  , fontDataVHangingBaseline       :: Maybe Text
  , fontDataUnderlinePos           :: Maybe Text
  , fontDataUnderlineThickness     :: Maybe Text
  , fontDataStrikethroughPos       :: Maybe Text
  , fontDataStrikethroughThickness :: Maybe Text
  , fontDataOverlinePos            :: Maybe Text
  , fontDataOverlineThickness      :: Maybe Text
  }

data Glyph b n = Glyph
  { glyphId     :: Maybe Text
  , glyph       :: Tag b n
  , d           :: Maybe Text
  , horizAdvX   :: n
  , vertOriginX :: n
  , vertOriginY :: n
  , vertAdvY    :: n
  , unicode     :: Maybe Text
  , glyphName   :: Maybe Text
  , orientation :: Maybe Text
  , arabicForm  :: Maybe Text
  , lang        :: Maybe Text
  }

data KernDir = HKern | VKern

data KernMaps n = KernMaps
  { kernDirs :: [KernDir]
  , kernU1S :: H.HashMap Text [Int]
  , kernU2S :: H.HashMap Text [Int]
  , kernG1S :: H.HashMap Text [Int]
  , kernG2S :: H.HashMap Text [Int]
  , kernKs   :: Vector n
  }