{-# LANGUAGE MultiWayIf, TupleSections, DeriveTraversable #-}
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_, foldlM)
import Control.Monad
import Control.Monad.Cont
import Data.List (foldl')
import Data.Maybe (isJust, fromMaybe)
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.Bits
import Data.Binary
import Data.Binary.Put
import Data.Binary.Get
import Lens.Micro

-- | 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.
newtype GlyfTable = GlyfTable {glyphVector :: (V.Vector (Glyph Int))}
  deriving Show

type StandardGlyph = Glyph Int

emptyGlyfTable :: GlyfTable
emptyGlyfTable = GlyfTable V.empty

-- | The glyph type is parametrized over the type of glyph reference for compound glyphs.
data Glyph a = Glyph {
  -- | The name of the glyph.  This field isn't used when writing
  -- truetype files.
  glyphName :: String,
  advanceWidth :: Word16,
  leftSideBearing :: Int16,
  -- | Bounding box: /will be overwritten/
  glyphXmin :: FWord,
  -- | Bounding box: /will be overwritten/
  glyphYmin :: FWord,
  -- | Bounding box: /will be overwritten/
  glyphXmax :: FWord,
  -- | Bounding box: /will be overwritten/
  glyphYmax :: FWord,
  glyphOutlines :: GlyphOutlines a}
  deriving (Show, Functor, Foldable, Traversable)

data GlyphOutlines a =
  GlyphContours [[CurvePoint]] Instructions |
  CompositeGlyph [GlyphComponent a]
  deriving (Show, Functor, Foldable, Traversable)

-- | traversal over simple glyph contours
_glyphContours :: Traversal' StandardGlyph [[CurvePoint]]
_glyphContours f glyph = case glyphOutlines glyph of
  GlyphContours pts instrs -> (\pts2 -> glyph {glyphOutlines = GlyphContours pts2 instrs})
                              <$> f pts
  _ -> pure glyph

-- | instructions for simple glyphs
_glyphInstructions :: Traversal' StandardGlyph Instructions
_glyphInstructions f glyph = case glyphOutlines glyph of
  GlyphContours pts instrs -> (\instrs2 -> glyph {glyphOutlines = GlyphContours pts instrs2})
                              <$> f instrs
  _ -> pure glyph

-- | traversal over compound glyph components
_glyphComponents :: Traversal' StandardGlyph [GlyphComponent Int]
_glyphComponents f glyph = case glyphOutlines glyph of
  CompositeGlyph comps -> (\c -> glyph {glyphOutlines = CompositeGlyph c}) <$> f comps
  _ -> pure glyph

-- | @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 a =
  GlyphComponent {
  componentID :: a,
  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,
  -- | If Just True, The component offset should be scaled by the
  -- rasterizer.  If the value is set to Nothing, it is platform
  -- dependent (False for opentype).  Set this value to "Just False"
  -- for new fonts.
  scaledComponentOffset :: Maybe Bool
  }
  deriving (Show, Functor, Foldable, Traversable)

sum' :: Num a => [a] -> a
sum' = foldl' (+) 0

emptyGlyph :: StandardGlyph
emptyGlyph = Glyph ".notdef" 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 StandardGlyph)
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 :: Bool -> V.Vector StandardGlyph -> PutM (V.Vector Int)
writeGlyphs scale vec = traverse (writeGlyph . updateBB scale vec) vec

writeGlyph :: StandardGlyph -> 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 StandardGlyph -> 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 :: StandardGlyph -> 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 StandardGlyph
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 x2 y2 on) (CurvePoint x1 y1 _) =
      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 = tail . scanl (+) 0 
  
getContour :: Int -> Get (GlyphOutlines Int)
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 :: StandardGlyph -> Int16
glyphRsb g =
  fromIntegral (advanceWidth g) - glyphExtent g
  
glyphExtent glyf = leftSideBearing glyf + (glyphXmax glyf - glyphXmin glyf)

extendBB :: (FWord, FWord, FWord, FWord)
         -> (FWord, FWord, FWord, FWord)
         -> (FWord, FWord, FWord, FWord) 
extendBB (xMin1, yMin1, xMax1, yMax1)
  (xMin2, yMin2, xMax2, yMax2) =
  (min xMin1 xMin2, min yMin1 yMin2,
   max xMax1 xMax2, max yMax1 yMax2)

extendBB2 :: (FWord, FWord, FWord, FWord) -> CurvePoint
          -> (FWord, FWord, FWord, FWord) 
extendBB2 (xMin1, yMin1, xMax1, yMax1) (CurvePoint x y _) =
  (min xMin1 x, min yMin1 y, max xMax1 x, max yMax1 y)

minBB :: (FWord, FWord, FWord, FWord)
minBB = (maxBound, maxBound, minBound, minBound)

safeIndex :: Int -> [a] -> Maybe a
safeIndex _ [] = Nothing
safeIndex 0 (x:_) = Just x
safeIndex n (_:l) = safeIndex (n-1) l

onCurve :: CurvePoint -> Bool
onCurve (CurvePoint _ _ on) = on

