{-# 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