diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 Kristof Bastiaensen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Opentype/Fileformat.hs b/Opentype/Fileformat.hs
new file mode 100644
--- /dev/null
+++ b/Opentype/Fileformat.hs
@@ -0,0 +1,281 @@
+{-# LANGUAGE TupleSections #-}
+
+-- | This module provides opentype file loading and writing.  An
+-- attempt was made to have a higher level interface without
+-- sacrificing features of the file format.
+
+module Opentype.Fileformat
+       (-- * Types
+         ShortFrac (..), Fixed, FWord, UFWord, GlyphID,
+         -- * Main datatype
+         OpentypeFont (..), maxpTable, glyfTable, OutlineTables (..), GenericTables,
+         -- * IO
+         readOTFile, writeOTFile,
+         -- * Head table
+         HeadTable(..),
+         -- * Glyf table
+         GlyfTable(..), Glyph(..), GlyphOutlines(..),
+         CurvePoint(..), Instructions, GlyphComponent(..),
+         -- * CMap table
+         CmapTable(..), CMap(..), PlatformID(..), MapFormat (..),
+         -- * Hhea table
+         HheaTable(..),
+         -- * Maxp table
+         MaxpTable(..),
+         -- * Name table
+         NameTable(..), NameRecord(..),
+         -- * Post table
+         PostTable(..), PostVersion(..),
+         -- * OS/2 table
+         OS2Table(..),
+         -- * Kern table
+         KernTable(..), KernPair(..)
+       ) where
+import Opentype.Fileformat.Types
+import Opentype.Fileformat.Head
+import Opentype.Fileformat.Glyph
+import Opentype.Fileformat.Hhea
+import Opentype.Fileformat.Cmap
+import Opentype.Fileformat.Maxp
+import Opentype.Fileformat.Name
+import Opentype.Fileformat.Post
+import Opentype.Fileformat.Kern
+import Opentype.Fileformat.OS2
+import Data.Binary.Get
+import Data.Binary.Put
+import Data.Binary
+import Data.Maybe
+import Data.Bits
+import Data.List (zip4, sort)
+import Data.Char
+import Data.Foldable
+import Control.Monad
+import qualified Data.ByteString.Lazy as Lazy
+import qualified Data.ByteString as Strict
+import Data.ByteString.Unsafe
+import qualified Data.Map as M
+
+type GenericTables = M.Map String Lazy.ByteString
+
+-- | truetype or opentype font
+data OpentypeFont = OpentypeFont {
+  -- | Use apple scaler.  Should not be used for opentype fonts.
+  appleScaler :: Bool,
+  -- | global information about the font. 
+  headTable :: HeadTable,
+  -- | global horizontal metrics information
+  hheaTable :: HheaTable,
+  -- | mapping of character codes to the glyph index values
+  cmapTable :: CmapTable,
+  -- | information strings in different languages
+  nameTable :: NameTable,
+  -- | data for postscript printers
+  postTable :: PostTable,
+  -- | windows specific information
+  os2Table :: Maybe OS2Table,
+  -- | Kerning tables
+  kernTable :: Maybe KernTable,
+  -- | tables specific to the outline type (cubic or quadratic).
+  outlineTables :: OutlineTables,
+  -- | not (yet) supported tables
+  otherTables :: GenericTables
+  }
+  deriving Show
+
+-- | tables for quadratic outlines (truetype or opentype)
+data OutlineTables =
+  QuadTables MaxpTable GlyfTable |
+  CubicTables 
+  deriving Show
+
+maxpTable :: OpentypeFont -> Maybe MaxpTable
+maxpTable font = case outlineTables font of
+  QuadTables m _ -> Just m
+  _ -> Nothing
+
+glyfTable :: OpentypeFont -> Maybe GlyfTable
+glyfTable font = case outlineTables font of
+  QuadTables _ g -> Just g
+  _ -> Nothing
+
+data ScalerType =
+  -- opentype with cff
+  CubicScaler |
+  -- truetype and opentype with glyf
+  QuadScaler |
+  -- apple only scaler
+  AppleScaler
+  deriving Eq
+  
+type SfntLocs = M.Map Scaler (Word32, Word32)
+type Scaler = Word32
+
+-- | write an opentype font to a file
+writeOTFile :: OpentypeFont -> FilePath -> IO ()
+writeOTFile font file = 
+  case outlineTables font of
+    CubicTables ->
+      error "cubic splines are not yet supported"
+    QuadTables maxpTbl (GlyfTable glyphs) ->
+       let (lengths, glyphBs) = runPutM $ writeGlyphs glyphs
+           (format, locaBs) = runPutM $ writeLoca lengths
+           (longHor, hmtxBs) = runPutM $ writeHmtx glyphs
+           head2 = updateHead glyphs $
+                   (headTable font) {
+                     headVersion = 0x00010000,
+                     fontDirectionHint = 2,
+                     longLocIndices = format }
+           hhea2 = updateHhea glyphs $
+                   (hheaTable font) {numOfLongHorMetrics = fromIntegral longHor}
+           maxp2 = updateMaxp glyphs $
+                   maxpTbl {maxpVersion = 0x00010000}
+           headBs = Lazy.toStrict $ runPut $ putHeadTable head2
+           cmapBs = Lazy.toStrict $ runPut $ putCmapTable $ cmapTable font
+           hheaBs = Lazy.toStrict $ runPut $ putHheaTable hhea2
+           maxpBs = Lazy.toStrict $ runPut $ putMaxpTable maxp2 
+           nameBs = Lazy.toStrict $ runPut $ putNameTable $ nameTable font
+           postBs = Lazy.toStrict $ runPut $ putPostTable $ postTable font
+           os2Bs  = (Lazy.toStrict . runPut . putOS2Table) <$> os2Table font
+           kernBs = (Lazy.toStrict . runPut . putKernTable) <$> kernTable font
+           scaler | appleScaler font = AppleScaler 
+                  | otherwise = QuadScaler
+       in Lazy.writeFile file $ runPut $ 
+          writeTables scaler $ concat 
+          [[(nameToInt "head", headBs),
+            (nameToInt "hhea", hheaBs),
+            (nameToInt "maxp", maxpBs)],
+           maybeToList $ (nameToInt "OS/2",) <$> os2Bs,
+           [(nameToInt "hmtx", Lazy.toStrict hmtxBs),
+            (nameToInt "cmap", cmapBs),
+            (nameToInt "loca", Lazy.toStrict locaBs),
+            (nameToInt "glyf", Lazy.toStrict glyphBs)],
+           maybeToList $ (nameToInt "kern",) <$> kernBs,
+           [(nameToInt "name", nameBs),
+            (nameToInt "post", postBs)]]
+  
+
+runGetOrErr :: Get b -> Lazy.ByteString -> Either String b
+runGetOrErr g bs = case runGetOrFail g bs of
+  Left (_, _, str) -> Left str
+  Right (_, _, res) -> Right res
+
+-- | read an opentype font from a file.  
+readOTFile :: FilePath -> IO OpentypeFont
+readOTFile file = do
+  strict <- Strict.readFile file
+  let res = do
+        (locs, scaler) <- runGetOrErr readTables $
+                          Lazy.fromStrict strict
+        let readTable tag = case M.lookup (nameToInt tag) locs of
+              Nothing -> Left $ "Table " ++ tag ++ " not found."
+              Just (offset, _) ->
+                Right $ Strict.drop (fromIntegral offset) strict
+            readLazy tag = Lazy.fromStrict <$> readTable tag
+            readMaybe tag = case M.lookup (nameToInt tag) locs of
+              Nothing -> Right Nothing
+              Just (offset, _) ->
+                Right $ Just $ Lazy.fromStrict $ Strict.drop (fromIntegral offset) strict
+        headBs <- runGetOrErr getHeadTable =<< readLazy "head"
+        maxpTbl <- runGetOrErr getMaxpTable =<< readLazy "maxp"
+        hheaTbl <- runGetOrErr getHheaTable =<< readLazy "hhea"
+        offsets <- runGetOrErr (readGlyphLocs (longLocIndices headBs)
+                                (fromIntegral $ numGlyphs maxpTbl)) =<<
+                   readLazy "loca"
+        hmetrics <- runGetOrErr (readHmetrics (fromIntegral $ numOfLongHorMetrics hheaTbl)
+                                 (fromIntegral $ numGlyphs maxpTbl))
+                    =<< readLazy "hmtx"
+        glyphTbl <- readGlyphTable (zip offsets (zipWith (-) offsets (tail offsets)))
+                    hmetrics =<< readTable "glyf"
+        postTbl <- runGetOrErr getPostTable =<< readLazy "post"
+        nameTbl <- readNameTable =<< readTable "name"
+        cmapTbl <- readCmapTable =<< readTable "cmap"
+        os2tbl <- traverse (runGetOrErr getOS2Table) =<< readMaybe "OS/2"
+        kerntbl <- traverse (runGetOrErr getKernTable) =<< readMaybe "kern"
+        return $ OpentypeFont (scaler == AppleScaler) headBs hheaTbl
+          cmapTbl nameTbl postTbl os2tbl kerntbl
+          (QuadTables maxpTbl (GlyfTable glyphTbl)) M.empty
+  either (ioError.userError) return res
+  
+nameToInt :: String -> Word32
+nameToInt string =
+  fromIntegral $ sum $ zipWith (\c b -> ord c `shift` b) string [24, 16..0]
+
+readTables :: Get (SfntLocs, ScalerType)
+readTables = do
+  scaler <- getWord32be
+  scalerType <- case scaler of
+    0x74727565 -> return AppleScaler
+    0x4F54544F -> return CubicScaler
+    0x00010000 -> return QuadScaler
+    _ -> fail "This file is not a truetype or opentype file."
+  numTables <- getWord16be
+  skip 6
+  locs <- fmap M.fromAscList $
+          replicateM (fromIntegral numTables) $
+    do tag <- getWord32be
+       _ <- getWord32be
+       offset <- getWord32be
+       size <- getWord32be
+       return (tag, (offset, size))
+  return (locs, scalerType)
+
+checkSum :: Strict.ByteString -> Word32
+checkSum bs
+  | Strict.length bs < 4 =
+      sum [fromIntegral n `shift` l  | (n, l) <- zip (Strict.unpack bs) [24, 16, 8, 0]]
+  | otherwise =
+      fromIntegral (unsafeIndex bs 0) `shift` 24 +
+      fromIntegral (unsafeIndex bs 1) `shift` 16 +
+      fromIntegral (unsafeIndex bs 2) `shift` 8 +
+      fromIntegral (unsafeIndex bs 3) +
+      checkSum (Strict.drop 4 bs)
+
+headWithChecksum :: Strict.ByteString -> Word32 -> Put
+headWithChecksum bs cksum = do
+  putByteString $ Strict.take 8 bs
+  putWord32be $ 0xB1B0AFBA - cksum
+  putByteString $ Strict.drop 12 bs
+  
+putPadding :: Strict.ByteString -> Put
+putPadding bs = replicateM_ (pad-sz) (putInt8 0)
+  where sz = fromIntegral $ Strict.length bs
+        pad = padded sz
+  
+padded :: (Bits a, Num a) => a -> a
+padded len = (len+3) .&. complement 3
+
+writeTables :: ScalerType -> [(Word32, Strict.ByteString)] -> Put
+writeTables scaler tables = do
+  putByteString unChecked
+  let cksumTot = fromIntegral $ sum $ checkSum unChecked:ckSums
+  for_ (zip tables tableBs) $
+    \((tag,_), bs) -> 
+      if tag == nameToInt "head"
+        then do headWithChecksum bs cksumTot
+                putPadding bs
+        else do putByteString bs
+                putPadding bs
+  where
+    entrySelector, searchRange, nTables :: Word16
+    nTables = fromIntegral $ length tables
+    entrySelector = fromIntegral $ iLog2 nTables
+    searchRange = 1 `shift` (fromIntegral entrySelector+4)
+    offsets = scanl (+) (fromIntegral $ 16*length tables + 12) (map padded lengths)
+    lengths = map Strict.length tableBs
+    tableBs = map snd tables
+    ckSums = map checkSum tableBs
+    unChecked = Lazy.toStrict $ runPut $ do
+      putWord32be $ case scaler of
+        AppleScaler -> nameToInt "true"
+        CubicScaler -> nameToInt "OTTO"
+        QuadScaler -> 0x00010000
+      putWord16be $ fromIntegral nTables
+      putWord16be searchRange
+      putWord16be entrySelector
+      putWord16be $ nTables * 16 - searchRange
+      for_ (sort $ zip4 tables ckSums offsets lengths) $
+        \((tag,_), cksum, offset, len) -> do
+          putWord32be tag
+          putWord32be cksum
+          putWord32be $ fromIntegral offset
+          putWord32be $ fromIntegral len
diff --git a/Opentype/Fileformat/Cmap.hs b/Opentype/Fileformat/Cmap.hs
new file mode 100644
--- /dev/null
+++ b/Opentype/Fileformat/Cmap.hs
@@ -0,0 +1,524 @@
+module Opentype.Fileformat.Cmap where
+import Opentype.Fileformat.Types
+import Data.Binary
+import Data.Binary.Put
+import Data.List (sort, mapAccumL, foldl')
+import Data.Either (either)
+import Control.Monad
+import Data.Traversable (for)
+import Data.Foldable (for_, traverse_)
+import qualified Data.ByteString.Lazy as Lazy
+import qualified Data.ByteString as Strict
+import qualified Data.Map as M
+import qualified Data.IntSet as IS
+import Data.Maybe
+import Data.Bits
+import Data.Int
+-- import Hexdump -- for debugging
+
+-- | This table defines the mapping of character codes to the glyph
+-- index values used in the font. It may contain more than one
+-- subtable, in order to support more than one character encoding
+-- scheme. Character codes that do not correspond to any glyph in the
+-- font should be mapped to glyph index 0. The glyph at this location
+-- must be a special glyph representing a missing character, commonly
+-- known as .notdef.
+--
+-- The table header indicates the character encodings for which
+-- subtables are present. Each subtable is in one of seven possible
+-- formats and begins with a format code indicating the format
+-- used.
+-- 
+-- The `platformID` and platform-specific `encodingID` in the header
+-- entry (and, in the case of the Macintosh platform, the `macLanguage`
+-- field in the subtable itself) are used to specify a particular
+-- cmap encoding. Each platform ID, platform-specific encoding ID,
+-- and subtable `macLanguage` combination may appear only once in the
+-- `CmapTable`.
+--
+-- When `platformID` is `UnicodePlatform`, `encodingID` is interpreted as follows:
+-- 
+--  * 0: Default semantics
+--  * 1: Version 1.1 semantics
+--  * 2: ISO 10646 1993 semantics (deprecated)
+--  * 3: Unicode 2.0 or later semantics (BMP only)
+--  * 4: Unicode 2.0 or later semantics (non-BMP characters allowed)
+--  * 5: Unicode Variation Sequences
+--  * 6: Full Unicode coverage (used with type 13.0 cmaps by OpenType)
+--
+-- When `platformID` `MacintoshPlatform`, the `encodingID` is a QuickDraw script code.
+-- 
+-- Note that the use of the Macintosh platformID is currently
+-- discouraged. Subtables with a Macintosh platformID are only
+-- required for backwards compatibility with QuickDraw and will be
+-- synthesized from Unicode-based subtables if ever needed.
+--
+-- When `platformID` is `MicrosoftPlatform`, the `encodingID` is a is interpreted as follows:
+--
+-- * 0: Symbol
+-- * 1: Unicode BMP-only (UCS-2)
+-- * 2: Shift-JIS
+-- * 3: PRC
+-- * 4: BigFive
+-- * 5: Johab
+-- * 10: Unicode UCS-4
+
+data CmapTable = CmapTable [CMap]
+  deriving Show
+
+data CMap = CMap {
+  cmapPlatform :: PlatformID,
+  cmapEncoding :: Word16,
+  -- | used only in the Macintosh platformID (/DEPRECATED/)
+  cmapLanguage :: Word16,
+  -- | internal format of the map
+  mapFormat :: MapFormat,
+  -- | set contains high byte\/word if part of multibyte\/word
+  -- character.
+  multiByte :: IS.IntSet,
+  -- | map from character code to glyph index
+  glyphMap :: WordMap GlyphID
+  }
+  deriving Show
+
+instance Ord CMap where
+  compare (CMap pfID encID lang _ _ _) (CMap pfID2 encID2 lang2 _ _ _) =
+    compare (pfID, encID, lang) (pfID2, encID2, lang2)
+
+instance Eq CMap where
+  (CMap pfID encID lang _ _ _) == (CMap pfID2 encID2 lang2 _ _ _) =
+    (pfID, encID, lang) == (pfID2, encID2, lang2)
+
+data MapFormat = 
+  -- | 8 bit encoding, contiguous block of bytes.  /LEGACY ONLY./
+  MapFormat0 |
+  -- | mixed 8\/16 bit encoding with gaps.  /LEGACY ONLY./
+  MapFormat2 |
+  -- | 16 bit encoding with holes.  This should contain the BMP for a
+  -- unicode font.
+  MapFormat4 |
+  -- | 16 bit single contiguous block (trimmed).
+  MapFormat6 |
+  -- | mixed 16\/32 bit, for compatibility only, /DO NOT USE/
+  MapFormat8 |
+  -- | 32 bit single contiguous block (trimmed), for compatibility
+  -- only, /DO NOT USE/
+  MapFormat10 |
+  -- | 32 bit segmented coverage.  This should contain Unicode
+  -- encodings with glyphs above 0xFFFF.  It's recommended to save a
+  -- subset to format 4, for backwards compatibility.
+  MapFormat12
+  deriving Show
+
+putCmapTable :: CmapTable -> Put
+putCmapTable (CmapTable cmaps_) =
+  do putWord16be 0
+     putWord16be $ fromIntegral $ length cmaps
+     for_ (zip offsets cmaps) $
+       \(offset, CMap pfID encID _ _ _ _) ->
+         do putPf pfID
+            putWord16be encID
+            putWord32be offset
+     traverse_ putLazyByteString cmapsBs
+       where
+         cmaps = sort cmaps_
+         offsets :: [Word32]
+         offsets =
+           scanl (+) (fromIntegral $ 4 + 8 * length cmaps) $
+           map (fromIntegral . Lazy.length) cmapsBs
+         cmapsBs = map (runPut.putCmap) cmaps
+
+readCmapTable :: Strict.ByteString -> Either String CmapTable
+readCmapTable bs = do
+  version <- index16 bs 0
+  when (version /= 0) $
+    fail "unsupported cmap version."
+  n <- index16 bs 1
+  entries <- for [0..n-1] $ \i -> do
+    pfID <- toPf =<< (index16 bs $ 2 + i*4)
+    encID <- index16 bs $ 2 + i*4 + 1
+    offset <- index32 bs $ 2 + fromIntegral i*2
+    return (offset, pfID, encID)
+  cmaps <- for entries $ \(offset, pfID, encID) -> do
+    cm <- readCmap $ Strict.drop (fromIntegral offset) bs
+    Right $ cm {cmapPlatform = pfID, cmapEncoding = encID}
+  return $ CmapTable cmaps
+
+putCmap :: CMap -> Put
+putCmap cmap = case mapFormat cmap of
+  MapFormat0 -> putMap0 cmap
+  MapFormat2 -> putMap2 cmap
+  -- 16 bit encoding with holes
+  MapFormat4 -> putMap4 cmap
+  -- trimmed 16 bit mapping.
+  MapFormat6 -> putMap6 cmap
+  -- mixed 16/32 bit encoding (/DEPRECATED/)
+  MapFormat8 -> putMap8 cmap
+  MapFormat10 -> putMap10 cmap
+  -- 32 bit segmented coverage
+  MapFormat12 -> putMap12 cmap
+
+readCmap :: Strict.ByteString -> Either String CMap
+readCmap bs_ = do
+  c <- index16 bs_ 0
+  let bs | (c >= 8 && c < 14) = Strict.drop 8 bs_
+         | otherwise = Strict.drop 4 bs_
+  either fail return $
+    case c of 
+      0 -> getMap0 bs
+      2 -> getMap2 bs
+      4 -> getMap4 bs
+      6 -> getMap6 bs
+      8 -> getMap8 bs
+      10 -> getMap10 bs
+      12 -> getMap12 bs
+      i -> fail $ "unsupported map encoding " ++ show i
+
+subIntMap :: Word32 -> Word32 -> WordMap GlyphID -> WordMap GlyphID
+subIntMap from to intMap =
+  fst $ M.split (fromIntegral to+1) $ snd $
+  M.split (fromIntegral from-1) intMap
+
+asSubtypeFrom :: b -> [(a, b)] -> b
+asSubtypeFrom a _ = a
+
+-- Put codes in range, and use zero glyph when no code is found.
+putCodes :: Word32 -> Word32 -> [(Word32, GlyphID)] -> Put
+putCodes start end _
+  | start > end = return ()
+
+putCodes start end [] = do
+  putWord16be 0
+  putCodes (start+1) end []
+      
+putCodes start end l@((i, code):rest)
+  | start < i = do putWord16be 0
+                   putCodes (start+1) end l
+  | otherwise = do putWord16be code
+                   putCodes (i+1) end rest
+
+-- write subcodes as range, with zero glyph for missing codes.
+subCodes :: WordMap GlyphID -> Word32 -> Word32 -> Put
+subCodes set start end =
+  putCodes start end $ M.toList $ subIntMap start end set
+
+data SubTable2 = SubTable2 {
+  highByte :: Word16,
+  firstCode :: Word16,
+  entryCount :: Word16,
+  rangeOffset :: Word16,
+  rangeBytes :: Put
+  }
+
+putMap0 :: CMap -> PutM ()
+putMap0 cmap = do
+  putWord16be 0
+  putWord16be 262
+  putWord16be $ cmapLanguage cmap
+  let gm = glyphMap cmap
+  for_ [0..255] $ \c ->
+    putWord8 $ fromIntegral $
+    fromMaybe 0 (M.lookup c gm)
+
+getMap0 :: Strict.ByteString -> Either String CMap
+getMap0 bs =
+  if Strict.length bs < 258 then
+    Left "invalid map format 0"
+  else
+    do lang <- index16 bs 0
+       let gmap = M.fromAscList $
+             filter ((/= 0).snd) $
+             (flip map) [0..255] $ \c ->
+             (fromIntegral c, fromIntegral $ Strict.index bs (c+2))
+       Right $ CMap UnicodePlatform 0 lang MapFormat0 IS.empty gmap
+
+putMap2 :: CMap -> PutM ()
+putMap2 cmap = do
+  putWord16be 2
+  putWord16be size
+  putWord16be (cmapLanguage cmap)
+  putCodes 0 255 $ zip (map (fromIntegral.highByte) subTableCodes) [1::Word16 ..]
+  for_ subTables $ \(SubTable2 _ fc ec ro _) ->
+    do putWord16be fc
+       putWord16be ec
+       putWord16be 0
+       putWord16be ro
+  for_ subTables rangeBytes
+    where
+      highBytes :: [Int]
+      highBytes =
+        IS.toList $ fst $
+        IS.split 255 (multiByte cmap)
+      subTableCodes =
+        filter ((/= 0) . entryCount) $ 
+        flip map highBytes $ \hb ->
+        let subMap = subIntMap (fromIntegral hb `shift` 8)
+                     (fromIntegral hb `shift` 8 .|. 0xff) $
+                     glyphMap cmap
+            (fstCode, lstCode)
+              | M.null subMap = (0, -1)
+              | otherwise = (fst $ M.findMin subMap,
+                             fst $ M.findMax subMap)
+            ec = lstCode - fstCode + 1
+            rb = subCodes subMap fstCode lstCode
+        in SubTable2 (fromIntegral hb) (fromIntegral fstCode) (fromIntegral ec) 0 rb
+          where
+      subTables = scanl calcOffset firstTable subTableCodes
+      firstTable =
+        SubTable2 0 0 256 (fromIntegral $ length subTableCodes * 8 + 2) $
+        subCodes (glyphMap cmap) 0 255
+      size :: Word16
+      size = 518 + 8 * (fromIntegral $ length subTables) + 2 * sum (map entryCount subTables)
+      calcOffset prev st = st { rangeOffset = rangeOffset prev - 8 + 2*entryCount prev }
+        
+getMap2 :: Strict.ByteString -> Either String CMap
+getMap2 bs = do
+  lang <- index16 bs 0
+  highBytes <- do
+    l <- traverse (index16 bs) [1..256]
+    Right $ map fst $ filter ((/=0).snd) $ zip [0::Int ..255] l
+  l <- for [0::Word16 .. fromIntegral $ length highBytes] $ \i -> do
+    fstCode <- index16 bs (fromIntegral $ 257 + i*4)
+    cnt <- index16 bs (fromIntegral $ 257 + i*4 + 1)
+    delta <- index16 bs (fromIntegral $ 257 + i*4 + 2)
+    ro <- index16 bs (fromIntegral $ 257 + i*4 + 3)
+    for [0 .. fromIntegral cnt-1] $ \entry -> do
+      p <- index16 bs (fromIntegral $ 257 + i*4 + 3 + ro `quot` 2 + entry)
+      Right (fromIntegral $ fstCode + entry, if p == 0 then 0 else p + delta)
+  let im = M.fromAscList $ filter ((/= 0).snd) $ concat l
+      is = IS.fromAscList $ map fromIntegral highBytes
+  Right $ CMap UnicodePlatform 0 lang MapFormat2 is im      
+      
+data Segment4 = RangeSegment Word16 Word16 Word16
+              | CodeSegment Word16 Word16 [Word16]
+              deriving Show
+
+findRange :: Word32 -> Int64 -> [(Word32, Word16)] -> (Word32, [(Word32, Word16)])
+findRange nextI _ [] =
+  (nextI-1, [])
+findRange nextI offset l@((i,c):r)
+  | i == nextI && c == fromIntegral (fromIntegral nextI+offset) =
+    findRange (nextI+1) offset r
+  | otherwise = (nextI-1, l)
+
+findCodes :: Word32 -> [(Word32, Word16)] -> ([GlyphID], [(Word32, Word16)])
+findCodes _ [] = ([], [])
+findCodes prevI l@((i,c):r)
+  -- maximum gap is 4
+  | i - prevI > 4 = ([], l)
+  | otherwise = (replicate (fromIntegral $ i-prevI-1) 0 ++ c:c2, r2)
+  where (c2, r2) = findCodes i r
+
+getSegments :: [(Word32, Word16)] -> [Segment4]
+getSegments [] = [RangeSegment 0xffff 1 0]
+getSegments l@((start, c):_)
+  | fromIntegral end - start >= 4 ||
+    lc <= end-start+1 = 
+      RangeSegment (fromIntegral start) (fromIntegral end-fromIntegral start+1) c :
+      getSegments r
+  | otherwise =
+      CodeSegment (fromIntegral start) (fromIntegral lc) codes :
+      getSegments r2
+  where
+    lc = fromIntegral $ length codes
+    (end, r) = findRange start (fromIntegral c - fromIntegral start) l
+    (codes, r2) = findCodes (start-1) l
+
+data Segment4layout = Segment4layout {
+  s4endCode :: Word16,
+  s4startCode :: Word16,
+  s4idDelta :: Word16,
+  s4idRangeOffset :: Word16,
+  s4glyphIndex :: [GlyphID] }
+  deriving Show
+  
+putMap4 :: CMap -> PutM ()
+putMap4 cmap = do
+  putWord16be 4
+  putWord16be size
+  putWord16be (cmapLanguage cmap)
+  putWord16be (segCount*2)
+  putWord16be searchRange
+  putWord16be entrySelector
+  putWord16be $ 2*segCount - searchRange
+  traverse_ (put.s4endCode) layout
+  putWord16be 0
+  traverse_ (put.s4startCode) layout
+  traverse_ (put.s4idDelta) layout
+  traverse_ (put.s4idRangeOffset) layout
+  traverse_ (traverse_ put.s4glyphIndex) layout
+    where
+      size, segCount, searchRange, entrySelector :: Word16
+      entrySelector = iLog2 segCount
+      searchRange = 1 `shift` (fromIntegral $ entrySelector+1)
+      segments = getSegments $ M.toList $ glyphMap cmap
+      (codeSize, layout) = mapAccumL foldLayout (segCount*2) segments
+      foldLayout offset (RangeSegment start len code) =
+        (offset-2, Segment4layout (fromIntegral $ start+len-1)
+                   (fromIntegral start) (code-(fromIntegral start)) 0 [])
+      foldLayout offset (CodeSegment start len codes) =
+        (offset+fromIntegral len*2-2,
+         Segment4layout (fromIntegral $ start+len-1)
+         (fromIntegral start) 0 offset codes)
+      size = 8*segCount + codeSize + 16
+      segCount = fromIntegral $ length segments
+
+getMap4 :: Strict.ByteString -> Either String CMap
+getMap4 bs = do
+    lang <- index16 bs 0
+    segCount <- (`quot` 2) <$> index16 bs 1
+    gmap <- fmap (M.fromAscList . filter ((/= 0).snd) . concat ) $
+            for [0::Word16 .. segCount-2] $ \i ->
+      do idDelta <- index16 bs (i + 6 + segCount*2)
+         endCode <- index16 bs (i + 5)
+         startCode <- index16 bs (i + 6 + segCount)
+         idRangeOffset <- index16 bs (i + 6 + segCount*3)
+         if idRangeOffset == 0
+           then Right [(fromIntegral c, c+idDelta) | c <- [startCode .. endCode]]
+           else for [0..endCode-startCode] $ \j ->
+           do glyph <- index16 bs (fromIntegral $ i + 6 + segCount*3 + idRangeOffset`div`2 + j)
+              Right (fromIntegral $ startCode + j, glyph)
+    Right $ CMap UnicodePlatform 0 lang MapFormat4 IS.empty gmap
+
+putMap6 :: CMap -> PutM ()
+putMap6 cmap = do
+  putWord16be 6
+  putWord16be size
+  putWord16be (cmapLanguage cmap)
+  putWord16be fCode
+  putWord16be eCount
+  subCodes (glyphMap cmap) (fromIntegral fCode) (fromIntegral lastCode)
+  where
+    size, eCount, fCode, lastCode :: Word16
+    size = eCount*2 + 10
+    eCount = lastCode - fCode + 1
+    fCode = fromIntegral $
+      min (fromIntegral (maxBound :: Word16)::Word32) $
+      fst $ M.findMin (glyphMap cmap)
+    lastCode = fromIntegral $
+      min (fromIntegral (maxBound :: Word16)::Word32) $
+      fst $ M.findMax (glyphMap cmap)
+    
+getMap6 :: Strict.ByteString -> Either String CMap
+getMap6 bs = do
+  lang <- index16 bs 0
+  fCode <- index16 bs 1
+  eCount <- index16 bs 2
+  gmap <- fmap (M.fromAscList . filter ((/= 0).snd)) $
+          for [0..eCount-1] $ \i -> do
+    g <- index16 bs (i+3)
+    Right (fromIntegral $ i + fCode, g)
+  Right $ CMap UnicodePlatform 0 lang MapFormat6 IS.empty gmap
+
+putPacked :: Int -> [Int] -> Put
+putPacked start highBytes
+  | start >= 8192 = return ()
+  | otherwise = do
+      putWord8 $ foldl' (.|.) 0 $ map ((shift 1) . (.&. 7)) bytes
+      putPacked (start+1) rest
+        where
+          (bytes, rest) = span (\b -> b >= (start*8) &&
+                                      b < (start+1)*8)
+                          highBytes
+
+readPacked :: Strict.ByteString -> [Int]
+readPacked bs =
+  [i .|. b `shift` 3 |
+   (a, b) <- zip (Strict.unpack bs) [0..8191],
+   i <- [0..7],
+   a .&. (1 `shift` i) /= 0
+  ]
+      
+
+findRanges :: [(Word32, GlyphID)] -> [(Word32, Word32, GlyphID)]
+findRanges [] = []
+findRanges l@((i,c):_) = (i, i2, c) : findRanges next
+  where (i2, next) = findRange i (fromIntegral c-fromIntegral i) l
+  
+putMap8 :: CMap -> PutM ()
+putMap8 cmap = do
+  putWord16be 8
+  putWord16be 0
+  putWord32be $ fromIntegral size
+  putWord32be (fromIntegral $ cmapLanguage cmap)
+  putPacked 0 highBytes
+  putWord32be $ fromIntegral nGroups
+  for_ ranges $ \(start, end, code) -> do
+    putWord32be $ fromIntegral start
+    putWord32be $ fromIntegral end
+    putWord32be $ fromIntegral code
+  where
+    size = nGroups * 12 + 8208
+    highBytes = IS.toList $ multiByte cmap
+    ranges = findRanges $ M.toList $ glyphMap cmap
+    nGroups = length ranges
+
+getMap8 :: Strict.ByteString -> Either String CMap
+getMap8 bs = do
+  _ <- index16 bs 0
+  lang <- index16 bs 1
+  let is = IS.fromAscList $ readPacked (Strict.drop 4 bs)
+  nGroups <- index32 bs 2049
+  gmap <- fmap (M.fromAscList . concat) $ for [0..nGroups-1] $ \i -> do
+    start <- index32 bs (i*3 + 2050)
+    end <- index32 bs (i*3 + 2051)
+    glyph <- index32 bs (i*3 + 2052)
+    return [(fromIntegral c, fromIntegral $ glyph+c-start) | c <- [start .. end]]
+  Right $ CMap UnicodePlatform 0 lang MapFormat8 is gmap
+
+getMap10 :: Strict.ByteString -> Either String CMap
+getMap10 bs = do
+  lang <- index32 bs 0
+  fCode <- index32 bs 1
+  eCount <- index32 bs 2
+  gmap <- fmap (M.fromAscList . filter ((/= 0).snd)) $
+          for [0..eCount-1] $ \i -> do
+    g <- index16 bs (fromIntegral i+6)
+    Right (fromIntegral $ i + fCode, g)
+  Right $ CMap UnicodePlatform 0 (fromIntegral lang) MapFormat6 IS.empty gmap
+
+
+putMap10 :: CMap -> Put
+putMap10 cmap = do
+  putWord16be 10
+  putWord16be 0
+  putWord32be size
+  putWord32be $ fromIntegral $ cmapLanguage cmap
+  putWord32be fCode
+  putWord32be eCount
+  subCodes (glyphMap cmap) (fromIntegral fCode) (fromIntegral lastCode)
+  where
+    size, eCount, fCode, lastCode :: Word32
+    size = eCount*2 + 20
+    eCount = lastCode - fCode  + 1
+    fCode = fromIntegral $ fst $ M.findMin $ glyphMap cmap
+    lastCode = fromIntegral $ fst $ M.findMax $ glyphMap cmap
+
+putMap12 :: CMap -> PutM ()
+putMap12 cmap = do
+  putWord16be 12
+  putWord16be 0
+  putWord32be $ fromIntegral size
+  putWord32be (fromIntegral $ cmapLanguage cmap)
+  putWord32be $ fromIntegral nGroups
+  for_ ranges $ \(start, end, code) -> do
+    putWord32be $ fromIntegral start
+    putWord32be $ fromIntegral end
+    putWord32be $ fromIntegral code
+  where
+    size = nGroups * 12 + 16
+    ranges = findRanges $ M.toList $ glyphMap cmap
+    nGroups = length ranges
+
+getMap12 :: Strict.ByteString -> Either String CMap
+getMap12 bs = do
+  _ <- index16 bs 0
+  lang <- index16 bs 1
+  nGroups <- index32 bs 1
+  gmap <- fmap (M.fromAscList . concat) $ for [0..nGroups-1] $ \i -> do
+    start <- index32 bs (i*3 + 2)
+    end <- index32 bs (i*3 + 3)
+    glyph <- index32 bs (i*3 + 4)
+    return [(fromIntegral c, fromIntegral $ glyph+c-start) | c <- [start .. end]]
+  Right $ CMap UnicodePlatform 0 lang MapFormat8 IS.empty gmap
+
diff --git a/Opentype/Fileformat/Glyph.hs b/Opentype/Fileformat/Glyph.hs
new file mode 100644
--- /dev/null
+++ b/Opentype/Fileformat/Glyph.hs
@@ -0,0 +1,534 @@
+{-# LANGUAGE MultiWayIf, TupleSections #-}
+module Opentype.Fileformat.Glyph where
+import Opentype.Fileformat.Types
+import Opentype.Fileformat.Maxp
+import Opentype.Fileformat.Hhea
+import Opentype.Fileformat.Head
+import qualified Data.Vector as V
+import Data.Foldable (traverse_, for_)
+import Control.Monad
+import Data.Function (fix)
+import qualified Data.ByteString.Lazy as Lazy
+import qualified Data.ByteString as Strict
+import Data.Word
+import Data.Int
+import Data.Maybe (isJust)
+import Data.Bits
+import Data.Binary
+import Data.Binary.Put
+import Data.Binary.Get
+
+
+-- | This table contains the data that defines the appearance of
+-- the glyphs in the font. This includes specification of the points
+-- that describe the contours that make up a glyph outline and the
+-- instructions that grid-fit that glyph. The glyf table supports the
+-- definition of simple glyphs and compound glyphs, that is, glyphs
+-- that are made up of other glyphs.
+data GlyfTable = GlyfTable (V.Vector Glyph)
+  deriving Show
+
+data Glyph = Glyph {
+  advanceWidth :: Word16,
+  leftSideBearing :: Int16,
+  glyphXmin :: FWord,
+  glyphYmin :: FWord,
+  glyphXmax :: FWord,
+  glyphYmax :: FWord,
+  glyphOutlines :: GlyphOutlines}
+  deriving Show
+
+data GlyphOutlines =
+  GlyphContours [[CurvePoint]] Instructions |
+  CompositeGlyph [GlyphComponent]
+  deriving Show
+
+-- | @CurvePoint x y onCurve@: Points used to describe the outline
+-- using lines and quadratic beziers.  Coordinates are absolute (not
+-- relative).  If two off-curve points follow each other, an on-curve
+-- point is added halfway between.
+data CurvePoint = CurvePoint FWord FWord Bool
+  deriving Show
+
+-- | TODO: make a proper datatype for instructions.
+type Instructions = V.Vector Word8
+
+data GlyphComponent =
+  GlyphComponent {
+  componentID :: Int,
+  componentInstructions :: Maybe Instructions,
+  -- | transformation matrix for scaling the glyph
+  componentXX :: ShortFrac,
+  -- | transformation matrix for scaling the glyph
+  componentXY :: ShortFrac,
+  -- | transformation matrix for scaling the glyph
+  componentYX :: ShortFrac,
+  -- | transformation matrix for scaling the glyph
+  componentYY :: ShortFrac,
+  -- | if `matchPoints` is `True`, index of matching point in compound
+  -- being constructed, otherwise x shift.  If `scaledComponentOffset`
+  -- is `False`, this offset is unscaled (microsoft and opentype
+  -- default) in the rasterizer.
+  componentX :: Int,
+  -- | if `matchPoints` is `True`, index of matching point in compound,
+  -- otherwise y shift.  If `scaledComponentOffset` is `False`, this
+  -- offset is unscaled (microsoft and opentype default) in the
+  -- rasterizer.
+  componentY :: Int,
+    -- | see previous
+  matchPoints :: Bool,
+  -- | For the xy values if `matchPoints` is `False`.
+  roundXYtoGrid :: Bool,
+  -- | Use metrics from this component for the compound glyph.
+  useMyMetrics :: Bool, 
+  -- | If set, the components of the compound glyph overlap. Use of
+  -- this flag is not required in OpenType — that is, it is valid to
+  -- have components overlap without having this flag set. It may
+  -- affect behaviors in some platforms, however. (See Apple’s
+  -- specification for details regarding behavior in Apple platforms.)
+  overlapCompound :: Bool,
+  -- | The component offset is scaled by the rasterizer (apple).  Should be
+  -- set to `False` (opentype default), unless the font is meant to work
+  -- with old apple software.
+  scaledComponentOffset :: Bool
+  }
+  deriving Show
+
+emptyGlyph :: Glyph
+emptyGlyph = Glyph 0 0 0 0 0 0 (GlyphContours [] V.empty)
+
+readHmetrics :: Int -> Int -> Get [(Word16, Int16)]
+readHmetrics 1 m = do
+  aw <- getWord16be
+  lsb <- getInt16be
+  ((aw, lsb):) <$> replicateM (m-1) ((aw,) <$> getInt16be)
+
+readHmetrics 0 _ = fail "no horizontal metrics found"
+readHmetrics n m = do
+  aw <- getWord16be
+  lsb <- getInt16be
+  ((aw, lsb):) <$> readHmetrics (n-1) (m-1)
+
+readGlyphLocs :: Bool -> Int -> Get [Int]
+readGlyphLocs long n =
+  replicateM (n+1) $ 
+  if long then fromIntegral <$> getWord32be
+  else (*2).fromIntegral <$> getWord16be
+ 
+readGlyphTable :: [(Int, Int)] -> [(Word16, Int16)] -> Strict.ByteString
+               -> Either String (V.Vector Glyph)
+readGlyphTable glyphSizes hmetrics glyfBs =
+  V.fromList <$> zipWithM readGlyph glyphSizes hmetrics
+  where
+    readGlyph (offset, size) (aw, lsb)
+      | offset + size > Strict.length glyfBs =
+        Left "glyph past table bounds."
+      | otherwise =
+        case runGetOrFail getGlyph (Lazy.fromStrict $ Strict.drop offset glyfBs) of
+          Left (_, _, err) -> Left err
+          Right (_, _, g) -> Right $ g {advanceWidth = aw, leftSideBearing = lsb}
+
+-- return bytestring lengths
+writeGlyphs :: V.Vector Glyph -> PutM (V.Vector Int)
+writeGlyphs = traverse writeGlyph
+
+writeGlyph :: Glyph -> PutM Int
+writeGlyph g = do
+  putLazyByteString bs
+  replicateM_ pad (putWord8 0)
+  return $ fromIntegral (len+pad)
+    where bs = runPut $ putGlyph g
+          len = fromIntegral $ Lazy.length bs
+          pad = (- fromIntegral len) .&. 3
+  
+-- return long or short format  
+writeLoca :: V.Vector Int -> PutM Bool
+writeLoca vec
+  | V.last offsets > 0xffff =
+     do traverse_ (putWord32be . fromIntegral) offsets
+        return True
+  | otherwise =
+     do traverse_ (putWord16be . (`quot` 2) . fromIntegral) offsets
+        return False
+  where
+    offsets = V.scanl (+) 0 vec
+    
+writeHmtx :: V.Vector Glyph -> PutM Int
+writeHmtx gs
+  | V.null gs = return 0
+  | otherwise = 
+    do traverse_ (\g -> do putWord16be (advanceWidth g)
+                           putInt16be (leftSideBearing g)
+                 ) dbl
+       traverse_ (putInt16be.leftSideBearing) sngl
+       return (len-tl)
+     where
+      findTail i cnt
+        | i < 0 = cnt
+        | advanceWidth (V.unsafeIndex gs i) == aw =
+            findTail (i-1) (cnt+1)
+        | otherwise = cnt
+      aw = advanceWidth (V.unsafeLast gs)
+      len = V.length gs
+      tl = findTail (len-2) 0
+      (dbl, sngl) = V.splitAt (len-tl) gs
+  
+putGlyph :: Glyph -> Put
+putGlyph (Glyph _ _ xmin ymin xmax ymax outlines) = do
+  putInt16be $ case outlines of
+    GlyphContours pts _ -> fromIntegral $ length pts
+    _ -> -1
+  putInt16be xmin
+  putInt16be ymin
+  putInt16be xmax
+  putInt16be ymax
+  case outlines of
+    GlyphContours pts instrs ->
+      putContour pts instrs
+    CompositeGlyph comps -> do
+      traverse_ (putComponent True) (init comps)
+      putComponent False $ last comps
+
+getGlyph :: Get Glyph
+getGlyph = do
+  n <- getInt16be
+  xmin <- getInt16be
+  ymin <- getInt16be
+  xmax <- getInt16be
+  ymax <- getInt16be
+  outlines <-
+    if n >= 0
+    then getContour (fromIntegral n)
+    else fmap CompositeGlyph $ fix $ \nextComponent -> do
+      (c, more) <- getComponent
+      if more then (c:) <$> nextComponent
+        else return [c]
+  return $ Glyph 0 0 xmin ymin xmax ymax outlines
+
+isShort :: FWord -> Bool
+isShort n = abs n <= 255
+
+putCompressFlags :: [Word8] -> Put
+putCompressFlags [] = return ()
+putCompressFlags (a:r) =
+  do if null as
+       then putWord8 a
+       else do putWord8 (a .|. 8)
+               putWord8 $ fromIntegral $ length as
+     putCompressFlags r2
+       where
+         (as, r2) = span (== a) r
+         
+contourFlag :: CurvePoint -> Word8  
+contourFlag (CurvePoint x y oc) =
+  fromIntegral $ 
+  makeFlag [oc, sx && x /= 0, sy && y /= 0, False,
+            x == 0 || (sx && x >= 0),
+            y == 0 || (sy && y >= 0)]
+  where
+    sx = isShort x
+    sy = isShort y
+
+firstFlag :: CurvePoint -> Word8
+firstFlag (CurvePoint x y oc) =
+  fromIntegral $
+  makeFlag [oc, sx, sy, False, sx && x >= 0, sy && y >= 0]
+  where sx = isShort x
+        sy = isShort y
+        
+putCoordX :: CurvePoint -> Word8 -> Put
+putCoordX (CurvePoint x _ _) flag
+  | byteAt flag 1 = putWord8 (fromIntegral $ abs x)
+  | byteAt flag 4 = return ()
+  | otherwise = putInt16be (fromIntegral x)
+  
+putCoordY :: CurvePoint -> Word8 -> Put
+putCoordY (CurvePoint _ y _) flag
+  | byteAt flag 2 = putWord8 (fromIntegral $ abs y)
+  | byteAt flag 5 = return ()
+  | otherwise = putInt16be (fromIntegral y)
+
+putContour :: [[CurvePoint]] -> V.Vector Word8 -> Put    
+putContour points instr = do
+  traverse_ (putWord16be.fromIntegral) endPts
+  putWord16be $ fromIntegral $ V.length instr
+  traverse_ putWord8 instr
+  putCompressFlags flags
+  zipWithM_ putCoordX allPts flags
+  zipWithM_ putCoordY allPts flags
+  where
+    endPts = tail $ scanl (+) (-1) $ map length points
+    allPts = case concat points of
+      [] -> []
+      pts@(p: pts2) -> p: zipWith subCoord pts2 pts
+    subCoord (CurvePoint x1 y1 _) (CurvePoint x2 y2 on) =
+      CurvePoint (x2-x1) (y2-y1) on
+    flags = case allPts of
+      [] -> []
+      (p:pts) -> firstFlag p : map contourFlag pts
+
+getFlags :: Int -> Get [Word8]
+getFlags n
+  | n <= 0 = return []
+  | otherwise = do
+      flag <- getWord8
+      if flag .&. 8 /= 0 && n > 1
+        then do m <- fromIntegral <$> getWord8
+                (replicate (min (m+1) n) flag ++) <$> getFlags (n-m-1)
+        else (flag:) <$> getFlags (n-1)
+
+getXcoords, getYcoords :: [Word8] -> Get [FWord]
+getXcoords [] = return []
+getXcoords (f:r) 
+  | byteAt f 1 = do
+      x <- getWord8
+      let x' | byteAt f 4 = fromIntegral x
+             | otherwise = - fromIntegral x
+      (x':) <$> getXcoords r
+  | byteAt f 4 = (0:) <$> getXcoords r
+  | otherwise = do
+      x <- fromIntegral <$> getInt16be
+      (x:) <$> getXcoords r
+
+getYcoords [] = return []
+getYcoords (f:r) 
+  | byteAt f 2 = do
+      y <- getWord8
+      let y' | byteAt f 5 = fromIntegral y
+             | otherwise = - fromIntegral y
+      (y':) <$> getYcoords r
+  | byteAt f 5 = (0:) <$> getYcoords r
+  | otherwise = do
+      y <- fromIntegral <$> getInt16be
+      (y:) <$> getYcoords r
+
+getPoint :: FWord -> FWord -> Word8 -> CurvePoint
+getPoint x y flag = CurvePoint x y (byteAt flag 0)
+
+reGroup :: [a] -> [Int] -> [[a]]
+reGroup _ []  = []
+reGroup l (n:ns) = c : reGroup r ns
+  where
+    (c, r) = splitAt n l
+
+toOffsets :: (Num a) => [a] -> [a]
+toOffsets [] = []
+toOffsets (x:xs) = scanl (-) x xs
+  
+getContour :: Int -> Get GlyphOutlines
+getContour 0 =  return $ GlyphContours [] V.empty
+getContour nContours = do
+  lastPts <- replicateM nContours (fromIntegral <$> getWord16be)
+  iLen <- fromIntegral <$> getWord16be
+  instructions <- V.replicateM iLen getWord8
+  flags <- getFlags $ last lastPts + 1
+  xCoords <- toOffsets <$> getXcoords flags
+  yCoords <- toOffsets <$> getYcoords flags
+  let coords = zipWith3 getPoint xCoords yCoords flags
+      contours = reGroup coords $
+                 zipWith (-) lastPts ((-1):lastPts)
+  return $ GlyphContours contours instructions
+
+isShortInt :: Int -> Bool
+isShortInt x = x <= 127 && x >= -128
+
+glyphExtent, glyphRsb :: Glyph -> Int16
+glyphRsb g =
+  fromIntegral (advanceWidth g) - glyphExtent g
+  
+glyphExtent glyf = leftSideBearing glyf + (glyphXmax glyf - glyphXmin glyf)
+
+updateHhea :: V.Vector Glyph -> HheaTable -> HheaTable
+updateHhea v h = V.foldl updateHhea1
+                 (h {advanceWidthMax     = minBound,
+                     minLeftSideBearing  = maxBound,
+                     minRightSideBearing = maxBound,
+                     xMaxExtent          = minBound})
+                 v
+  
+
+updateMinMax :: (FWord, FWord, FWord, FWord)
+             -> Glyph -> (FWord, FWord, FWord, FWord)
+updateMinMax (xmin, ymin, xmax, ymax) g =
+  (min xmin (glyphXmin g),
+   min ymin (glyphYmin g),
+   max xmax (glyphXmax g),
+   max ymax (glyphYmax g))
+
+updateHead :: V.Vector Glyph -> HeadTable -> HeadTable
+updateHead vec headTbl =
+  headTbl {xMin = xmin, yMin = ymin,
+           xMax = xmax, yMax = ymax}
+  where (xmin, ymin, xmax, ymax) =
+          V.foldl updateMinMax (maxBound, maxBound, minBound, minBound) vec
+
+updateHhea1 :: HheaTable -> Glyph -> HheaTable
+updateHhea1 hhea g =
+  hhea {advanceWidthMax     = max (advanceWidthMax hhea)
+                              (advanceWidth g),
+        minLeftSideBearing  = min (minLeftSideBearing hhea)
+                              (leftSideBearing g),
+        minRightSideBearing = min (minRightSideBearing hhea)
+                              (glyphRsb g),
+        xMaxExtent          = max (xMaxExtent hhea)
+                              (glyphExtent g)}
+updateMaxp :: V.Vector Glyph -> MaxpTable -> MaxpTable
+updateMaxp vec tbl = V.foldl (updateMaxp1 vec)
+                     (tbl {numGlyphs = 0,
+                           maxPoints = 0,
+                           maxContours = 0,
+                           maxComponentPoints = 0,
+                           maxComponentContours = 0,
+                           maxComponentElements = 0,
+                           maxComponentDepth = 0})
+                     vec
+
+updateMaxp1 :: V.Vector Glyph -> MaxpTable -> Glyph -> MaxpTable
+updateMaxp1 vec maxp glyf =
+  maxp {numGlyphs            = numGlyphs maxp + 1,
+        maxPoints            = max (maxPoints maxp) $
+                               glyfPoints vec glyf,
+        maxContours          = max (maxContours maxp) $
+                               glyfContours vec glyf,
+        maxComponentPoints   = max (maxComponentPoints maxp) $
+                               componentPoints vec glyf,
+        maxComponentContours = max (maxComponentContours maxp) $
+                               componentContours vec glyf,
+        maxComponentElements = max (maxComponentElements maxp) $
+                               componentRefs vec glyf,
+        maxComponentDepth    = max (maxComponentDepth maxp) $
+                               componentDepth vec glyf}
+
+overComponents :: ([[CurvePoint]] -> Word16) -> ([Word16] ->  Word16)
+               -> Int -> Bool -> V.Vector Glyph -> Glyph -> Word16
+overComponents f h maxD d v g
+  | maxD <= 0 = 0
+  | otherwise =
+    case glyphOutlines g of
+      GlyphContours p _
+        | d -> 0
+        | otherwise -> f p
+      CompositeGlyph comps ->
+        h $ map overSub comps
+        where overSub comp = case v V.!? componentID comp of
+                Nothing -> 0
+                Just g2 -> overComponents f h (maxD-1) False v g2 
+  
+glyfPoints, glyfContours, componentRefs, componentDepth, componentPoints, componentContours :: V.Vector Glyph -> Glyph -> Word16
+
+glyfPoints  =
+  overComponents (sum . map (fromIntegral.length)) (const 0) 2 False
+
+glyfContours =
+  overComponents (fromIntegral.length) (const 0) 2 False
+
+componentRefs =
+  overComponents (const 0) (fromIntegral.length) 2 True
+
+componentPoints =
+  overComponents (sum . map (fromIntegral . length)) sum 10 True
+
+componentDepth =
+  overComponents (const 0) ((+1).maximum) 10 True
+
+componentContours =
+  overComponents (fromIntegral.length) sum 10 True
+
+putComponent :: Bool -> GlyphComponent -> Put
+putComponent more c = do
+  putWord16be flag
+  putWord16be $ fromIntegral $ componentID c
+  case (byteAt flag 0, byteAt flag 1) of
+    (False, False) -> do
+      putWord8 $ fromIntegral $ componentX c
+      putWord8 $ fromIntegral $ componentY c
+    (False, True) -> do
+      putInt8 $ fromIntegral $ componentX c
+      putInt8 $ fromIntegral $ componentY c
+    (True, False) -> do
+      putWord16be $ fromIntegral $ componentX c
+      putWord16be $ fromIntegral $ componentY c
+    (True, True) -> do
+      putInt16be $ fromIntegral $ componentX c
+      putInt16be $ fromIntegral $ componentY c
+  when (flag .&. (shift 1 3 + shift 1 6 + shift 1 7) /= 0) $
+    putShortFrac $ componentXX c
+  when (byteAt flag 7) $ do
+    putShortFrac $ componentXY c
+    putShortFrac $ componentYX c
+  when (flag .&. (shift 1 6 + shift 1 7) /= 0) $
+    putShortFrac $ componentYY c
+  for_ (componentInstructions c) $ \instr -> do
+    putWord16be $ fromIntegral $ V.length instr
+    traverse_ putWord8 instr
+      where
+        flag = makeFlag [
+          if matchPoints c
+          then componentX c > 0xff ||
+               componentY c > 0xff
+          else (not $ isShortInt $ componentX c) ||
+               (not $ isShortInt $ componentY c),
+          not $ matchPoints c,
+          roundXYtoGrid c,
+          componentXX c /= 1 &&
+          componentXX c == componentYY c &&
+          componentXY c == 0 && componentYX c == 0,
+          False,
+          more,
+          componentXX c /= componentYY c &&
+          componentXY c == 0 && componentYX c == 0,
+          componentXY c /= 0 || componentYX c /= 0,
+          isJust (componentInstructions c),
+          useMyMetrics c,
+          overlapCompound c,
+          scaledComponentOffset c,
+          not $ scaledComponentOffset c]
+
+getComponent :: Get (GlyphComponent, Bool)
+getComponent = do
+  flag <- getWord16be
+  gID <- getWord16be
+  (cX, cY) <-
+    if | byteAt flag 0 && byteAt flag 1 ->
+           liftM2 (,)
+           (fromIntegral <$> getInt16be)
+           (fromIntegral <$> getInt16be)
+       | byteAt flag 0 ->
+           liftM2 (,)
+           (fromIntegral <$> getWord16be)
+           (fromIntegral <$> getWord16be)
+       | byteAt flag 1 ->
+           liftM2 (,)
+           (fromIntegral <$> getInt8)
+           (fromIntegral <$> getInt8)
+       | otherwise ->
+           liftM2 (,)
+           (fromIntegral <$> getWord8)
+           (fromIntegral <$> getWord8)
+  (tXX, tXY, tYX, tYY) <-
+    if | byteAt flag 3 -> do
+           x <- ShortFrac <$> getInt16be
+           return (x, 0, 0, x)
+       | byteAt flag 6 -> do
+           x <- ShortFrac <$> getInt16be
+           y <- ShortFrac <$> getInt16be
+           return (x, 0, 0, y)
+       | byteAt flag 7 -> do
+           xx <- ShortFrac <$> getInt16be
+           xy <- ShortFrac <$> getInt16be
+           yx <- ShortFrac <$> getInt16be
+           yy <- ShortFrac <$> getInt16be
+           return (xx, xy, yx, yy)
+       | otherwise -> return (1, 0, 0, 1)
+  instructions <- 
+    if byteAt flag 8
+    then Just <$> do
+      l <- fromIntegral <$> getWord16be
+      V.replicateM l getWord8
+    else return Nothing
+  return (
+    GlyphComponent (fromIntegral gID) instructions tXX tXY tYX tYY
+      cX cY (not $ byteAt flag 1) (byteAt flag 2)
+      (byteAt flag 9) (byteAt flag 10) (byteAt flag 11),
+    byteAt flag 5) -- more components
+
diff --git a/Opentype/Fileformat/Head.hs b/Opentype/Fileformat/Head.hs
new file mode 100644
--- /dev/null
+++ b/Opentype/Fileformat/Head.hs
@@ -0,0 +1,186 @@
+module Opentype.Fileformat.Head where
+import Opentype.Fileformat.Types
+import Data.Time
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.Put
+import Control.Monad
+import Data.Int
+
+-- | This table contains global information about the font. it
+-- records such facts as the font version number, the creation and
+-- modification dates, revision number and basic typographic data that
+-- applies to the font as a whole. this includes a specification of
+-- the font bounding box, the direction in which the font's glyphs are
+-- most likely to be written and other information about the placement
+-- of glyphs in the em square.
+data HeadTable = HeadTable {
+  -- | 0x00010000 for version 1.0.  /Will be auto written./
+  headVersion :: Fixed,
+  -- | set by font manufacturer.
+  fontRevision :: Fixed,
+  -- | baseline for font at y=0
+  baselineYZero :: Bool,
+  -- |  left sidebearing point at x=0;
+  sidebearingXZero :: Bool,
+  -- | instructions may depend on point size;
+  pointsizeDepend :: Bool,
+  -- | Force ppem to integer values for all internal scaler math; may
+  -- use fractional ppem sizes if this bit is clear;
+  integerScaling :: Bool,  
+  -- | /Microsoft/: Instructions may alter advance width (the advance widths might not scale linearly);
+  alterAdvanceWidth :: Bool,
+  -- | /Apple/: This bit should be set in fonts that are intended to e
+  -- laid out vertically, and in which the glyphs have been drawn such
+  -- that an x-coordinate of 0 corresponds to the desired vertical
+  -- baseline.
+  
+  -- bit 6 zero
+  verticalFont :: Bool,
+  -- | /Apple/: This should be set if the font requires layout for
+  -- correct linguistic rendering (e.g. Arabic fonts).
+  linguisticRenderingLayout :: Bool,
+  -- | /Apple/: This should be set for an AAT font which has one or more
+  -- metamorphosis effects designated as happening by default.
+  metamorphosisEffects :: Bool,
+  -- | his bit should be set if the font contains any strong
+  -- right-to-left glyphs.
+  rightToLeftGlyphs :: Bool,
+  -- | This bit should be set if the font contains Indic-style
+  -- rearrangement effects.
+  indicRearrangements :: Bool,
+  -- | /Adobe/: Font data is ‘lossless’ as a results of having been
+  -- subjected to optimizing transformation and/or compression (such
+  -- as e.g. compression mechanisms defined by ISO/IEC 14496-18,
+  -- MicroType Express, WOFF 2.0 or similar) where the original font
+  -- functionality and features are retained but the binary
+  -- compatibility between input and output font files is not
+  -- guaranteed. As a result of the applied transform, the ‘DSIG’
+  -- Table may also be invalidated.
+  losslessFontData :: Bool,
+  -- | /Adobe/: Font converted (produce compatible metrics)
+  convertedFont :: Bool,
+  -- | /Adobe/: Font optimized for ClearType™. Note, fonts that rely on
+  -- embedded bitmaps (EBDT) for rendering should not be considered
+  -- optimized for ClearType, and therefore should keep this bit
+  -- cleared.
+  clearTypeOptimized :: Bool,
+    -- | Last Resort font. If set, indicates that the glyphs encoded in
+  -- the cmap subtables are simply generic symbolic representations of
+  -- code point ranges and don’t truly represent support for those
+  -- code points. If unset, indicates that the glyphs encoded in the
+  -- cmap subtables represent proper support for those code points.
+  lastResortFont :: Bool,
+  -- | Valid range is from 16 to 16384. This value should be a power
+  -- of 2 for fonts that have TrueType outlines.
+
+  -- bit 15 zero
+  unitsPerEm :: Word16,
+  created :: UTCTime,  modified :: UTCTime,
+  -- | /Will be auto written./
+  xMin :: FWord,
+  -- | /Will be auto written./
+  yMin :: FWord,
+  -- | /Will be auto written./
+  xMax :: FWord,
+  -- | /Will be auto written./
+  yMax :: FWord,
+  -- macStyle
+  
+  boldStyle :: Bool,
+  italicStyle :: Bool,
+  underlineStyle :: Bool,
+  outlineStyle :: Bool,
+  shadowStyle :: Bool,
+  condensedStyle :: Bool,
+  extendedStyle :: Bool,
+  -- | Smallest readable size in pixels.
+  lowerRecPPEM :: Word16,
+  -- | deprecated, will be set to 2
+  fontDirectionHint :: Int16,
+  -- | 0 for short offsets, 1 for long.  /Will be auto written./
+  longLocIndices :: Bool,
+  -- | 0 for current format.  /Will be auto written./
+  glyphDataFormat :: Int16
+  }
+  deriving Show
+
+getHeadTable :: Get HeadTable
+getHeadTable = do
+    major <- getWord16be
+    minor <- getWord16be
+    when (major /= 1 && minor /= 0)
+      (fail "Invalid head table")
+    revision <- getWord32be
+    _ <- getWord32be
+    magic <- getWord32be
+    when (magic /= 0x5F0F3CF5)
+      (fail "Invalid magic value in head table")
+    flags <- getWord16be
+    uPe <- getWord16be
+    created_ <- getInt64be
+    modified_ <- getInt64be
+    xMin_ <- getInt16be
+    yMin_ <- getInt16be
+    xMax_ <- getInt16be
+    yMax_ <- getInt16be
+    mcStyle <- getWord16be
+    lRec <- getWord16be
+    fDir <- getInt16be
+    iToL <- getInt16be
+    gd <- getInt16be
+    let flagAt = byteAt flags
+        styleAt = byteAt mcStyle
+    return $ HeadTable 0x00010000 revision
+      (flagAt 0) (flagAt 1) (flagAt 2) (flagAt 3)
+      (flagAt 4) (flagAt 5) (flagAt 7) (flagAt 8) (flagAt 9)
+      (flagAt 10) (flagAt 11) (flagAt 12) (flagAt 13) (flagAt 14) 
+      uPe (getTime created_) (getTime modified_)
+      xMin_ yMin_ xMax_ yMax_
+      (styleAt 0) (styleAt 1) (styleAt 2) (styleAt 3) (styleAt 4)
+      (styleAt 5) (styleAt 6) lRec fDir (iToL /= 0) gd
+
+putHeadTable :: HeadTable -> Put
+putHeadTable headTbl = do
+  putWord16be 1
+  putWord16be 0
+  putWord32be $ fontRevision headTbl
+  putWord32be 0
+  putWord32be 0x5F0F3CF5
+  putWord16be $ makeFlag $ map ($ headTbl)
+    [baselineYZero, sidebearingXZero, pointsizeDepend, integerScaling, alterAdvanceWidth,
+    const False, verticalFont, linguisticRenderingLayout, metamorphosisEffects, rightToLeftGlyphs,
+    indicRearrangements, losslessFontData, convertedFont, clearTypeOptimized, lastResortFont, const False]
+  putWord16be $ unitsPerEm headTbl
+  putInt64be $ putTime $ created headTbl
+  putInt64be $ putTime $ modified headTbl
+  putInt16be $ xMin headTbl
+  putInt16be $ yMin headTbl
+  putInt16be $ xMax headTbl
+  putInt16be $ yMax headTbl
+  putWord16be $ makeFlag $ map ($ headTbl)
+    [boldStyle, italicStyle, underlineStyle, outlineStyle, shadowStyle, condensedStyle, extendedStyle]
+  putWord16be $ lowerRecPPEM headTbl
+  putInt16be 2 -- fontDirectionHint headTbl
+  putInt16be $ fromIntegral $ fromEnum $ longLocIndices headTbl
+  putInt16be 0 -- glyphDataFormat headTbl
+
+secDay :: Int64
+secDay = 60 * 60 * 24
+
+diffSeconds :: Int64
+diffSeconds =
+  secDay * fromIntegral (fromGregorian 1858 11 17 `diffDays` fromGregorian 1904 1 1)
+
+getTime :: Int64  -> UTCTime
+getTime secs = UTCTime (ModifiedJulianDay $ fromIntegral d) (secondsToDiffTime $ fromIntegral t)
+  where (d,t) = (secs - diffSeconds) `quotRem` fromIntegral secDay
+
+putTime :: UTCTime -> Int64
+putTime (UTCTime (ModifiedJulianDay d) t) =
+  fromIntegral d * secDay + diffTimeToSeconds t + diffSeconds
+
+diffTimeToSeconds :: DiffTime -> Int64
+diffTimeToSeconds d =
+  fromIntegral $ diffTimeToPicoseconds d `quot` 1000000000
+
diff --git a/Opentype/Fileformat/Hhea.hs b/Opentype/Fileformat/Hhea.hs
new file mode 100644
--- /dev/null
+++ b/Opentype/Fileformat/Hhea.hs
@@ -0,0 +1,61 @@
+module Opentype.Fileformat.Hhea
+where
+import Opentype.Fileformat.Types
+import Data.Binary.Get
+import Data.Binary.Put
+import Data.Int
+import Data.Word
+import Control.Monad
+
+-- | This table contains information for horizontal layout. 
+data HheaTable = HheaTable {
+  -- | 0x00010000 (1.0), will be auto written.
+  version :: Fixed,
+  -- | Distance from baseline of highest ascender
+  ascent :: FWord,
+  -- | Distance from baseline of lowest descender
+  descent :: FWord,
+  -- | typographic line gap
+  lineGap :: FWord,
+  -- | /Will be auto written./
+  advanceWidthMax :: UFWord,
+  -- | /Will be auto written./
+  minLeftSideBearing :: FWord,
+  -- | /Will be auto written./
+  minRightSideBearing :: FWord,
+  -- | /Will be auto written./
+  xMaxExtent :: FWord,
+  -- | used to calculate the slope of the caret (rise/run) set to 1 for vertical caret
+  caretSlopeRise :: Int16,
+  -- | 0 for vertical
+  caretSlopeRun :: Int16,
+  -- | set value to 0 for non-slanted fonts
+  caretOffset :: FWord,
+  -- | number of advance widths in metrics table. /Will be auto written./
+  numOfLongHorMetrics :: Word16}
+  deriving Show
+
+putHheaTable :: HheaTable -> Put
+putHheaTable table = do
+  putWord32be 0x00010000
+  putInt16be $ ascent table
+  putInt16be $ descent table
+  putInt16be $ lineGap table
+  putWord16be $ advanceWidthMax table
+  putInt16be $ minLeftSideBearing table
+  putInt16be $ minRightSideBearing table
+  putInt16be $ xMaxExtent table
+  putInt16be $ caretSlopeRise table
+  putInt16be $ caretSlopeRun table
+  putInt16be $ caretOffset table
+  replicateM_ 5 $ putInt16be 0
+  putWord16be $ numOfLongHorMetrics table
+  
+getHheaTable :: Get HheaTable
+getHheaTable =
+  HheaTable <$> getWord32be <*> getInt16be <*>
+  getInt16be <*> getInt16be <*> getWord16be <*>
+  getInt16be <*> getInt16be <*> getInt16be <*>
+  getInt16be <*> getInt16be <*> getInt16be <*>
+  (skip 10 *> getWord16be)
+  
diff --git a/Opentype/Fileformat/Kern.hs b/Opentype/Fileformat/Kern.hs
new file mode 100644
--- /dev/null
+++ b/Opentype/Fileformat/Kern.hs
@@ -0,0 +1,69 @@
+module Opentype.Fileformat.Kern
+where
+import Opentype.Fileformat.Types
+import Data.Word
+import Data.Binary.Put
+import Data.Binary.Get
+import Data.Bits
+import Control.Monad
+import Data.Foldable
+
+-- | @KernPair left right adjustment@: Pair of kerning values.  left
+-- and right are indices in the glyph table.
+data KernPair = KernPair Word16 Word16 FWord
+  deriving Show
+
+data KernTable = KernTable {
+  -- | various flags
+  coverage :: Word8,
+  kernPairs :: [KernPair]}
+  deriving Show
+
+getKernTable :: Get KernTable
+getKernTable = do
+  version <- getWord16be
+  when (version /= 0) $
+    fail "Unsupported kern table."
+  nTables <- getWord16be
+  if nTables == 0
+    then return $ KernTable 0 []
+    else do
+    skip 4
+    cov <- getWord16be
+    if cov .&. 0xff00 /= 0
+      then return $ KernTable 0 []
+      else do
+      nPairs <- getWord16be
+      skip 6
+      fmap (KernTable (fromIntegral $ cov .&. 0xff)) $
+        replicateM (fromIntegral nPairs) $
+        KernPair <$> getWord16be <*>  getWord16be <*> getInt16be
+
+putKernTable :: KernTable -> Put
+putKernTable (KernTable cov pairs) = do
+  putWord16be 0
+  putWord16be 1
+  putWord16be 0
+  putWord16be $ fromIntegral $ 14+6 * length pairs
+  putWord16be $ fromIntegral cov
+  putWord16be $ fromIntegral len
+  putWord16be searchRange
+  putWord16be entrySelector
+  putWord16be $ len*6 - searchRange
+  for_ pairs $ \(KernPair l r v) -> do
+    putWord16be l
+    putWord16be r
+    putInt16be v
+      where
+        len = fromIntegral $ length pairs
+        entrySelector = fromIntegral $ iLog2 len
+        searchRange = 6 * (1 `shift` fromIntegral entrySelector)
+    
+        
+
+
+      
+      
+    
+  
+  
diff --git a/Opentype/Fileformat/Maxp.hs b/Opentype/Fileformat/Maxp.hs
new file mode 100644
--- /dev/null
+++ b/Opentype/Fileformat/Maxp.hs
@@ -0,0 +1,72 @@
+module Opentype.Fileformat.Maxp
+where
+import Opentype.Fileformat.Types
+import Data.Binary.Get
+import Data.Binary.Put
+import Data.Word
+
+
+-- | The 'maxp' table establishes the memory requirements for a font.
+-- Only instruction data needs to be filled in, since instructions
+-- aren't yet supported.  __Note:__ The cff version of this table will
+-- be handled automatically.
+data MaxpTable = MaxpTable {
+  -- | 0x00010000 (1.0).  /Will be auto written./ 
+  maxpVersion :: Fixed,
+  -- | the number of glyphs in the font.  /Will be auto written./
+  numGlyphs :: Word16,
+  -- | points in non-compound glyph.  /Will be auto written./
+  maxPoints :: Word16,
+  -- | contours in non-compound glyph.  /Will be auto written./
+  maxContours :: Word16,
+  -- | points in compound glyph.  /Will be auto written./
+  maxComponentPoints :: Word16,
+  -- | contours in compound glyph. /Will be auto written./
+  maxComponentContours :: Word16,
+  -- | set to 2
+  maxZones :: Word16,
+  -- | points used in Twilight Zone (Z0)
+  maxTwilightPoints :: Word16,
+  -- | number of Storage Area locations
+  maxStorage :: Word16,
+  -- | number of FDEFs
+  maxFunctionDefs :: Word16,
+  -- | number of IDEFs
+  maxInstructionDefs :: Word16,
+  -- | maximum stack depth
+  maxStackElements :: Word16,
+  -- | byte count for glyph instructions
+  maxSizeOfInstructions :: Word16,
+  -- | number of glyphs referenced at top level. /Will be auto written./
+  maxComponentElements :: Word16,
+  -- | levels of recursion.  /Will be auto written./
+  maxComponentDepth :: Word16}
+  deriving Show
+
+putMaxpTable :: MaxpTable -> Put
+putMaxpTable maxp = do
+  putWord32be $ maxpVersion maxp
+  putWord16be $ numGlyphs maxp
+  putWord16be $ maxPoints maxp
+  putWord16be $ maxContours maxp
+  putWord16be $ maxComponentPoints maxp
+  putWord16be $ maxComponentContours maxp
+  putWord16be $ maxZones maxp
+  putWord16be $ maxTwilightPoints maxp
+  putWord16be $ maxStorage maxp
+  putWord16be $ maxFunctionDefs maxp
+  putWord16be $ maxInstructionDefs maxp
+  putWord16be $ maxStackElements maxp
+  putWord16be $ maxSizeOfInstructions maxp
+  putWord16be $ maxComponentElements maxp
+  putWord16be $ maxComponentDepth maxp
+  
+getMaxpTable :: Get MaxpTable
+getMaxpTable =
+  MaxpTable <$> getWord32be <*>
+  getWord16be <*> getWord16be <*> getWord16be <*>
+  getWord16be <*> getWord16be <*> getWord16be <*>
+  getWord16be <*> getWord16be <*> getWord16be <*>
+  getWord16be <*> getWord16be <*> getWord16be <*>
+  getWord16be <*> getWord16be 
+  
diff --git a/Opentype/Fileformat/Name.hs b/Opentype/Fileformat/Name.hs
new file mode 100644
--- /dev/null
+++ b/Opentype/Fileformat/Name.hs
@@ -0,0 +1,95 @@
+module Opentype.Fileformat.Name
+where
+import Opentype.Fileformat.Types
+import Data.List (sort)
+import Data.Word
+import Control.Monad
+import Data.Binary.Put
+import Data.Foldable (for_, traverse_)
+import Data.Traversable (for)
+import qualified Data.ByteString as Strict
+
+-- | This table allows multilingual strings to be associated with the
+-- OpenType™ font file. These strings can represent copyright notices,
+-- font names, family names, style names, and so on. To keep this
+-- table short, the font manufacturer may wish to make a limited set
+-- of entries in some small set of languages; later, the font can be
+-- “localized” and the strings translated or added. Other parts of the
+-- OpenType font file that require these strings can then refer to
+-- them simply by their index number. Clients that need a particular
+-- string can look it up by its platform ID, character encoding ID,
+-- language ID and name ID. Note that some platforms may require
+-- single byte character strings, while others may require double byte
+-- strings.
+--
+--  For historical reasons, some applications which install fonts
+-- perform version control using Macintosh platform (platform ID 1)
+-- strings from the 'name' table. Because of this, it is strongly
+-- recommended that the 'name' table of all fonts include Macintosh
+-- platform strings and that the syntax of the version number (name id
+-- 5) follows the guidelines given in the opentype specification.
+--
+-- The encoding for each bytestring depends on the PlatformID and
+-- encodingID.  This library doesn't do any conversion.  For more
+-- information see the opentype specification:
+-- https://www.microsoft.com/typography/otspec/name.htm
+data NameTable = NameTable [NameRecord]
+  deriving Show
+
+data NameRecord = NameRecord {
+  namePlatform :: PlatformID,
+  nameEncoding :: Word16,
+  nameLanguage :: Word16,
+  nameID :: Word16,
+  nameString :: Strict.ByteString}
+  deriving Show
+
+instance Ord NameRecord where
+  compare (NameRecord pID eID lang nID _)
+    (NameRecord pID2 eID2 lang2 nID2 _) =
+    compare (pID, eID, lang, nID) (pID2, eID2, lang2, nID2)
+
+instance Eq NameRecord where
+  (NameRecord pID eID lang nID _) ==
+    (NameRecord pID2 eID2 lang2 nID2 _) =
+    (pID, eID, lang, nID) == (pID2, eID2, lang2, nID2)    
+
+putNameTable :: NameTable -> Put
+putNameTable (NameTable records_) = do
+  putWord16be 0
+  putWord16be $ fromIntegral len
+  putWord16be $ fromIntegral $ len * 12 + 6
+  for_ (zip offsets records) $ \(offset, r) -> do
+    putPf $ namePlatform r
+    putWord16be $ nameEncoding r
+    putWord16be $ nameLanguage r
+    putWord16be $ nameID r
+    putWord16be $ fromIntegral $ Strict.length $ nameString r
+    putWord16be offset
+  traverse_ (putByteString.nameString) records
+  where len = length records
+        records = sort records_
+        lengths = map (fromIntegral . Strict.length . nameString) records
+        offsets = scanl (+) 0 lengths
+
+readNameTable :: Strict.ByteString -> Either String NameTable
+readNameTable bs = do
+  version <- index16 bs 0
+  when (version > 0) $ fail "Unsupported name table format."
+  len <- index16 bs 1 
+  storage <- index16 bs 2
+  records <- for [0..len-1] $ \i -> do
+    pf <- toPf =<< index16 bs (3 + i*6)
+    enc <- index16 bs $ 3 + i*6 + 1
+    lang <- index16 bs $ 3 + i*6 + 2
+    nID <- index16 bs $ 3 + i*6 + 3
+    len2 <- index16 bs $ 3 + i*6 + 4 
+    offset <- index16 bs $ 3 + i*6 + 5
+    Right (offset, len2, NameRecord pf enc lang nID)
+  records2 <- for records $
+    \(offset, len2, r) ->
+      if storage+offset+len2 > fromIntegral (Strict.length bs)
+        then Left "string storage bounds exceeded"
+        else Right $ r (Strict.take (fromIntegral len2) $
+                        Strict.drop (fromIntegral $ offset+storage) bs)
+  return $ NameTable records2
diff --git a/Opentype/Fileformat/OS2.hs b/Opentype/Fileformat/OS2.hs
new file mode 100644
--- /dev/null
+++ b/Opentype/Fileformat/OS2.hs
@@ -0,0 +1,150 @@
+module Opentype.Fileformat.OS2
+where
+import Data.Word
+import Data.Int
+import Data.Binary.Put
+import Data.Binary.Get
+import Control.Monad (when)
+
+-- | The OS/2 table consists of a set of metrics that are required in
+-- OpenType fonts.  For a description of these fields see:
+-- https://www.microsoft.com/typography/otspec/os2.htm
+data OS2Table = OS2Table {
+  os2version :: Word16,
+  xAvgCharWidth :: Int16,
+  usWeightClass :: Word16,
+  usWidthClass :: Word16,
+  fsType :: Word16,
+  ySubscriptXSize :: Int16,
+  ySubscriptYSize :: Int16,
+  ySubscriptXOffset :: Int16,
+  ySubscriptYOffset :: Int16,
+  ySuperscriptXSize :: Int16,
+  ySuperscriptYSize :: Int16,
+  ySuperscriptXOffset :: Int16,
+  ySuperscriptYOffset :: Int16,
+  yStrikeoutSize :: Int16,
+  yStrikeoutPosition :: Int16,
+  bFamilyClass :: Int16,
+  bFamilyType :: Int8,
+  bSerifStyle :: Int8,
+  bWeight :: Int8,
+  bProportion :: Int8,
+  bContrast :: Int8,
+  bStrokeVariation :: Int8,
+  bArmStyle :: Int8,
+  bLetterform :: Int8,
+  bMidline :: Int8,
+  bXHeight :: Int8,
+  ulUnicodeRange1 :: Word32,
+  ulUnicodeRange2 :: Word32,
+  ulUnicodeRange3 :: Word32,
+  ulUnicodeRange4 :: Word32,
+  achVendID :: Word32,
+  fsSelection :: Word16,
+  usFirstCharIndex :: Word16,
+  usLastCharIndex :: Word16,
+  sTypoAscender :: Int16,
+  sTypoDescender :: Int16,
+  sTypoLineGap :: Int16,
+  usWinAscent :: Word16,
+  usWinDescent :: Word16,
+  ulCodePageRange1 :: Word32,
+  ulCodePageRange2 :: Word32,
+  sxHeight :: Int16,
+  sCapHeight :: Int16,
+  usDefaultChar :: Word16,
+  usBreakChar :: Word16,
+  usMaxContext :: Word16,
+  usLowerOpticalPointSize :: Word16,
+  usUpperOpticalPointSize :: Word16
+  }
+  deriving Show
+
+getOS2Table :: Get OS2Table
+getOS2Table = do
+  version <- getWord16be
+  partial1 <- OS2Table version <$> getInt16be
+    <*> getWord16be <*> getWord16be <*> getWord16be
+    <*> getInt16be <*> getInt16be <*> getInt16be
+    <*> getInt16be <*> getInt16be <*> getInt16be
+    <*> getInt16be <*> getInt16be <*> getInt16be
+    <*> getInt16be <*> getInt16be <*> getInt8
+    <*> getInt8 <*> getInt8 <*> getInt8 <*> getInt8
+    <*> getInt8 <*> getInt8 <*> getInt8 <*> getInt8
+    <*> getInt8 <*> getWord32be <*> getWord32be <*> getWord32be
+    <*> getWord32be <*> getWord32be <*> getWord16be
+    <*> getWord16be <*> getWord16be <*> getInt16be
+    <*> getInt16be <*> getInt16be <*> getWord16be <*> getWord16be
+  if version == 0 then
+    return $ partial1 0 0 0 0 0 0 0 0 0
+    else do
+    partial2 <- partial1 <$> getWord32be <*> getWord32be
+    if version == 1 then
+      return $ partial2 0 0 0 0 0 0 0
+      else do
+      partial3 <- partial2 <$> getInt16be <*> getInt16be <*>
+        getWord16be <*> getWord16be <*> getWord16be
+      if version < 5 then
+        return $ partial3 0 0
+        else partial3 <$> getWord16be <*> getWord16be
+
+putOS2Table :: OS2Table -> Put
+putOS2Table (OS2Table version f2 f3 f4 f5 f6 f7 f8 f9 f10
+             f11 f12 f13 f14 f15 f16 f17 f18 f19 f20
+             f21 f22 f23 f24 f25 f26 f27 f28 f29 f30
+             f31 f32 f33 f34 f35 f36 f37 f38 f39 f40
+             f41 f42 f43 f44 f45 f46 f47 f48) =
+  do putWord16be version
+     putInt16be f2
+     putWord16be f3
+     putWord16be f4
+     putWord16be f5
+     putInt16be f6
+     putInt16be f7
+     putInt16be f8
+     putInt16be f9
+     putInt16be f10
+     putInt16be f11
+     putInt16be f12
+     putInt16be f13
+     putInt16be f14
+     putInt16be f15
+     putInt16be f16
+     putInt8 f17
+     putInt8 f18
+     putInt8 f19
+     putInt8 f20
+     putInt8 f21
+     putInt8 f22
+     putInt8 f23
+     putInt8 f24
+     putInt8 f25
+     putInt8 f26
+     putWord32be f27
+     putWord32be f28
+     putWord32be f29
+     putWord32be f30
+     putWord32be f31
+     putWord16be f32
+     putWord16be f33
+     putWord16be f34
+     putInt16be f35
+     putInt16be f36
+     putInt16be f37
+     putWord16be f38
+     putWord16be f39
+     when (version > 0) $ do
+       putWord32be f40
+       putWord32be f41
+       when (version > 1) $ do
+         putInt16be f42
+         putInt16be f43
+         putWord16be f44
+         putWord16be f45
+         putWord16be f46
+         when (version > 4) $ do
+           putWord16be f47
+           putWord16be f48
+           
+
diff --git a/Opentype/Fileformat/Post.hs b/Opentype/Fileformat/Post.hs
new file mode 100644
--- /dev/null
+++ b/Opentype/Fileformat/Post.hs
@@ -0,0 +1,135 @@
+{-# LANGUAGE LambdaCase #-}
+module Opentype.Fileformat.Post where
+import Opentype.Fileformat.Types
+import Data.Word
+import Data.Char
+import Data.Binary.Put
+import Data.Binary.Get
+import Data.Foldable
+import Data.Traversable
+import Control.Monad (replicateM, replicateM_, when)
+
+data PostVersion =
+  -- | The first 258 Glyphs have standard names
+  PostTable1 |
+  -- | Order of glyph names can be changed, and glyphs can have
+  -- non-standard names.
+  PostTable2 |
+  -- | No glyph names
+  PostTable3
+  deriving (Eq, Show)
+
+-- |This table contains additional information needed to use TrueType
+-- or OpenType™ fonts on PostScript printers. This includes data for
+-- the FontInfo dictionary entry and the PostScript names of all the
+-- glyphs. For more information about PostScript names, see the Adobe
+-- document Unicode and Glyph Names.
+--
+-- Versions 1.0, 2.0, and 2.5 refer to TrueType fonts and OpenType
+-- fonts with TrueType data. OpenType fonts with TrueType data may
+-- also use Version 3.0. OpenType fonts with CFF data use Version 3.0
+-- only.
+data PostTable = PostTable {
+  postVersion :: PostVersion,
+  -- | Italic angle in counter-clockwise degrees from the
+  -- vertical. Zero for upright text, negative for text that leans to
+  -- the right (forward).
+  italicAngle :: Fixed,
+  -- | suggested values for the underline thickness.
+  underlinePosition :: FWord,
+  -- | This is the suggested distance of the top of the underline from
+  -- the baseline (negative values indicate below baseline).
+  --
+  -- The PostScript definition of this FontInfo dictionary key (the y
+  -- coordinate of the center of the stroke) is not used for
+  -- historical reasons. The value of the PostScript key may be
+  -- calculated by subtracting half the underlineThickness from the
+  -- value of this field.
+  underlineThickness :: FWord,
+  -- | Set to 0 if the font is proportionally spaced, non-zero if the
+  -- font is not proportionally spaced (i.e. monospaced).
+  isFixedPitch :: Word32,
+  -- | Minimum memory usage when an OpenType font is downloaded.  Set
+  -- to 0 if unsure.
+  minMemType42 :: Word32,
+  -- | Maximum memory usage when an OpenType font is downloaded.  Set
+  -- to 0 if unsure.
+  maxMemType42 :: Word32,
+  -- | Minimum memory usage when an OpenType font is downloaded as a
+  -- Type 1 font.  Set to 0 if unsure.
+  minMemType1 :: Word32,
+  -- | Maximum memory usage when an OpenType font is downloaded as a
+  -- Type 1 font.  Set to 0 if unsure.
+  maxMemType1 :: Word32,
+  -- | Ordinal number of the glyph in 'post' string tables.  For
+  -- format 2.0 only.
+  -- 
+  -- If the name index is between 0 and 257, treat the name index as a
+  -- glyph index in the Macintosh standard order. If the name index is
+  -- between 258 and 65535, then subtract 258 and use that to index
+  -- into the list of Pascal strings at the end of the table. Thus a
+  -- given font may map some of its glyphs to the standard glyph
+  -- names, and some to its own names.
+  --
+  -- If you do not want to associate a PostScript name with a
+  -- particular glyph, use index number 0 which points to the name
+  -- .notdef.
+  glyphNameIndex :: [Int],
+  -- | strings for indices 258 and upwards.
+  postStrings :: [String]
+  }
+  deriving (Show)
+
+getPostTable :: Get PostTable
+getPostTable = do
+  iVersion <- getWord32be
+  version <- case iVersion of
+    0x00010000 -> return PostTable1
+    0x00020000 -> return PostTable2
+    0x00030000 -> return PostTable3
+    _ -> fail "Unsupported post table version."
+  itA <- getWord32be
+  ulPos <- getInt16be
+  ulThick <- getInt16be
+  isFixed <- getWord32be
+  min42 <- getWord32be
+  max42 <- getWord32be
+  min1 <- getWord32be
+  max1 <- getWord32be
+  if version /= PostTable2
+    then return $ PostTable version itA ulPos
+         ulThick isFixed min42 max42 min1 max1 [] []
+    else do
+      n <- fromIntegral <$> getWord16be
+      gIndex <- replicateM n (fromIntegral <$> getWord16be)
+      pStrings <- replicateM ((maximum gIndex+1)-258) $ do
+        l <- fromIntegral <$> getWord8
+        replicateM l ((chr.fromIntegral) <$> getWord8)
+      return $ PostTable version itA ulPos
+         ulThick isFixed min42 max42 min1 max1 gIndex pStrings
+
+putPostTable :: PostTable -> Put
+putPostTable table = do
+  putWord32be $ case postVersion table of
+    PostTable1 -> 0x00010000
+    PostTable2 -> 0x00020000
+    PostTable3 -> 0x00030000
+  putWord32be $ italicAngle table
+  putInt16be $ underlinePosition table
+  putInt16be $ underlineThickness table
+  putWord32be $ isFixedPitch table
+  putWord32be $ minMemType42 table
+  putWord32be $ maxMemType42 table
+  putWord32be $ minMemType1 table
+  putWord32be $ maxMemType1 table
+  when (postVersion table == PostTable2) $ do
+    let nameIndexLen = length (glyphNameIndex table)
+        nameMax = maximum (glyphNameIndex table) + 1
+        postLen = length (postStrings table)
+    putWord16be $ fromIntegral nameIndexLen
+    traverse_ (putWord16be.fromIntegral) $ glyphNameIndex table
+    for_ (take (nameMax-258) $ postStrings table) $ \str -> do
+      putWord8 $ fromIntegral $ length str
+      traverse_ (putWord8.fromIntegral.ord) str
+    replicateM_ (nameMax-258-postLen) (putWord8 0)
+    
diff --git a/Opentype/Fileformat/Types.hs b/Opentype/Fileformat/Types.hs
new file mode 100644
--- /dev/null
+++ b/Opentype/Fileformat/Types.hs
@@ -0,0 +1,127 @@
+module Opentype.Fileformat.Types where
+import Data.Word
+import Data.Int
+import Data.Bits
+import Data.Binary.Put
+import Data.ByteString.Unsafe
+import qualified Data.ByteString as Strict
+import qualified Data.Map as M
+
+-- | A ShortFrac is an 16 bit signed fixed number with a bias of
+-- 14. This means it can represent numbers between 1.999 (0x7fff) and
+-- -2.0 (0x8000). 1.0 is stored as 16384 (0x4000) and -1.0 is stored
+-- as -16384 (0xc000).  Efficient numeric instances are provided.
+newtype ShortFrac = ShortFrac Int16
+  
+-- | signed fixed-point number
+type Fixed = Word32
+-- | FWord describes a quantity in FUnits, the smallest
+-- measurable distance in em space.
+type FWord = Int16
+-- | UFWord describes a quantity in FUnits, the smallest measurable
+-- distance in em space.
+type UFWord = Word16
+-- | the glyph index in the glyph table
+type GlyphID = Word16
+
+data PlatformID =
+  UnicodePlatform |
+  -- | /DEPRECATED/
+  MacintoshPlatform |
+  MicrosoftPlatform
+  deriving (Ord, Eq, Show)
+
+
+type WordMap a = M.Map Word32 a
+-- return larged power of 2 <= i 
+iLog2 :: Integral a => a -> a
+iLog2 = iLog2' 0 where
+  iLog2' base i
+    | i > 0 = iLog2' (base+1) (i `quot` 2) 
+    | otherwise = base-1
+
+byteAt :: (Bits a, Num a) => a -> Int -> Bool
+byteAt flag i = flag .&. 1 `shift` i /= 0
+{-# SPECIALIZE byteAt :: Word8 -> Int -> Bool #-}
+{-# SPECIALIZE byteAt :: Word16 -> Int -> Bool #-}
+
+makeFlag :: [Bool] -> Word16
+makeFlag l =
+  fromIntegral $ sum $ zipWith (*) (iterate (*2) 1) $
+  map fromEnum l
+
+instance Num ShortFrac where
+  (ShortFrac a) + (ShortFrac b) = ShortFrac $ a + b
+  (ShortFrac a) - (ShortFrac b) = ShortFrac $ a - b
+  (ShortFrac a) * (ShortFrac b) =
+    ShortFrac $ fromIntegral (((fromIntegral a :: Int32) * (fromIntegral b :: Int32)) `shift` (-14))
+  abs (ShortFrac a) = ShortFrac $ abs a
+  fromInteger i = ShortFrac $ fromIntegral i `shift` 14
+  signum (ShortFrac a) = fromIntegral $ signum a
+
+instance Eq ShortFrac where
+  (ShortFrac a) == (ShortFrac b) = a == b
+
+instance Ord ShortFrac where
+  compare (ShortFrac a) (ShortFrac b) = compare a b
+
+instance Fractional ShortFrac where
+  fromRational r =
+    ShortFrac $ fromIntegral $ 
+    floor ((r+2) * 0x4000) - (0x8000::Word16)
+  (ShortFrac a) / (ShortFrac b) =
+    ShortFrac $ fromIntegral $
+    ((fromIntegral a :: Int32) `shift` 14) `quot` fromIntegral b
+
+instance Show ShortFrac where
+  show a = show (realToFrac a :: Float)
+
+instance Real ShortFrac where
+  toRational (ShortFrac a) =
+    fromIntegral ((fromIntegral a::Word16) + 0x8000) / 0x4000 - 2
+
+instance RealFrac ShortFrac where
+  properFraction (ShortFrac a)
+    | a < 0 && f /= 0 = (i+1, ShortFrac (-f))
+    | otherwise = (i, ShortFrac f)
+    where i = fromIntegral (((fromIntegral a :: Word16) + 0x8000) `shift` (-14)) - 2
+          f = a .&. 0x3fff
+
+putShortFrac :: ShortFrac -> Put
+putShortFrac (ShortFrac a) = putInt16be a
+
+putPf :: PlatformID -> Put
+putPf UnicodePlatform = putWord16be 0
+putPf MacintoshPlatform = putWord16be 1
+putPf MicrosoftPlatform = putWord16be 3
+
+toPf :: Word16 -> Either String PlatformID
+toPf i =
+  case i of
+    0 -> Right UnicodePlatform
+    1 -> Right MacintoshPlatform
+    3 -> Right MicrosoftPlatform
+    j -> Left $ "unknown platformID " ++ show j
+
+index16 :: Strict.ByteString -> Word16 -> Either String Word16
+index16 bs i
+  | Strict.length bs < fromIntegral ((i+1)*2) ||
+    i < 0 = Left $ "Index " ++ show i ++ " out of Bounds"
+  | otherwise = Right $ b1 * 256 + b2
+  where
+    b1, b2 :: Word16
+    b1 = fromIntegral $ unsafeIndex bs (fromIntegral $ i*2)
+    b2 = fromIntegral $ unsafeIndex bs (fromIntegral $ i*2 + 1)
+
+index32 :: Strict.ByteString -> Word32 -> Either String Word32
+index32 bs i
+  | Strict.length bs < fromIntegral ((i+1)*4) ||
+    i < 0 = Left $ "Index " ++ show i ++ " Out of Bounds"
+  | otherwise = Right $ b1 `shift` 24 .|. b2 `shift` 16 .|. b3 `shift` 8 .|. b4
+  where
+    b1, b2, b3, b4 :: Word32
+    b1 = fromIntegral $ unsafeIndex bs (fromIntegral $ i*4)
+    b2 = fromIntegral $ unsafeIndex bs (fromIntegral $ i*4 + 1)
+    b3 = fromIntegral $ unsafeIndex bs (fromIntegral $ i*4 + 2)
+    b4 = fromIntegral $ unsafeIndex bs (fromIntegral $ i*4 + 3)
+
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,6 @@
+#!/usr/bin/runhaskell 
+> module Main where
+> import Distribution.Simple
+> main :: IO ()
+> main = defaultMain
+
diff --git a/opentype.cabal b/opentype.cabal
new file mode 100644
--- /dev/null
+++ b/opentype.cabal
@@ -0,0 +1,46 @@
+name: opentype
+version: 0.0.1
+cabal-version: >=1.8
+build-type: Simple
+license: BSD3
+license-file: LICENSE
+copyright: Kristof Bastiaensen (2014)
+maintainer: Kristof Bastiaensen
+stability: Very Unstable
+bug-reports: https://github.com/kuribas/haskell-opentype/issues
+synopsis: Opentype loading and writing
+description: This library supports loading and writing of opentype files.
+category: Typography
+author: Kristof Bastiaensen
+data-dir: ""
+ 
+source-repository head
+  type: git
+  location: https://github.com/kuribas/haskell-opentype
+ 
+library
+  build-depends:
+    base >=3 && <5, binary >=0.8.1.0,
+    bytestring >0.10.0, containers >=0.5.3, ghc >=7.10.0, time >1.4.0,
+    vector >=0.10, pretty-hex >=1.0
+  exposed-modules: Opentype.Fileformat
+  exposed: True
+  buildable: True
+  other-modules:
+    Opentype.Fileformat.Types
+    Opentype.Fileformat.Glyph
+    Opentype.Fileformat.Cmap
+    Opentype.Fileformat.Head
+    Opentype.Fileformat.Hhea
+    Opentype.Fileformat.Maxp
+    Opentype.Fileformat.Name
+    Opentype.Fileformat.Post
+    Opentype.Fileformat.Kern
+    Opentype.Fileformat.OS2
+  ghc-options: -Wall
+ 
+test-suite test
+  type: exitcode-stdio-1.0
+  main-is: test.hs
+  buildable: True
+  hs-source-dirs: tests
diff --git a/tests/test.hs b/tests/test.hs
new file mode 100644
--- /dev/null
+++ b/tests/test.hs
@@ -0,0 +1,1 @@
+main = return ()