-- get scaled on-curve points
getScaledContours' :: Int -> Bool -> V.Vector StandardGlyph -> StandardGlyph -> [[CurvePoint]]
getScaledContours' d scale vec glyph
  | d <= 0 = []
  | otherwise =
    case glyphOutlines glyph of
      GlyphContours cp _ -> cp
      CompositeGlyph comps ->
        flip runCont id $ foldlM scalePoints [] comps
    where
      getCompContours :: GlyphComponent Int -> [[CurvePoint]]
      getCompContours comp =
        case vec V.!? componentID comp of
          Nothing -> []
          Just g2 -> getScaledContours' (d-1) scale vec g2
      scalePoints :: [[CurvePoint]] -> GlyphComponent Int -> Cont [[CurvePoint]] [[CurvePoint]]
      scalePoints pts comp
        | matchPoints comp = cont $ \next -> 
            let pts2 = getCompContours comp
                (tx, ty) = fromMaybe (0, 0) $ do
                  CurvePoint x1 y1 _ <- safeIndex (componentX comp) $ concat pts
                  CurvePoint x2 y2 _ <- safeIndex (componentY comp) $ concat pts2
                  return (realToFrac $ x1-x2, realToFrac $ y1-y2)
            in if useMyMetrics comp
               then map (map (scalePt tx ty)) pts2
               else next $ pts ++ map (map (scalePt tx ty)) pts2
        | otherwise =  cont $ \next -> 
            if useMyMetrics comp
            then map (map (scalePt offsetX offsetY)) (getCompContours comp)
            else next $ pts ++ map (map (scalePt offsetX offsetY)) (getCompContours comp)
            where
              sqr x = x*x
              (offsetX, offsetY) =
                if fromMaybe scale (scaledComponentOffset comp)
                then (realToFrac (componentX comp) *
                      sqrt (sqr (realToFrac (componentXX comp)) +
                            sqr (realToFrac (componentYX comp))),
                      realToFrac (componentY comp) *
                      sqrt (sqr (realToFrac (componentXY comp)) +
                            sqr (realToFrac (componentYY comp))))
                else (realToFrac (componentX comp), realToFrac (componentY comp))
              xx, xy, yx, yy :: Double
              (xx, xy, yx, yy) =
                (realToFrac (componentXX comp),
                 realToFrac (componentXY comp),
                 realToFrac (componentYX comp),
                 realToFrac (componentYY comp))
              scalePt tx ty (CurvePoint x y on) =
                if roundXYtoGrid comp then
                  CurvePoint
                  (round tx + round (realToFrac x * xx + realToFrac y * xy))
                  (round ty + round (realToFrac x * yx + realToFrac y * yy))
                  on
                else
                  CurvePoint
                  (round $ realToFrac x * xx + realToFrac y * xy + tx)
                  (round $ realToFrac x * yx + realToFrac y * yy + ty)
                  on

glyphBB :: Bool -> V.Vector StandardGlyph -> StandardGlyph -> (FWord, FWord, FWord, FWord)
glyphBB scale vec glyph =
  foldl' extendBB2 minBB $
  filter onCurve $ concat $
  getScaledContours' 10 scale vec glyph

updateBB :: Bool -> V.Vector StandardGlyph -> StandardGlyph -> StandardGlyph
updateBB scale vec glyph =
  glyph {glyphXmin = xMin_,
         glyphYmin = yMin_,
         glyphXmax = xMax_,
         glyphYmax = yMax_}
  where
    (xMin_, yMin_, xMax_, yMax_) = glyphBB scale vec glyph


updateHhea :: V.Vector StandardGlyph -> HheaTable -> HheaTable
updateHhea v h = V.foldl' updateHhea1
                 (h {advanceWidthMax     = minBound,
                     minLeftSideBearing  = maxBound,
                     minRightSideBearing = maxBound,
                     xMaxExtent          = minBound})
                 v
  

updateMinMax  :: (FWord, FWord, FWord, FWord, Double)
              -> StandardGlyph -> (FWord, FWord, FWord, FWord, Double)
updateMinMax (xmin, ymin, xmax, ymax, totWidth) g =
  (min xmin (glyphXmin g),
   min ymin (glyphYmin g),
   max xmax (glyphXmax g),
   max ymax (glyphYmax g),
   totWidth + realToFrac (glyphXmax g - glyphXmin g))

getMinMax :: V.Vector StandardGlyph -> (FWord, FWord, FWord, FWord, FWord)
getMinMax vec =
  over _5 (round . (/ realToFrac (V.length vec))) $
  V.foldl' updateMinMax (maxBound, maxBound, minBound, minBound, 0) vec
  
updateHhea1 :: HheaTable -> StandardGlyph -> 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 StandardGlyph -> 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 StandardGlyph -> MaxpTable -> StandardGlyph -> 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 StandardGlyph -> StandardGlyph -> 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 StandardGlyph -> StandardGlyph -> 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 Int -> 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,
          fromMaybe False (scaledComponentOffset c),
          not $ fromMaybe True (scaledComponentOffset c)]

getComponent :: Get (GlyphComponent Int, 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)
      (if | (byteAt flag 11) -> Just True
          | (byteAt flag 12) -> Just False
          | otherwise -> Nothing),
    byteAt flag 5) -- more components