packages feed

FontyFruity (empty) → 0.1

raw patch · 14 files changed

+1849/−0 lines, 14 filesdep +basedep +binarydep +bytestringsetup-changed

Dependencies added: base, binary, bytestring, containers, vector

Files

+ FontyFruity.cabal view
@@ -0,0 +1,41 @@+-- Initial FontyFruity.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                FontyFruity
+version:             0.1
+synopsis:            A true type file format loader
+-- description:         
+license:             BSD3
+license-file:        LICENSE
+author:              Vincent Berthoux
+maintainer:          vincent.berthoux@gmail.com
+-- copyright:           
+category:            Graphics, Text, Font
+build-type:          Simple
+-- extra-source-files:  
+cabal-version:       >=1.10
+
+extra-source-files: changelog
+
+library
+  exposed-modules: Graphics.Text.TrueType
+  other-modules: Graphics.Text.TrueType.Bytecode    
+               , Graphics.Text.TrueType.MaxpTable
+               , Graphics.Text.TrueType.Header
+               , Graphics.Text.TrueType.Glyph
+               , Graphics.Text.TrueType.Types
+               , Graphics.Text.TrueType.OffsetTable
+               , Graphics.Text.TrueType.CharacterMap
+               , Graphics.Text.TrueType.LanguageIds
+               , Graphics.Text.TrueType.HorizontalInfo
+
+  ghc-options: -Wall -O2
+  ghc-prof-options: -Wall -prof -auto-all
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  build-depends: base       >= 4.6   && < 4.7
+               , binary     >= 0.5   && < 0.8
+               , bytestring >= 0.10  && < 0.11
+               , containers >= 0.4.2 && < 0.6
+               , vector     >= 0.9   && < 0.11
+
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Vincent Berthoux
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * 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.
+
+    * Neither the name of Vincent Berthoux nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT
+OWNER 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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple
+main = defaultMain
+ changelog view
@@ -0,0 +1,5 @@+-*-change-log-*-
+
+v0.1
+ * Initial version
+
+ src/Graphics/Text/TrueType.hs view
@@ -0,0 +1,291 @@+{-# LANGUAGE CPP #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+-- | Module in charge of loading fonts.
+module Graphics.Text.TrueType
+    ( -- * Functions
+      decodeFont
+    , loadFontFile
+    , getStringCurveAtPoint
+
+      -- * Types
+    , Font
+    , Dpi
+    , PointSize
+    ) where
+
+import Control.Applicative( (<$>) )
+import Control.Monad( foldM )
+import Data.Function( on )
+import Data.List( sortBy, mapAccumL )
+import Data.Word( Word32 )
+import Data.Binary( Binary( .. ) )
+import Data.Binary.Get( Get
+                      , bytesRead
+                      , getWord16be
+                      , getWord32be
+                      , getByteString
+                      , getLazyByteString
+                      , skip
+                      )
+
+#if MIN_VERSION_binary(0,6,4)
+import qualified Data.Binary.Get as G
+#else
+import qualified Data.Binary as DB
+import qualified Control.Exception as E
+-- I feel so dirty. :(
+import System.IO.Unsafe( unsafePerformIO )
+#endif
+
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as LB
+import qualified Data.Vector as V
+import qualified Data.Vector.Unboxed as VU
+
+{-import Graphics.Text.TrueType.Types-}
+import Graphics.Text.TrueType.MaxpTable
+import Graphics.Text.TrueType.Glyph
+import Graphics.Text.TrueType.Header
+import Graphics.Text.TrueType.OffsetTable
+import Graphics.Text.TrueType.CharacterMap
+import Graphics.Text.TrueType.HorizontalInfo
+
+{-import Debug.Trace-}
+
+-- | Type representing a font.
+data Font = Font
+    { _fontOffsetTable       :: !OffsetTable
+    , _fontTables            :: ![(B.ByteString, B.ByteString)]
+    , _fontHeader            :: Maybe FontHeader
+    , _fontMaxp              :: Maybe MaxpTable
+    , _fontMap               :: Maybe CharacterMaps
+    , _fontGlyph             :: Maybe (V.Vector Glyph)
+    , _fontLoca              :: Maybe (VU.Vector Word32)
+    , _fontHorizontalHeader  :: Maybe HorizontalHeader
+    , _fontHorizontalMetrics :: Maybe HorizontalMetricsTable
+    }
+
+emptyFont :: OffsetTable -> Font
+emptyFont table = Font
+    { _fontTables            = []
+    , _fontOffsetTable       = table
+    , _fontHeader            = Nothing
+    , _fontGlyph             = Nothing
+    , _fontMaxp              = Nothing
+    , _fontLoca              = Nothing
+    , _fontMap               = Nothing
+    , _fontHorizontalHeader  = Nothing
+    , _fontHorizontalMetrics = Nothing
+    }
+
+-- | Load a font file, the file path must be pointing
+-- to the true type file (.ttf)
+loadFontFile :: FilePath -> IO (Either String Font)
+loadFontFile filepath = decodeFont <$> LB.readFile filepath
+
+-- | Decode a in-memory true type file.
+decodeFont :: LB.ByteString -> Either String Font
+decodeFont str =
+#if MIN_VERSION_binary(0,6,4)
+  case G.runGetOrFail getFont str of
+    Left err -> Left $ show err
+    Right (_, _, value) -> Right value
+#else
+  unsafePerformIO $ E.evaluate (return $ G.runGet getFont str)
+    `E.catch` catcher
+      where catcher :: E.SomeException -> IO (Either String a)
+            catcher e = return . Left $ show e
+#endif
+
+decodeWithDefault :: forall a . Binary a => a -> LB.ByteString -> a
+decodeWithDefault defaultValue str =
+#if MIN_VERSION_binary(0,6,4)
+  case G.runGetOrFail get str of
+    Left _ -> defaultValue
+    Right (_, _, value) -> value
+#else
+  unsafePerformIO $ E.evaluate (DB.decode str) `E.catch` catcher
+      where catcher :: E.SomeException -> IO a
+            catcher _ = return defaultValue
+#endif
+
+fetchTables :: OffsetTable -> Get Font
+fetchTables tables = foldM fetch (emptyFont tables) tableList
+  where
+    tableList = sortBy (compare `on` _tdeOffset)
+                    . V.toList
+                    $ _otEntries tables
+    gotoOffset entry = do
+        readed <- bytesRead 
+        let toDrop = fromIntegral (_tdeOffset entry) - readed
+        if toDrop < 0 then fail "Weirdo weird"
+        else skip $ fromIntegral toDrop
+
+    getLoca font@(Font { _fontMaxp = Just maxp, _fontHeader = Just hdr })
+      | _fHdrIndexToLocFormat hdr == 0 = do
+          v <- VU.replicateM glyphCount
+                ((* 2) . fromIntegral <$> getWord16be)
+          return $ font { _fontLoca = Just v }
+      | otherwise = do
+          v <- VU.replicateM glyphCount getWord32be
+          return $ font { _fontLoca = Just v }
+      where glyphCount = fromIntegral $ _maxpnumGlyphs maxp
+    getLoca font = return font
+
+    getGlyph font@(Font { _fontLoca = Just locations }) str =
+      return $ font { _fontGlyph = Just . V.map decoder $ VU.convert locationInterval }
+          where decoder (xStart, xEnd)
+                    | xEnd <= xStart = emptyGlyph
+                    | otherwise = 
+                        decodeWithDefault emptyGlyph $ chop xStart xEnd
+                chop start _ = LB.drop (fromIntegral start) str
+                locationsAll = locations `VU.snoc` (fromIntegral $ LB.length str)
+                locationInterval = VU.zip locations $ VU.tail locationsAll
+
+    getGlyph font _ = return font
+
+    fetch font entry | _tdeTag entry == "loca" =
+      gotoOffset entry >> getLoca font
+
+    fetch font entry | _tdeTag entry == "glyf" =
+      gotoOffset entry >>
+          getLazyByteString (fromIntegral $ _tdeLength entry)
+                >>= getGlyph font
+
+    fetch font entry | _tdeTag entry == "head" = do
+      table <- gotoOffset entry >> get
+      return $ font { _fontHeader = Just table }
+
+    fetch font entry | _tdeTag entry == "maxp" = do
+      table <- gotoOffset entry >> get
+      return $ font { _fontMaxp = Just table }
+
+    fetch font entry | _tdeTag entry == "cmap" = do
+      table <- gotoOffset entry >> get
+      return $ font { _fontMap = Just table }
+
+    fetch font entry | _tdeTag entry == "hhea" = do
+      table <- gotoOffset entry >> get
+      return $ font { _fontHorizontalHeader = Just table }
+    fetch font@Font { _fontMaxp = Just maxp,
+                      _fontHorizontalHeader = Just hdr } entry
+        | _tdeTag entry == "hmtx" = do
+      gotoOffset entry
+      let metricCount = _hheaLongHorMetricCount hdr
+      table <- getHorizontalMetrics (fromIntegral metricCount) glyphCount
+      return font { _fontHorizontalMetrics = Just table }
+     where glyphCount = fromIntegral $ _maxpnumGlyphs maxp
+
+    fetch font entry = do
+      let tableLength = fromIntegral $ _tdeLength entry
+      rawData <- gotoOffset entry >> getByteString tableLength
+      return $ font { _fontTables =
+                        (_tdeTag entry, rawData) : _fontTables font}
+
+getFont :: Get Font
+getFont = get >>= fetchTables
+
+-- | Express device resolution in dot per inch.
+type Dpi = Int
+
+-- | Font size expressed in points.
+type PointSize = Int
+
+glyphOfStrings :: Font -> String -> [(Glyph, HorizontalMetric)]
+glyphOfStrings Font { _fontMap = Just mapping
+                    , _fontGlyph = Just glyphes
+                    , _fontHorizontalMetrics = Just hmtx } str = fetcher . findCharGlyph mapping 0 <$> str
+  where
+    fetcher ix = (glyphes V.! ix, _glyphMetrics hmtx V.! ix)
+glyphOfStrings _ _ = []
+
+-- | Extract a list of outlines for every char in the string.
+-- The given curves are in an image like coordinate system,
+-- with the origin point in the upper left corner.
+getStringCurveAtPoint :: Dpi            -- ^ Dot per inch of the output.
+                      -> (Float, Float) -- ^ Initial position of the baseline.
+                      -> [(Font, PointSize, String)] -- ^ Text to draw
+                      -> [[VU.Vector (Float, Float)]] -- ^ List of contours for each char
+getStringCurveAtPoint dpi initPos lst = snd $ mapAccumL go initPos glyphes where 
+  glyphes = concat [ (font, size, unitsPerEm font,) <$> glyphOfStrings font str | (font, size, str) <- lst]
+
+  unitsPerEm Font { _fontHeader = Just hdr } = fromIntegral $ _fUnitsPerEm hdr
+  unitsPerEm  _ = 1
+
+  toPixel (_, pointSize, emSize, _) v = fromIntegral v * pixelSize / emSize
+    where
+      pixelSize = fromIntegral (pointSize * dpi) / 72
+
+  toFCoord (_, pointSize, emSize, _) v = floor $ v * emSize / pixelSize
+    where
+      pixelSize = fromIntegral (pointSize * dpi) / 72
+
+  maximumSize = maximum [ toPixel p . _glfYMax $ _glyphHeader glyph
+                                | p@(_, _, _, (glyph, _)) <- glyphes ]
+
+  go (xf, yf) p@(font, pointSize, _, (glyph, metric)) = ((toPixel p $ xi + advance, yf), curves)
+    where
+      (xi, yi) = (toFCoord p xf, toFCoord p yf)
+      bearing = fromIntegral $ _hmtxLeftSideBearing metric
+      advance = fromIntegral $ _hmtxAdvanceWidth metric
+      curves =
+          getGlyphIndexCurvesAtPointSizeAndPos font dpi (toFCoord p maximumSize)
+            (pointSize, glyph) (xi + bearing, yi)
+
+
+getGlyphIndexCurvesAtPointSizeAndPos :: Font -> Dpi -> Int -> (PointSize, Glyph) -> (Int, Int)
+                                     -> [VU.Vector (Float, Float)]
+getGlyphIndexCurvesAtPointSizeAndPos Font { _fontHeader = Nothing } _ _ _ _ = []
+getGlyphIndexCurvesAtPointSizeAndPos Font { _fontGlyph = Nothing } _ _ _ _ = []
+getGlyphIndexCurvesAtPointSizeAndPos
+    Font { _fontHeader = Just hdr, _fontGlyph = Just allGlyphs }
+        dpi maximumSize (pointSize, topGlyph) (baseX, baseY) = glyphReverse <$> glyphExtract topGlyph
+  where
+    go index | index >= V.length allGlyphs = []
+             | otherwise = glyphExtract $ allGlyphs V.! index
+
+    pixelSize = fromIntegral (pointSize * dpi) / 72
+    emSize = fromIntegral $ _fUnitsPerEm hdr
+
+    maxiF = toPixelCoordinate (0 :: Int) maximumSize
+    baseYF = toPixelCoordinate (0 :: Int) baseY
+
+    glyphReverse = VU.map (\(x,y) -> (x, maxiF - y + baseYF))
+
+    toPixelCoordinate shift coord =
+        (fromIntegral (shift + fromIntegral coord) * pixelSize) / emSize
+
+    composeGlyph composition = VU.map updateCoords <$> subCurves
+      where
+        subCurves = go . fromIntegral $ _glyphCompositeIndex composition
+        toFloat v = fromIntegral v / (0x4000 :: Float)
+        CompositeScaling ai bi ci di ei fi = _glyphCompositionScale composition
+
+        scaler v1 v2
+            | fromIntegral (abs (abs ai - abs ci)) <= (33 / 65536 :: Float) = 2 * vf
+            | otherwise = vf
+          where
+           vf = toFloat $ max (abs v1) (abs v2)
+
+        m = scaler ai bi
+        n = scaler ci di
+
+        am = toFloat ai / m
+        cm = toFloat ci / m
+        bn = toFloat ci / n
+        dn = toFloat di / n
+        e = toFloat ei
+        f = toFloat fi
+
+        updateCoords (x,y) =
+            (m * (am * x + cm *y + e), n * (bn * x + dn * y + f))
+
+    glyphExtract Glyph { _glyphContent = GlyphEmpty } = []
+    glyphExtract Glyph { _glyphContent = GlyphComposite compositions _ } =
+        concatMap composeGlyph $ V.toList compositions
+    glyphExtract Glyph { _glyphContent = GlyphSimple countour } =
+        [ VU.map (\(x, y) -> (toPixelCoordinate baseX x, toPixelCoordinate (0 :: Int) y)) c
+                | c <- extractFlatOutline countour]
+
+ src/Graphics/Text/TrueType/Bytecode.hs view
@@ -0,0 +1,122 @@+module Graphics.Text.TrueType.Bytecode where
+
+import Data.Word( Word8, Word16 )
+import qualified Data.Vector.Unboxed as VU
+
+type InstructionFlag = Bool
+
+data Instruction =
+      NPUSHB (VU.Vector Word8)
+    | NPUSHW (VU.Vector Word16)
+
+    -- | Set Vectors To Coordinate axis.
+    -- Apply to freedom & projection vector
+    | SVTCA InstructionFlag
+    -- | Set projection vector to coordinate axis.
+    -- Apply to projection vector
+    | SPVTCA InstructionFlag
+    -- | Set Freedom Vector To Coordinate Axis.
+    -- Apply to the freedom vector
+    | SFVTCA InstructionFlag
+    -- | Set Projection Vector To Line.
+    -- Apply to projection vector
+    | SPVTL InstructionFlag
+    -- | Set Freedom Vector To Line.
+    -- Apply to freedom vector
+    | SFVTL InstructionFlag
+    -- | Set Freedom Vector To Projection Vector.
+    -- Apply to freedom vector
+    | SFVTPV
+ 	-- | Set Dual Projection Vector To Line
+ 	-- dual projection vector
+ 	| SDPVTL
+ 	-- | Set Projection Vector To Line.
+ 	-- Apply to projection vector
+ 	| SVPTL
+ 	-- | Set Projection Vector From Stack.
+ 	-- Apply to projection vector
+ 	| SPVFS
+ 	-- | Set Freedom Vector From Stack.
+ 	-- Apply to freedom vector
+ 	| SFVFS
+ 	-- | Set reference point 0.
+ 	-- Apply to rp0
+ 	| SRP0
+ 	-- | Set reference point 1.
+ 	-- Apply to rp1
+ 	| SRP1
+ 	-- | Set reference point 2.
+ 	-- Apply to rp2
+ 	| SRP2
+ 	-- | Set zone pointer 0.
+ 	-- Apply to zp0.
+ 	| SZP0
+ 	-- | Set zone pointer 1.
+ 	-- Apply to zp1.
+ 	| SZP1
+ 	-- | Set zone pointer 2.
+ 	-- Apply to zp2.
+ 	| SZP2
+ 	-- | Set zone pointers
+ 	-- Apply to zp0, zp1, zp2
+ 	| SZPS
+    -- | Round To Half Grid.
+    -- Apply to round state.
+    | RTHG
+    -- | Round To Grid.
+    -- Apply to round state.
+    | RTG
+    -- | Round To Double Grid.
+    -- Apply to round state.
+    | RTDG
+    -- | Round up To Grid.
+    -- Apply to round state.
+    | RUTG
+    -- | Round down to grid.
+    -- Apply to round state.
+    | RDTG
+    -- | Set rounding off
+    -- Apply to rounds tate.
+    | ROFF
+    -- | Super round
+    -- Apply to rounds tate.
+    | SROUND
+    -- | Super 45 round
+    -- Apply to rounds tate.
+    | S45ROUND
+    -- | Set loop.
+    -- Apply to loop
+    | SLOOP
+    -- | Set Single Width Cut-In
+    -- Apply to single width cut-in
+    | SSWCI
+    -- | Set Control Value Table Cut-In
+    -- Apply to control value cut-in
+    | SCVTCI
+    -- | Set Minimum Distance
+    -- Apply to minimum distance
+    | SMD
+    -- | Get Freedom vector
+    | GFV
+    -- | Get Projection vector
+    | GPV
+    -- | Get Information
+    | GETINFO
+    -- | Measure Pixels Per EM
+    | MPPEM
+    -- | Measure Point size
+    | MPS
+
+    -- | If test
+    | IF
+    -- | Else
+    | ELSE
+    -- | Jump relative on false
+    | JROF
+    -- | Jump relative on true
+    | JROT
+    -- | Jump relative
+    | JMPR
+    -- | Loop and call
+    | LOOPCALL
+
+ src/Graphics/Text/TrueType/CharacterMap.hs view
@@ -0,0 +1,358 @@+{-# LANGUAGE RankNTypes #-}
+module Graphics.Text.TrueType.CharacterMap
+    ( TtfEncoding( .. )
+    , CharacterMaps
+    , LangId
+    , findCharGlyph
+    ) where
+
+import Control.Monad( replicateM, forM )
+import Control.Applicative( (<$>), (<*>) )
+import Control.Monad( when )
+import Data.Binary( Binary( .. ) )
+import Data.Binary.Get( Get
+                      , skip
+                      , bytesRead
+                      , getWord8
+                      , getWord16be
+                      , getWord32be )
+
+import Data.Binary.Put( putWord16be
+                      , putWord32be )
+import Data.Int( Int16 )
+import Data.List( find, sortBy )
+import qualified Data.Map as M
+import Data.Maybe( fromMaybe )
+import Data.Word( Word8, Word16, Word32 )
+import Data.Ord( comparing )
+
+import qualified Data.Vector as V
+import qualified Data.Vector.Unboxed as VU
+
+import Graphics.Text.TrueType.LanguageIds
+
+{-import Debug.Trace-}
+{-import Text.Printf-}
+
+--------------------------------------------------
+----            TtfEncoding
+--------------------------------------------------
+data TtfEncoding
+  = EncodingSymbol
+  | EncodingUnicode
+  | EncodingShiftJIS
+  | EncodingBig5
+  | EncodingPRC
+  | EncodingWansung
+  | EncodingJohab
+  deriving (Eq, Show)
+
+instance Binary TtfEncoding where
+    put EncodingSymbol = putWord16be 0
+    put EncodingUnicode = putWord16be 1
+    put EncodingShiftJIS = putWord16be 2
+    put EncodingBig5 = putWord16be 3
+    put EncodingPRC = putWord16be 4
+    put EncodingWansung = putWord16be 5
+    put EncodingJohab = putWord16be 6
+
+    get = do
+      v <- getWord16be
+      case v of
+        0 -> return EncodingSymbol
+        1 -> return EncodingUnicode
+        2 -> return EncodingShiftJIS
+        3 -> return EncodingBig5
+        4 -> return EncodingPRC
+        5 -> return EncodingWansung
+        6 -> return EncodingJohab
+        _ -> fail "Unknown encoding"
+
+class CharMappeable a where
+  -- | Given a group a character, return a valid glyph
+  -- id and the rest of the string.
+  glyphIdFromTable :: a -> Char -> Int
+
+  -- | Given a table, retrieve the language code
+  langIdOfCharMap :: a -> LangId
+
+type LangId = Word16
+
+--------------------------------------------------
+----            CharacterMaps
+--------------------------------------------------
+data CharacterMap = CharacterMap
+    { _charMapPlatformId       :: !PlatformId
+    , _charMapPlatformSpecific :: !Word16
+    , _charMap                 :: !CharacterTable
+    }
+    deriving (Eq, Show)
+
+instance Ord CharacterMap where
+    compare = comparing _charMap
+
+newtype CharacterMaps = CharacterMaps [CharacterMap]
+    deriving (Eq, Show)
+
+instance Binary CharacterMaps where
+  put _ = fail "Unimplemented"
+  get = do
+    startIndex <- bytesRead
+    versionNumber <- getWord16be
+    when (versionNumber /= 0)
+         (fail "Characte map - invalid version number")
+    tableCount <- fromIntegral <$> getWord16be
+    tableDesc <- replicateM tableCount $
+        (,,) <$> get <*> getWord16be <*> getWord32be
+
+    tables <-
+      forM tableDesc $ \(platformId, platformSpecific, offset) -> do
+         currentOffset <- fromIntegral <$> bytesRead
+         let toSkip = fromIntegral offset - currentOffset + startIndex
+         when (toSkip > 0)
+              (skip $ fromIntegral toSkip)
+         CharacterMap platformId platformSpecific <$> get
+    return . CharacterMaps $ sortBy (comparing _charMap) tables
+
+data CharMapOffset = CharMapOffset
+    { _cmoPlatformId :: !Word16
+    , _cmoEncodingId :: !TtfEncoding
+    , _cmoOffset     :: !Word32
+    }
+    deriving (Eq, Show)
+
+instance Binary CharMapOffset where
+    get = CharMapOffset <$> getWord16be <*> get <*> getWord32be
+    put (CharMapOffset platform encoding offset) =
+      putWord16be platform >> put encoding >> putWord32be offset
+
+--------------------------------------------------
+----            CharacterTable
+--------------------------------------------------
+data CharacterTable
+    = TableFormat0 !Format0
+    | TableFormat2 !Format2
+    | TableFormat4 !Format4
+    | TableFormat6 !Format6
+    | TableFormatUnknown !Word16
+    deriving (Eq, Show)
+
+charTableMap :: (forall table . (CharMappeable table) => table -> a)
+             -> CharacterTable -> a
+charTableMap f = go
+  where
+    go (TableFormat0 t) = f t
+    go (TableFormat2 t) = f t
+    go (TableFormat4 t) = f t
+    go (TableFormat6 t) = f t
+    go (TableFormatUnknown v) = f v
+
+findCharGlyph :: CharacterMaps -> LangId -> Char -> Int
+findCharGlyph (CharacterMaps charMaps) langId character =
+    fromMaybe 0 $ find (/= 0)
+        [charTableMap (flip glyphIdFromTable character) m
+                | allMap <- charMaps
+                , let m = _charMap allMap
+                , isLangCompatible m]
+  where
+    isLangCompatible v = tableLang == 0 || tableLang == langId 
+      where tableLang = charTableMap langIdOfCharMap v
+
+instance Ord CharacterTable where
+    compare (TableFormat0 v1) (TableFormat0 v2) =
+        (comparing langIdOfCharMap) v1 v2
+    compare (TableFormat2 v1) (TableFormat2 v2) =
+        (comparing langIdOfCharMap) v1 v2
+    compare (TableFormat4 v1) (TableFormat4 v2) =
+        (comparing langIdOfCharMap) v1 v2
+    compare (TableFormat6 v1) (TableFormat6 v2) =
+        (comparing langIdOfCharMap) v1 v2
+    compare (TableFormat0 _) _ = LT
+    compare (TableFormat2 _) _ = LT
+    compare (TableFormat4 _) _ = LT
+    compare (TableFormat6 _) _ = LT
+    compare _ _ = GT
+
+instance Binary CharacterTable where
+    put _ = fail "Binary.put CharacterTable - Unimplemented"
+    get = do
+      format <- getWord16be
+      case format of
+        0 -> TableFormat0 <$> get
+        2 -> TableFormat2 <$> get
+        4 -> TableFormat4 <$> get
+        6 -> TableFormat6 <$> get
+        n -> return $ TableFormatUnknown n
+
+instance CharMappeable Word16 where
+  glyphIdFromTable _ _ = 0
+  langIdOfCharMap _ = 0
+
+
+--------------------------------------------------
+----            Format4
+--------------------------------------------------
+data Format4 = Format4
+    { _f4Language :: {-# UNPACK #-} !LangId
+    , _f4Map :: M.Map Word16 Word16
+    }
+    deriving (Eq, Show)
+
+instance CharMappeable Format4 where
+  glyphIdFromTable tab v =
+      fromIntegral . M.findWithDefault 0 wc $ _f4Map tab
+        where wc = fromIntegral $ fromEnum v
+  langIdOfCharMap = _f4Language
+
+instance Binary Format4 where
+  put _ = error "put Format4 - unimplemented"
+  get = do
+      startIndex <- bytesRead
+      tableLength <- fromIntegral <$> getWord16be
+      language <- getWord16be
+      -- 2 * segCount
+      segCount <- (`div` 2) . fromIntegral <$> getWord16be
+      -- 2 * (2**FLOOR(log2(segCount)))
+      _searchRange <- getWord16be
+      -- log2(searchRange/2)
+      _entrySelector <- getWord16be
+      -- (2 * segCount) - searchRange
+      _rangeShift <- getWord16be
+
+      let fetcher :: Get (VU.Vector Int)
+          fetcher =
+            VU.replicateM segCount (fromIntegral <$> getWord16be)
+
+      endCodes <- fetcher
+      _reservedPad <- getWord16be
+      startCodes <- fetcher
+      idDelta <-
+          VU.replicateM segCount (fromIntegral <$> getWord16be) :: Get (VU.Vector Int16)
+      idRangeOffset <- fetcher
+
+      tableBeginIndex <- bytesRead
+      let idDeltaInt = VU.map fromIntegral idDelta
+          rangeInfo = init . VU.toList $
+              VU.zip5 startCodes endCodes idDeltaInt idRangeOffset $
+                   VU.enumFromN 0 segCount
+
+          indexLeft = fromIntegral $
+              (tableLength - (tableBeginIndex - startIndex + 2))
+                  `div` 2
+
+      indexTable <- VU.replicateM indexLeft getWord16be
+
+      return . Format4 language . M.fromList
+             $ concatMap (prepare segCount indexTable) rangeInfo
+    where
+      prepare _ _ (start, end, delta, 0, _) =
+        [(fromIntegral $ char, fromIntegral $ char + delta)
+                    | char <- [start .. end]]
+      prepare segCount indexTable
+              (start, end, delta, rangeOffset, ix) =
+        -- this is... so convoluted oO
+        [( fromIntegral char
+         , fromIntegral (if glyphId == 0 then 0 else glyphId + fromIntegral delta))
+              | char <- [start .. end]
+              , let index =
+                      (rangeOffset `div` 2) + (char - start) + ix
+                            - segCount
+              , index < VU.length indexTable
+              , let glyphId = indexTable VU.! index
+              ]
+
+--------------------------------------------------
+----            Format0
+--------------------------------------------------
+data Format0 = Format0
+    { _format0Language :: {-# UNPACK #-} !LangId
+    , _format0Table    :: !(VU.Vector Word8)
+    }
+    deriving (Eq, Show)
+
+instance CharMappeable Format0 where
+  glyphIdFromTable Format0 { _format0Table = table } v
+    | ic > VU.length table = 0
+    | otherwise = fromIntegral $ table VU.! ic
+        where ic = fromEnum v
+        
+  langIdOfCharMap = _format0Language
+
+instance Binary Format0 where
+    put _ = fail "Binary.Format0.put - unimplemented"
+    get = do
+        tableSize <- getWord16be
+        when (tableSize /= 262) $
+            fail "table cmap format 0 : invalid size"
+        Format0 <$> getWord16be <*> VU.replicateM 256 getWord8
+
+--------------------------------------------------
+----            Format2
+--------------------------------------------------
+data Format2 = Format2
+    { _format2Language   :: {-# UNPACK #-} !LangId
+    , _format2SubKeys    :: !(VU.Vector Word16)
+    , _format2SubHeaders :: !(V.Vector Format2SubHeader)
+    }
+    deriving (Eq, Show)
+
+data Format2SubHeader = Format2SubHeader
+    { _f2SubCode       :: {-# UNPACK #-} !Word16
+    , _f2EntryCount    :: {-# UNPACK #-} !Word16
+    , _f2IdDelta       :: {-# UNPACK #-} !Int16
+    , _f2IdRangeOffset :: {-# UNPACK #-} !Word16
+    }
+    deriving (Eq, Show)
+
+instance CharMappeable Format2 where
+  glyphIdFromTable _ _ = 0
+  langIdOfCharMap = _format2Language
+
+instance Binary Format2SubHeader where
+    put (Format2SubHeader a b c d) =
+        p16 a >> p16 b >> pi16 c >> p16 d
+      where
+        p16 = putWord16be
+        pi16 = p16 . fromIntegral
+
+    get = Format2SubHeader <$> g16 <*> g16 <*> (fromIntegral <$> g16) <*> g16
+      where g16 = getWord16be
+
+
+instance Binary Format2 where
+    put _ = fail "Format2.put - unimplemented"
+    get = do
+      _tableSize <- getWord16be
+      lang <- getWord16be
+      subKeys <- VU.map (`div` 8) <$> VU.replicateM 256 getWord16be
+      let maxSubIndex = VU.maximum subKeys
+      subHeaders <- V.replicateM (fromIntegral maxSubIndex) get
+      -- TODO finish the parsing of format 2
+      return $ Format2 lang subKeys subHeaders
+
+--------------------------------------------------
+----            Format6
+--------------------------------------------------
+data Format6 = Format6
+    { _format6Language   :: {-# UNPACK #-} !LangId
+    , _format6FirstCode  :: {-# UNPACK #-} !Word16
+    , _format6ArrayIndex :: !(VU.Vector Word16)
+    }
+    deriving (Eq, Show)
+
+instance CharMappeable Format6 where
+  glyphIdFromTable Format6 { _format6ArrayIndex = table } v
+    | ic < VU.length table = fromIntegral $ table VU.! ic
+    | otherwise = 0
+      where ic = fromEnum v
+
+  langIdOfCharMap = _format6Language
+
+instance Binary Format6 where
+    put _ = fail "Format6.put - unimplemented"
+    get = do
+        _length <- getWord16be
+        language <- getWord16be
+        firstCode <- getWord16be
+        entryCount <- fromIntegral <$> getWord16be
+        Format6 language firstCode <$> VU.replicateM entryCount getWord16be
+ src/Graphics/Text/TrueType/Glyph.hs view
@@ -0,0 +1,305 @@+{-# LANGUAGE TupleSections #-}
+module Graphics.Text.TrueType.Glyph
+    ( GlyphHeader( .. )
+    , GlyphContour( .. )
+    , CompositeScaling( .. )
+    , GlyphComposition( .. )
+    , GlyphContent( .. )
+    , Glyph( .. )
+    , GlyphFlag( .. )
+    , extractFlatOutline
+    , emptyGlyph
+    ) where
+
+import Control.Applicative( (<$>), (<*>) )
+import Data.Bits( setBit, testBit, shiftL )
+import Data.Int( Int16 )
+import Data.List( mapAccumL, mapAccumR, zip4 )
+import Data.Monoid( mempty )
+import Data.Word( Word8, Word16 )
+import Data.Binary( Binary( .. ) )
+import Data.Binary.Get( Get
+                      , getWord8
+                      , getWord16be )
+
+import Data.Binary.Put( putWord8, putWord16be )
+import Data.Tuple( swap )
+
+import qualified Data.Vector as V
+import qualified Data.Vector.Unboxed as VU
+
+{-import Text.Printf-}
+{-import Debug.Trace-}
+
+data GlyphHeader = GlyphHeader
+    { -- | If the number of contours is greater than or equal
+      -- to zero, this is a single glyph; if negative, this is
+      -- a composite glyph.
+      _glfNumberOfContours :: {-# UNPACK #-} !Int16
+      -- | Minimum x for coordinate data.
+    , _glfXMin             :: {-# UNPACK #-} !Int16
+      -- | Minimum y for coordinate data.
+    , _glfYMin             :: {-# UNPACK #-} !Int16
+      -- | Maximum x for coordinate data.
+    , _glfXMax             :: {-# UNPACK #-} !Int16
+      -- | Maximum y for coordinate data.
+    , _glfYMax             :: {-# UNPACK #-} !Int16
+    }
+    deriving (Eq, Show)
+
+emptyGlyphHeader :: GlyphHeader
+emptyGlyphHeader = GlyphHeader 0 0 0 0 0
+
+instance Binary GlyphHeader where
+    get = GlyphHeader <$> g16 <*> g16 <*> g16 <*> g16 <*> g16
+      where g16 = fromIntegral <$> getWord16be
+
+    put (GlyphHeader count xmini ymini xmaxi ymaxi) =
+        p16 count >> p16 xmini >> p16 ymini >> p16 xmaxi >> p16 ymaxi
+      where p16 = putWord16be . fromIntegral
+
+data GlyphContour = GlyphContour
+    { _glyphInstructions :: !(VU.Vector Word8)
+    , _glyphFlags        :: ![GlyphFlag]
+    , _glyphPoints       :: ![VU.Vector (Int16, Int16)]
+    }
+    deriving (Eq, Show)
+
+data CompositeScaling = CompositeScaling
+    { _a :: {-# UNPACK #-} !Int16
+    , _b :: {-# UNPACK #-} !Int16
+    , _c :: {-# UNPACK #-} !Int16
+    , _d :: {-# UNPACK #-} !Int16
+    , _e :: {-# UNPACK #-} !Int16
+    , _f :: {-# UNPACK #-} !Int16
+    }
+    deriving (Eq, Show)
+
+data GlyphComposition = GlyphComposition
+    { _glyphCompositeFlag    :: {-# UNPACK #-} !Word16
+    , _glyphCompositeIndex   :: {-# UNPACK #-} !Word16
+    , _glyphCompositionArg   :: {-# UNPACK #-} !(Int16, Int16)
+    , _glyphCompositionScale :: !CompositeScaling
+    }
+    deriving (Eq, Show)
+
+data GlyphContent
+    = GlyphEmpty
+    | GlyphSimple    !GlyphContour
+    | GlyphComposite !(V.Vector GlyphComposition) !(VU.Vector Word8)
+    deriving (Eq, Show)
+
+data Glyph = Glyph
+    { _glyphHeader  :: !GlyphHeader
+    , _glyphContent :: !GlyphContent
+    }
+    deriving (Eq, Show)
+
+emptyGlyph :: Glyph
+emptyGlyph = Glyph emptyGlyphHeader GlyphEmpty
+
+getCompositeOutline :: Get GlyphContent
+getCompositeOutline =
+    (\(instr, vals) -> GlyphComposite (V.fromList vals) instr) <$> go
+  where
+    go = do
+      flag <- getWord16be
+      index <- getWord16be
+      args <- fetchArguments flag
+      scaling <- fetchScaling flag
+      let fullScaling = fetchOffset scaling args flag
+          value = GlyphComposition flag index args fullScaling
+
+      if flag `testBit` mORE_COMPONENTS then
+          (\(instr, acc) -> (instr, value : acc )) <$> go
+      else
+        if flag `testBit` wE_HAVE_INSTRUCTIONS then do
+            count <- fromIntegral <$> getWord16be
+            (, [value]) <$> VU.replicateM count getWord8
+        else
+            return (mempty, [value])
+
+
+    fetchArguments flag
+        | flag `testBit` aRG_1_AND_2_ARE_WORDS =
+            (,) <$> getInt16be <*> getInt16be
+        | otherwise =
+            (,) <$> getInt8 <*> getInt8
+
+    fetchScaling flag
+        | flag `testBit` wE_HAVE_A_SCALE =
+            (\v -> CompositeScaling v 0 0 v) <$> getF2Dot14
+        | flag `testBit` wE_HAVE_AN_X_AND_Y_SCALE =
+            (\x y -> CompositeScaling x 0 0 y) <$> getF2Dot14 <*> getF2Dot14
+        | flag `testBit` wE_HAVE_A_TWO_BY_TWO =
+            CompositeScaling <$> getF2Dot14 <*> getF2Dot14
+                             <*> getF2Dot14 <*> getF2Dot14
+        | otherwise = return $ CompositeScaling one 0 0 one
+           where one = 1 `shiftL` 14
+
+    fetchOffset scaling (a1, a2) flag
+        | flag `testBit` aRGS_ARE_XY_VALUES = scaling a1 a2
+        | otherwise = scaling 0 0 -- TODO fix this crap
+
+    {--
+    if (!ARGS_ARE_XY_VALUES)
+        1st short contains the index of matching point in compound being constructed
+        2nd short contains index of matching point in component -}
+
+    getInt16be = fromIntegral <$> getWord16be
+    getF2Dot14 = fromIntegral <$> getWord16be
+    getInt8 = fromIntegral <$> getWord8
+
+    aRG_1_AND_2_ARE_WORDS  = 0
+    aRGS_ARE_XY_VALUES  = 1
+    {-rOUND_XY_TO_GRID  = 2-}
+    wE_HAVE_A_SCALE  = 3
+    {-rESERVED  = 4-}
+    mORE_COMPONENTS  = 5
+    wE_HAVE_AN_X_AND_Y_SCALE  = 6
+    wE_HAVE_A_TWO_BY_TWO  = 7
+    wE_HAVE_INSTRUCTIONS  = 8
+    {-uSE_MY_METRICS  = 9-}
+    {-oVERLAP_COMPOUND  = 10-}
+    {-sCALED_COMPONENT_OFFSET  = 11-}
+    {-uNSCALED_COMPONENT_OFFSET = 12-}
+
+data GlyphFlag = GlyphFlag
+    { -- | If set, the point is on the curve;
+      -- otherwise, it is off the curve.
+      _flagOnCurve :: !Bool
+      -- | If set, the corresponding x-coordinate is 1
+      -- byte long. If not set, 2 bytes.
+    , _flagXshort  :: !Bool
+      -- | If set, the corresponding y-coordinate is 1
+      -- byte long. If not set, 2 bytes.
+    , _flagYShort  :: !Bool
+      -- | If set, the next byte specifies the number of additional
+      -- times this set of flags is to be repeated. In this way, the
+      -- number of flags listed can be smaller than the number of
+      -- points in a character.
+    , _flagRepeat  :: !Bool
+      -- | This flag has two meanings, depending on how the x-Short
+      -- Vector flag is set. If x-Short Vector is set, this bit
+      -- describes the sign of the value, with 1 equalling positive and
+      -- 0 negative. If the x-Short Vector bit is not set and this bit is set,
+      -- then the current x-coordinate is the same as the previous x-coordinate.
+      -- If the x-Short Vector bit is not set and this bit is also not set, the
+      -- current x-coordinate is a signed 16-bit delta vector.
+    , _flagXSame   :: !Bool
+      -- | This flag has two meanings, depending on how the y-Short Vector flag
+      -- is set. If y-Short Vector is set, this bit describes the sign of the
+      -- value, with 1 equalling positive and 0 negative. If the y-Short Vector
+      -- bit is not set and this bit is set, then the current y-coordinate is the
+      -- same as the previous y-coordinate. If the y-Short Vector bit is not set
+      -- and this bit is also not set, the current y-coordinate is a signed
+      -- 16-bit delta vector.
+    , _flagYSame   :: !Bool
+    }
+    deriving (Eq, Show)
+
+instance Binary GlyphFlag where
+    put (GlyphFlag a0 a1 a2 a3 a4 a5) =
+        putWord8 . foldl setter 0 $ zip [0..] [a0, a1, a2, a3, a4, a5]
+      where setter v ( _, False) = v
+            setter v (ix, True) = setBit v ix
+    get = do
+      tester <- testBit <$> getWord8
+      return $ GlyphFlag
+        { _flagOnCurve = tester 0
+        , _flagXshort  = tester 1
+        , _flagYShort  = tester 2
+        , _flagRepeat  = tester 3
+        , _flagXSame   = tester 4
+        , _flagYSame   = tester 5
+        }
+
+getGlyphFlags :: Int -> Get [GlyphFlag]
+getGlyphFlags count = go 0
+  where
+    go n | n >= count = return []
+    go n = do
+      flag <- get
+      if _flagRepeat flag
+        then do
+          repeatCount <- fromIntegral <$> getWord8
+          let real = min (count - n) (repeatCount + 1)
+          (replicate real flag ++) <$> go (n + real)
+        else (flag :) <$> go (n + 1)
+
+getCoords :: [GlyphFlag] -> Get (VU.Vector (Int16, Int16))
+getCoords flags =
+    VU.fromList <$> (zip <$> go (_flagXSame, _flagXshort) 0 flags
+                         <*> go (_flagYSame, _flagYShort) 0 flags)
+  where
+    go _ _ [] = return []
+    go axx@(isSame, isShort) prevCoord (flag:flagRest) = do
+        let fetcher
+              | isShort flag && isSame flag =
+                  (prevCoord +) . fromIntegral <$> getWord8
+              | isShort flag = 
+                  (prevCoord - ) . fromIntegral <$> getWord8
+              | isSame flag =
+                  return prevCoord
+              | otherwise =
+                  (prevCoord +) . fromIntegral <$> getWord16be
+
+        newCoord <- fetcher
+        (newCoord :) <$> go axx newCoord flagRest
+
+extractFlatOutline :: GlyphContour
+                   -> [VU.Vector (Int16, Int16)]
+extractFlatOutline contour = map go $ zip flagGroup coords
+  where
+    allFlags = _glyphFlags contour
+    coords = _glyphPoints contour
+    (_, flagGroup) =
+      mapAccumL (\acc v -> swap $ splitAt (VU.length v) acc) allFlags coords
+
+    go (flags, coord) 
+      | VU.null coord = mempty
+      | otherwise = VU.fromList . (firstPoint :) $ expand mixed
+      where
+       isOnSide = map _flagOnCurve flags
+       firstOnCurve = head isOnSide
+       lst@(firstPoint:xs) = VU.toList coord
+       mixed = zip4 isOnSide (tail isOnSide) lst xs
+       midPoint (x1, y1) (x2, y2) =
+           ((x1 + x2) `div` 2, (y1 + y2) `div` 2)
+
+       expand [] = []
+       expand [(onp, on, prevPoint, currPoint)]
+        | onp == on = (prevPoint `midPoint` currPoint) : endJunction
+        | otherwise = endJunction
+         where endJunction 
+                | on && firstOnCurve =
+                    [currPoint, currPoint `midPoint` firstPoint, firstPoint]
+                | otherwise = [currPoint, firstPoint]
+       expand ((onp, on, prevPoint, currPoint):rest)
+        | onp == on = prevPoint `midPoint` currPoint : currPoint : expand rest
+        | otherwise = currPoint : expand rest
+
+getSimpleOutline :: Int16 -> Get GlyphContent
+getSimpleOutline counterCount = do
+    endOfPoints <- VU.replicateM (fromIntegral counterCount) getWord16be
+    let pointCount = VU.last endOfPoints + 1
+    instructionCount <- fromIntegral <$> getWord16be
+    instructions <- VU.replicateM instructionCount getWord8
+
+    flags <- getGlyphFlags $ fromIntegral pointCount
+    GlyphSimple . GlyphContour instructions flags
+                . breakOutline endOfPoints <$> getCoords flags
+  where
+    prepender (v, lst) = v : lst
+    breakOutline endPoints coords =
+        prepender . mapAccumR breaker coords . VU.toList $ VU.init endPoints
+          where breaker array ix = VU.splitAt (fromIntegral ix + 1) array
+
+instance Binary Glyph where
+    put _ = fail "Glyph.put - unimplemented"
+    get = do
+      hdr <- get
+      case _glfNumberOfContours hdr of
+        -1 -> Glyph hdr <$> getCompositeOutline
+        n -> Glyph hdr <$> getSimpleOutline n
+
+ src/Graphics/Text/TrueType/Header.hs view
@@ -0,0 +1,108 @@+module Graphics.Text.TrueType.Header
+    ( FontHeader( .. )
+    , HeaderFlags( .. )
+    ) where
+
+import Control.Applicative( (<$>), (<*>) )
+import Data.Bits( setBit, testBit )
+import Data.Binary( Binary( .. ) )
+import Data.Binary.Get( getWord16be
+                      , getWord32be
+                      , getWord64be
+                      )
+
+import Data.Binary.Put( putWord16be)
+
+import Data.Int( Int16 )
+import Data.List( foldl' )
+import Data.Word( Word16, Word32, Word64 )
+
+import Graphics.Text.TrueType.Types
+
+data FontHeader = FontHeader
+    { -- | Table version number	0x00010000 for version 1.0.
+      _fHdrVersionNumber    :: !Fixed
+      -- | fontRevision	Set by font manufacturer.
+    , _fHdrFontRevision     :: !Fixed
+      -- | To compute:  set it to 0, sum the entire font as
+      -- ULONG, then store 0xB1B0AFBA - sum.
+    , _fHdrChecksumAdjust   :: !Word32
+     
+      -- | Should be equal to 0x5F0F3CF5.
+    , _fHdrMagicNumber      :: !Word32
+    , _fHdrFlags            :: !HeaderFlags
+
+      -- | Valid range is from 16 to 16384
+    , _fUnitsPerEm          :: !Word16
+      -- | International date (8-byte field).
+    , _fHdrCreateTime       :: !Word64
+      -- | International date (8-byte field).
+    , _fHdrModificationTime :: !Word64
+
+      -- | For all glyph bounding boxes.
+    , _fHdrxMin             :: !FWord
+      -- | For all glyph bounding boxes.
+    , _fHdrYMin             :: !FWord
+      -- | For all glyph bounding boxes.
+    , _fHdrXMax             :: !FWord
+      -- | For all glyph bounding boxes.
+    , _fHdrYMax             :: !FWord
+      -- | Bit 0 bold (if set to 1); Bit 1 italic (if set to 1)
+    , _fHdrMacStyle         :: !Word16
+      -- | Smallest readable size in pixels.
+    , _fHdrLowestRecPPEM    :: !Word16
+
+      -- | 0   Fully mixed directional glyphs;
+      --  1   Only strongly left to right;
+      --  2   Like 1 but also contains neutrals ;
+      -- -1   Only strongly right to left;
+      -- -2   Like -1 but also contains neutrals.
+    , _fHdrFontDirectionHint :: !Int16
+      -- | 0 for short offsets, 1 for long.
+    , _fHdrIndexToLocFormat  :: !Int16
+      -- | 0 for current format.
+    , _fHdrGlyphDataFormat   :: !Int16
+    }
+    deriving (Eq, Show)
+
+instance Binary FontHeader where
+  put _ = fail "Unimplemented"
+  get =
+    FontHeader <$> get <*> get <*> g32 <*> g32 <*> get
+               <*> g16 <*> g64 <*> g64 <*> get <*> get
+               <*> get <*> get <*> g16 <*> g16 <*> gi16
+               <*> gi16 <*> gi16
+      where g16 = getWord16be
+            g32 = getWord32be
+            gi16 = fromIntegral <$> getWord16be
+            g64 = getWord64be
+
+
+data HeaderFlags = HeaderFlags
+    { -- | Bit 0 - baseline for font at y=0;
+      _hfBaselineY0           :: !Bool
+      -- | Bit 1 - left sidebearing at x=0;
+    , _hfLeftSideBearing      :: !Bool
+      -- | Bit 2 - instructions may depend on point size;
+    , _hfInstrDependPointSize :: !Bool
+      -- | Bit 3 - force ppem to integer values for all internal
+      -- scaler math; may use fractional ppem sizes if this bit
+      -- is clear;
+    , _hfForcePPEM            :: !Bool
+      -- | Bit 4 - instructions may alter advance width (the
+      -- advance widths might not scale linearly);
+    , _hfAlterAdvance         :: !Bool
+    }
+    deriving (Eq, Show)
+
+instance Binary HeaderFlags where
+    get = do
+      flags <- getWord16be
+      let at ix = flags `testBit` ix
+      return $ HeaderFlags (at 0) (at 1) (at 2) (at 3) (at 4)
+
+    put (HeaderFlags a0 a1 a2 a3 a4) =
+      putWord16be . foldl' setter 0 $ zip [0..] [a0, a1, a2, a3, a4]
+        where setter acc (_, False) = acc
+              setter acc (ix, True) = setBit acc ix
+
+ src/Graphics/Text/TrueType/HorizontalInfo.hs view
@@ -0,0 +1,108 @@+module Graphics.Text.TrueType.HorizontalInfo
+    ( HorizontalHeader( .. )
+    , HorizontalMetricsTable( .. )
+    , HorizontalMetric( .. )
+    , getHorizontalMetrics
+    ) where
+
+import Control.Applicative( (<$>), (<*>) )
+import Control.Monad( when, replicateM_ )
+import Data.Word( Word16 )
+import Data.Int( Int16 )
+import Data.Binary( Binary( .. ) )
+import Data.Binary.Get( Get, getWord16be, getWord32be )
+import Data.Binary.Put( putWord16be, putWord32be )
+import qualified Data.Vector as V
+
+import Graphics.Text.TrueType.Types
+
+data HorizontalHeader = HorizontalHeader
+    { -- | Distance from baseline of highest ascender
+      _hheaAscent  :: {-# UNPACK #-} !FWord
+      -- | Distance from baseline of lowest descender
+    , _hheaDescent :: {-# UNPACK #-} !FWord
+      -- | typographic line gap
+    , _hheaLineGap :: {-# UNPACK #-} !FWord
+      -- | must be consistent with horizontal metrics
+    , _hheaAdvanceWidthMax :: {-# UNPACK #-} !FWord
+      -- | must be consistent with horizontal metrics
+    , _hheaMinLeftSideBearing :: {-# UNPACK #-} !FWord
+      -- | must be consistent with horizontal metrics
+    , _hheaMinRightSideBearing :: {-# UNPACK #-} !FWord
+      -- | max(lsb + (xMax-xMin))
+    , _hheaXmaxExtent :: {-# UNPACK #-} !FWord
+      -- | used to calculate the slope of the caret 
+      -- (rise/run) set to 1 for vertical caret
+    , _hheaCaretSlopeRise :: {-# UNPACK #-} !Int16
+      -- | 0 for vertical
+    , _hheaCaretSlopeRun :: {-# UNPACK #-} !Int16
+      -- | set value to 0 for non-slanted fonts
+    , _hheaCaretOffset  :: {-# UNPACK #-} !FWord
+      -- | 0 for current format
+    , _hheaMetricDataFormat :: {-# UNPACK #-} !Int16
+      -- | number of advance widths in metrics table
+    , _hheaLongHorMetricCount :: {-# UNPACK #-} !Word16
+    }
+    deriving (Eq, Show)
+
+instance Binary HorizontalHeader where
+  put hdr = do
+    putWord32be 0x00010000
+    put $ _hheaAscent hdr
+    put $ _hheaDescent hdr
+    put $ _hheaLineGap hdr
+    put $ _hheaAdvanceWidthMax hdr
+    put $ _hheaMinLeftSideBearing hdr
+    put $ _hheaMinRightSideBearing hdr
+    put $ _hheaXmaxExtent hdr
+    putWord16be . fromIntegral $ _hheaCaretSlopeRise hdr
+    putWord16be . fromIntegral $ _hheaCaretSlopeRun hdr
+    put $ _hheaCaretOffset hdr
+    replicateM_ 4 $ putWord16be 0
+    putWord16be . fromIntegral $ _hheaMetricDataFormat hdr
+    putWord16be $ _hheaLongHorMetricCount hdr
+
+  get = do
+    ver <- getWord32be
+    when (ver /= 0x00010000)
+         (fail "Invalid HorizontalHeader (hhea) version")
+    startHdr <- HorizontalHeader
+            <$> get <*> get <*> get <*> get
+            <*> get <*> get <*> get
+            <*> (fromIntegral <$> getWord16be)
+            <*> (fromIntegral <$> getWord16be)
+            <*> get
+    replicateM_ 4 getWord16be -- reserved, don't care
+    startHdr <$> (fromIntegral <$> getWord16be)
+             <*> getWord16be
+
+data HorizontalMetric = HorizontalMetric
+    { _hmtxAdvanceWidth    :: {-# UNPACK #-} !Word16
+    , _hmtxLeftSideBearing :: {-# UNPACK #-} !Int16
+    }
+    deriving (Eq, Show)
+
+instance Binary HorizontalMetric where
+    put (HorizontalMetric adv bear) =
+      putWord16be adv >> putWord16be (fromIntegral bear)
+
+    get = HorizontalMetric <$> g16 <*> (fromIntegral <$> g16)
+      where g16 = getWord16be
+
+data HorizontalMetricsTable = HorizontalMetricsTable
+    { _glyphMetrics :: !(V.Vector HorizontalMetric)
+    }
+    deriving (Eq, Show)
+
+getHorizontalMetrics :: Int -- ^ Metrics count
+                     -> Int -- ^ Glyph count
+                     -> Get HorizontalMetricsTable
+getHorizontalMetrics numberOfMetrics glyphCount = do
+    hMetrics <- V.replicateM numberOfMetrics get
+    let lastAdvance = _hmtxAdvanceWidth $ V.last hMetrics
+    run <- V.replicateM sideBearingCount $
+        HorizontalMetric lastAdvance . fromIntegral <$> getWord16be
+    return $ HorizontalMetricsTable $ V.concat [hMetrics, run]
+  where
+    sideBearingCount = glyphCount - numberOfMetrics 
+
+ src/Graphics/Text/TrueType/LanguageIds.hs view
@@ -0,0 +1,324 @@+{-# LANGUAGE FlexibleInstances #-}
+module Graphics.Text.TrueType.LanguageIds
+    ( PlatformId( .. )
+    , UnicodePlatformSpecific( .. )
+    , MacPlatformId( .. )
+    , MacLanguage( .. )
+    ) where
+
+import Control.Applicative( (<$>) )
+import Data.Binary( Binary( .. ) )
+import Data.Binary.Get( getWord16be )
+import Data.Binary.Put( putWord16be )
+import Data.Word( Word16 )
+import qualified Data.Map.Strict as M
+
+data PlatformId
+    = PlatformUnicode   -- ^ 0
+    | PlatformMacintosh -- ^ 1
+    | PlatformISO       -- ^ 2
+    | PlatformWindows   -- ^ 3
+    | PlatformCustom    -- ^ 4
+    | PlatformId Word16
+    deriving (Eq, Show)
+
+instance Binary PlatformId where
+    put = p where
+      p16 = putWord16be
+      p PlatformUnicode = p16 0
+      p PlatformMacintosh = p16 1
+      p PlatformISO = p16 2
+      p PlatformWindows = p16 3
+      p PlatformCustom = p16 4
+      p (PlatformId v) = p16 v
+
+    get = do
+      val <- getWord16be
+      return $ case val of
+        0 -> PlatformUnicode
+        1 -> PlatformMacintosh
+        2 -> PlatformISO
+        3 -> PlatformWindows
+        4 -> PlatformCustom
+        n -> PlatformId n
+
+data UnicodePlatformSpecific
+    = UnicodePlatform1_0
+    | UnicodePlatform1_1
+    | UnicodeISO10645
+    | UnicodeBMPOnly2_0
+    | UnicodeFull2_0
+    | UnicodeVariation
+    | UnicodeFull
+    deriving (Eq, Show)
+
+unicodePlatformSpecificToId :: UnicodePlatformSpecific -> Word16
+unicodePlatformSpecificToId = go
+  where
+    go UnicodePlatform1_0 = 0
+    go UnicodePlatform1_1 = 1
+    go UnicodeISO10645    = 2
+    go UnicodeBMPOnly2_0  = 3
+    go UnicodeFull2_0     = 4
+    go UnicodeVariation   = 5
+    go UnicodeFull        = 6
+
+instance Binary UnicodePlatformSpecific where
+    put = putWord16be . unicodePlatformSpecificToId 
+
+    get = do
+       v <- getWord16be
+       return $ case v of
+          0 -> UnicodePlatform1_0
+          1 -> UnicodePlatform1_1
+          2 -> UnicodeISO10645
+          3 -> UnicodeBMPOnly2_0
+          4 -> UnicodeFull2_0
+          5 -> UnicodeVariation
+          6 -> UnicodeFull
+          _ -> UnicodeFull
+
+data MacPlatformId
+   = MacSpecificRoman
+   | MacSpecificJapanese
+   | MacSpecificChineseTraditional
+   | MacSpecificKorean
+   | MacSpecificArabic
+   | MacSpecificHebrew
+   | MacSpecificGreek
+   | MacSpecificRussian
+   | MacSpecificRSymbol
+   | MacSpecificDevanagari
+   | MacSpecificGurmukhi
+   | MacSpecificGujarati
+   | MacSpecificOriya
+   | MacSpecificBengali
+   | MacSpecificTamil
+   | MacSpecificTelugu
+   | MacSpecificKannada
+   | MacSpecificMalayalam
+   | MacSpecificSinhalese
+   | MacSpecificBurmese
+   | MacSpecificKhmer
+   | MacSpecificThai
+   | MacSpecificLaotian
+   | MacSpecificGeorgian
+   | MacSpecificArmenian
+   | MacSpecificChineseSimplified
+   | MacSpecificTibetan
+   | MacSpecificMongolian
+   | MacSpecificGeez
+   | MacSpecificSlavic
+   | MacSpecificVietnamese
+   | MacSpecificSindhi
+   | MacSpecificUninterpreted
+   deriving (Eq, Ord, Show)
+
+macSpecifcIdList :: [MacPlatformId]
+macSpecifcIdList =
+    [ MacSpecificRoman, MacSpecificJapanese, MacSpecificChineseTraditional
+    , MacSpecificKorean, MacSpecificArabic, MacSpecificHebrew
+    , MacSpecificGreek, MacSpecificRussian, MacSpecificRSymbol
+    , MacSpecificDevanagari, MacSpecificGurmukhi, MacSpecificGujarati
+    , MacSpecificOriya, MacSpecificBengali, MacSpecificTamil
+    , MacSpecificTelugu, MacSpecificKannada, MacSpecificMalayalam
+    , MacSpecificSinhalese, MacSpecificBurmese, MacSpecificKhmer
+    , MacSpecificThai, MacSpecificLaotian, MacSpecificGeorgian
+    , MacSpecificArmenian, MacSpecificChineseSimplified, MacSpecificTibetan
+    , MacSpecificMongolian, MacSpecificGeez, MacSpecificSlavic
+    , MacSpecificVietnamese, MacSpecificSindhi, MacSpecificUninterpreted
+    ]
+
+
+prepareSpecificMaps :: Ord a => [a] -> (M.Map a Word16, M.Map Word16 a)
+prepareSpecificMaps lst = (toWord, toPlatform)
+  where
+    toWord = M.fromList $ zip lst [0 ..]
+    toPlatform = M.fromList $ zip [0 ..] lst
+
+mapSpecifcIdMaps :: ( M.Map MacPlatformId Word16
+                    , M.Map Word16 MacPlatformId )
+mapSpecifcIdMaps = prepareSpecificMaps macSpecifcIdList
+
+instance Binary MacPlatformId where
+    get = finder <$> getWord16be
+      where
+        (_, to) = mapSpecifcIdMaps
+        finder v = M.findWithDefault MacSpecificUninterpreted v to
+
+    put v = putWord16be val
+      where
+        (from, _) = mapSpecifcIdMaps
+        val = M.findWithDefault 32 v from
+
+data MacLanguage
+    = MacLangEnglish
+    | MacLangFrench
+    | MacLangGerman
+    | MacLangItalian
+    | MacLangDutch
+    | MacLangSwedish
+    | MacLangSpanish
+    | MacLangDanish
+    | MacLangPortuguese
+    | MacLangNorwegian
+    | MacLangHebrew
+    | MacLangJapanese
+    | MacLangArabic
+    | MacLangFinnish
+    | MacLangGreek
+    | MacLangInuktitut
+    | MacLangIcelandic
+    | MacLangMaltese
+    | MacLangTurkish
+    | MacLangCroatian
+    | MacLangChineseTraditional
+    | MacLangUrdu
+    | MacLangHindi
+    | MacLangThai
+    | MacLangKorean
+    | MacLangLithuanian
+    | MacLangPolish
+    | MacLangHungarian
+    | MacLangEstonian
+    | MacLangLatvian
+    | MacLangSami
+    | MacLangFaroese
+    | MacLangFarsiPersian
+    | MacLangRussian
+    | MacLangChineseSimplified
+    | MacLangFlemish
+    | MacLangIrishGaelic
+    | MacLangAlbanian
+    | MacLangRomanian
+    | MacLangCzech
+    | MacLangSlovak
+    | MacLangSlovenian
+    | MacLangYiddish
+    | MacLangSerbian
+    | MacLangMacedonian
+    | MacLangBulgarian
+    | MacLangUkrainian
+    | MacLangByelorussian
+    | MacLangUzbek
+    | MacLangKazakh
+    | MacLangAzerbaijaniCyrillic
+    | MacLangAzerbaijaniArabic
+    | MacLangArmenian
+    | MacLangGeorgian
+    | MacLangMoldavian
+    | MacLangKirghiz
+    | MacLangTajiki
+    | MacLangTurkmen
+    | MacLangMongolian
+    | MacLangMongolianCyrillic
+    | MacLangPashto
+    | MacLangKurdish
+    | MacLangKashmiri
+    | MacLangSindhi
+    | MacLangTibetan
+    | MacLangNepali
+    | MacLangSanskrit
+    | MacLangMarathi
+    | MacLangBengali
+    | MacLangAssamese
+    | MacLangGujarati
+    | MacLangPunjabi
+    | MacLangOriya
+    | MacLangMalayalam
+    | MacLangKannada
+    | MacLangTamil
+    | MacLangTelugu
+    | MacLangSinhalese
+    | MacLangBurmese
+    | MacLangKhmer
+    | MacLangLao
+    | MacLangVietnamese
+    | MacLangIndonesian
+    | MacLangTagalong
+    | MacLangMalayRoman
+    | MacLangMalayArabic
+    | MacLangAmharic
+    | MacLangTigrinya
+    | MacLangGalla
+    | MacLangSomali
+    | MacLangSwahili
+    | MacLangKinyarwandaRuanda
+    | MacLangRundi
+    | MacLangNyanjaChewa
+    | MacLangMalagasy
+    | MacLangEsperanto
+    | MacLangWelsh
+    | MacLangBasque
+    | MacLangCatalan
+    | MacLangLatin
+    | MacLangQuenchua
+    | MacLangGuarani
+    | MacLangAymara
+    | MacLangTatar
+    | MacLangUighur
+    | MacLangDzongkha
+    | MacLangJavanese
+    | MacLangSundanese
+    | MacLangGalician
+    | MacLangAfrikaans
+    | MacLangBreton
+    | MacLangScottishGaelic
+    | MacLangManxGaelic
+    | MacLangIrishGaelicWithDot
+    | MacLangTongan
+    | MacLangGreekPolytonic
+    | MacLangGreenlandic
+    | MacLangAzerbaijani
+    deriving (Eq, Ord, Show)
+
+macLangList :: [MacLanguage]
+macLangList =
+    [ MacLangEnglish , MacLangFrench , MacLangGerman , MacLangItalian
+    , MacLangDutch , MacLangSwedish , MacLangSpanish , MacLangDanish
+    , MacLangPortuguese , MacLangNorwegian , MacLangHebrew , MacLangJapanese
+    , MacLangArabic , MacLangFinnish , MacLangGreek , MacLangInuktitut
+    , MacLangIcelandic , MacLangMaltese , MacLangTurkish , MacLangCroatian
+    , MacLangChineseTraditional , MacLangUrdu , MacLangHindi , MacLangThai
+    , MacLangKorean , MacLangLithuanian , MacLangPolish , MacLangHungarian
+    , MacLangEstonian , MacLangLatvian , MacLangSami , MacLangFaroese
+    , MacLangFarsiPersian , MacLangRussian , MacLangChineseSimplified
+    , MacLangFlemish
+    , MacLangIrishGaelic , MacLangAlbanian , MacLangRomanian , MacLangCzech
+    , MacLangSlovak , MacLangSlovenian , MacLangYiddish , MacLangSerbian
+    , MacLangMacedonian , MacLangBulgarian , MacLangUkrainian , MacLangByelorussian
+    , MacLangUzbek , MacLangKazakh , MacLangAzerbaijaniCyrillic
+    , MacLangAzerbaijaniArabic
+    , MacLangArmenian , MacLangGeorgian , MacLangMoldavian , MacLangKirghiz
+    , MacLangTajiki , MacLangTurkmen , MacLangMongolian , MacLangMongolianCyrillic
+    , MacLangPashto , MacLangKurdish , MacLangKashmiri , MacLangSindhi
+    , MacLangTibetan , MacLangNepali , MacLangSanskrit , MacLangMarathi
+    , MacLangBengali , MacLangAssamese , MacLangGujarati , MacLangPunjabi
+    , MacLangOriya , MacLangMalayalam , MacLangKannada , MacLangTamil
+    , MacLangTelugu , MacLangSinhalese , MacLangBurmese , MacLangKhmer
+    , MacLangLao , MacLangVietnamese , MacLangIndonesian , MacLangTagalong
+    , MacLangMalayRoman , MacLangMalayArabic , MacLangAmharic , MacLangTigrinya
+    , MacLangGalla , MacLangSomali , MacLangSwahili , MacLangKinyarwandaRuanda
+    , MacLangRundi , MacLangNyanjaChewa , MacLangMalagasy , MacLangEsperanto
+    , MacLangWelsh , MacLangBasque , MacLangCatalan , MacLangLatin
+    , MacLangQuenchua , MacLangGuarani , MacLangAymara , MacLangTatar
+    , MacLangUighur , MacLangDzongkha , MacLangJavanese , MacLangSundanese
+    , MacLangGalician , MacLangAfrikaans , MacLangBreton , MacLangScottishGaelic
+    , MacLangManxGaelic , MacLangIrishGaelicWithDot , MacLangTongan
+    , MacLangGreekPolytonic , MacLangGreenlandic , MacLangAzerbaijani
+    ]
+
+mapLangIdMaps :: (M.Map MacLanguage Word16, M.Map Word16 MacLanguage)
+mapLangIdMaps = prepareSpecificMaps macLangList
+
+instance Binary MacLanguage where
+    get = finder <$> getWord16be
+      where
+        (_, to) = mapLangIdMaps
+        finder v = M.findWithDefault MacLangEnglish v to
+
+    put v = putWord16be val
+      where
+        (from, _) = mapLangIdMaps
+        val = M.findWithDefault 0 v from
+
+ src/Graphics/Text/TrueType/MaxpTable.hs view
@@ -0,0 +1,51 @@+module Graphics.Text.TrueType.MaxpTable ( MaxpTable( .. ) ) where
+
+import Control.Applicative( (<$>), (<*>) )
+import Data.Word( Word16 )
+import Data.Binary( Binary( .. ) )
+import Data.Binary.Get( getWord16be )
+
+import Graphics.Text.TrueType.Types
+
+data MaxpTable = MaxpTable 
+    { -- | version number	0x00010000 for version 1.0.
+      _maxpTableVersion :: !Fixed
+    -- | The number of glyphs in the font.
+    , _maxpnumGlyphs :: !Word16
+    -- | Maximum points in a non-composite glyph.
+    , _maxpmaxPoints :: !Word16
+    -- | Maximum contours in a non-composite glyph.
+    , _maxpmaxContours :: !Word16
+    -- | Maximum points in a composite glyph.
+    , _maxpmaxCompositePoints :: !Word16
+    -- | Maximum contours in a composite glyph.
+    , _maxpmaxCompositeContours :: !Word16
+    -- | 1 if instructions do not use the twilight zone (Z0), or 2 if instructions do use Z0; should be set to 2 in most cases.
+    , _maxpmaxZones :: !Word16
+    -- | Maximum points used in Z0.
+    , _maxpmaxTwilightPoints :: !Word16
+    -- | Number of Storage Area locations. 
+    , _maxpmaxStorage :: !Word16
+    -- | Number of FDEFs.
+    , _maxpmaxFunctionDefs :: !Word16
+    -- | Number of IDEFs.
+    , _maxpmaxInstructionDefs :: !Word16
+    -- | Maximum stack depth .
+    , _maxpmaxStackElements :: !Word16
+    -- | Maximum byte count for glyph instructions.
+    , _maxpmaxSizeOfInstructions :: !Word16
+    -- | Maximum number of components referenced at “top level” for any composite glyph.
+    , _maxpmaxComponentElements :: !Word16
+    -- | Maximum levels of recursion; 1 for simple components.
+    , _maxpmaxComponentDepth :: !Word16
+    }
+    deriving (Eq, Show)
+
+instance Binary MaxpTable where
+    put _ = fail "Unimplemented"
+    get = MaxpTable 
+       <$> get <*> g16 <*> g16 <*> g16 <*> g16 <*> g16
+       <*> g16 <*> g16 <*> g16 <*> g16 <*> g16 <*> g16
+       <*> g16 <*> g16 <*> g16
+         where g16 = getWord16be
+
+ src/Graphics/Text/TrueType/OffsetTable.hs view
@@ -0,0 +1,79 @@+module Graphics.Text.TrueType.OffsetTable
+    ( OffsetTableHeader( .. )
+    , OffsetTable( .. )
+    , TableDirectoryEntry( .. )
+    ) where
+
+import Control.Applicative( (<$>), (<*>) )
+import Data.Word( Word16, Word32 )
+import Data.Binary( Binary( .. ) )
+import Data.Binary.Get( getWord16be
+                      , getWord32be
+                      , getByteString
+                      )
+
+import Data.Binary.Put( putWord16be
+                      , putWord32be
+                      , putByteString
+                      )
+
+import qualified Data.Vector as V
+import qualified Data.ByteString.Char8 as BC
+
+import Graphics.Text.TrueType.Types
+
+data OffsetTableHeader = OffsetTableHeader
+    { -- | sfnt version 0x00010000 for version 1.0.
+      _othSfntVersion   :: !Fixed
+      -- | numTables Number of tables.
+    , _othTableCount    :: !Word16
+      -- | searchRange (Maximum power of 2 ? numTables) x 16.
+    , _othSearchRange   :: !Word16
+      -- | entrySelector Log2(maximum power of 2 ? numTables).
+    , _othEntrySelector :: !Word16
+      -- | rangeShift NumTables x 16-searchRange.
+    , _othRangeShift    :: !Word16
+    }
+    deriving (Eq, Show)
+
+instance Binary OffsetTableHeader where
+  get = OffsetTableHeader <$> get <*> g16 <*> g16 <*> g16 <*> g16
+    where g16 = getWord16be
+  put (OffsetTableHeader ver c sr es rs) =
+    put ver >> p16 c >> p16 sr >> p16 es >> p16 rs
+      where p16 = putWord16be
+
+data TableDirectoryEntry = TableDirectoryEntry
+    { -- | tag 4 -byte identifier.
+      _tdeTag      :: !BC.ByteString
+      -- | CheckSum for this table.
+    , _tdeChecksum :: !Word32
+      -- | Offset from beginning of TrueType font file.
+    , _tdeOffset   :: !Word32
+      -- | Length of this table.
+    , _tdeLength   :: !Word32
+    }
+    deriving (Eq, Show)
+
+instance Binary TableDirectoryEntry where
+  get = TableDirectoryEntry <$> getByteString 4 <*> g32 <*> g32 <*> g32
+    where g32 = getWord32be
+  put (TableDirectoryEntry tag chk offset ln) =
+      putByteString tag >> p32 chk >> p32 offset >> p32 ln
+        where p32 = putWord32be
+
+data OffsetTable = OffsetTable
+    { _otHeader  :: !OffsetTableHeader
+    , _otEntries :: !(V.Vector TableDirectoryEntry)
+    }
+    deriving (Eq, Show)
+
+instance Binary OffsetTable where
+    put (OffsetTable hdr entries) =
+        put hdr >> V.forM_ entries put
+
+    get = do
+        hdr <- get
+        let count = fromIntegral $ _othTableCount hdr
+        OffsetTable hdr <$> V.replicateM count get
+
+ src/Graphics/Text/TrueType/Types.hs view
@@ -0,0 +1,25 @@+module Graphics.Text.TrueType.Types
+    ( Fixed
+    , FWord
+    ) where
+
+import Control.Applicative( (<$>), (<*>) )
+import Data.Word( Word16 )
+import Data.Binary( Binary( .. ) )
+import Data.Binary.Get( getWord16be)
+import Data.Binary.Put( putWord16be )
+
+data Fixed = Fixed Word16 Word16
+    deriving (Eq, Show)
+
+instance Binary Fixed where
+    get = Fixed <$> getWord16be <*> getWord16be
+    put (Fixed a b) = putWord16be a >> putWord16be b
+
+newtype FWord = FWord Word16
+    deriving (Eq, Show)
+
+instance Binary FWord where
+  put (FWord w) = putWord16be w
+  get = FWord <$> getWord16be
+