packages feed

webify-0.1.6.0: src/WOFF.hs

module WOFF(
  generate
) where

import qualified Codec.Compression.Hopfli as Hopfli
import qualified Codec.Compression.Zlib   as Zlib
import           Control.Monad
import           Data.Binary.Put
import qualified Data.ByteString          as B
import           Data.ByteString.Char8    (pack)
import           Data.Function
import           Data.List
import qualified Data.Map                 as Map
import           Data.Word
import           Font
import           Utils


type UInt32 = Word32
type UInt16 = Word16

putUInt32 :: UInt32 -> Put
putUInt32 = putWord32be

putUInt16 :: UInt16 -> Put
putUInt16 = putWord16be

putTableDirectory ::
  ((Int, Int, Int, B.ByteString), TableDirectory) -> PutM ()
putTableDirectory ((startOffset, size, _padding, _compressedData), directory) = do
  putByteString $ pack $ tDTag directory
  putUInt32 $ fromIntegral startOffset
  putUInt32 $ fromIntegral size
  putUInt32 $ fromIntegral $ tDLength directory
  putUInt32 $ tDCheckSum directory


type Compressor = B.ByteString -> B.ByteString

getCompressor :: Bool -> Compressor
getCompressor False = toStrict . Zlib.compress . toLazy
getCompressor True = Hopfli.compress

calculateOffset ::
  Compressor ->
  [(Int, Int, Int, B.ByteString)] -> B.ByteString
  -> [(Int, Int, Int, B.ByteString)]
calculateOffset compressor offsets raw =
  (start, size, padding, compressedData) : offsets
  where originalSize = B.length raw
        compressed = compressor raw
        compressedSize = B.length compressed
        compressedData | originalSize <= compressedSize = raw
                       | otherwise = compressed
        size = min originalSize compressedSize
        (lastStart, lastSize, lastPadding, _) = head offsets
        start = lastStart + lastSize + lastPadding
        padding | (size `mod` 4) == 0 = 0
                | otherwise = 4 - (size `mod` 4)

putFontData :: (Int, Int, Int, B.ByteString) -> PutM ()
putFontData (_, _, padding, compressedData) = do
  putByteString compressedData
  replicateM_ padding (putWord8 0x0)

payload :: Font f => f -> B.ByteString -> Bool -> Put
payload font rawFont enableZopfli = do
  putUInt16 $ numTables font
  putUInt16 0 -- reserved
  putUInt32 $ fromIntegral $ B.length rawFont
  putUInt16 1 -- woff version major
  putUInt16 0 -- woff version minor
  putUInt32 0 -- meta offset
  putUInt32 0 -- meta length
  putUInt32 0 -- meta length uncompressed
  putUInt32 0 -- private block offset
  putUInt32 0 -- private block length
  let tds = Map.elems $ tableDirectories font
      sortByOffset = sortBy (compare `on` tDOffset)
      sortedByTag = sortBy (compare `on` tDTag . snd)
      initialOffset = [(fromIntegral (44 + (20 * numTables font)), 0, 0, pack "")]
      offsets = drop 1 $ reverse $
                foldl (calculateOffset $ getCompressor enableZopfli) initialOffset (map tDRawData $ sortByOffset tds)
  mapM_ putTableDirectory $ sortedByTag $ zip offsets (sortByOffset tds)
  mapM_ putFontData offsets


combine :: Font f => f -> B.ByteString -> PutM ()
combine font rest = do
  putUInt32 0x774F4646
  putUInt32 $ version font
  putUInt32 $ fromIntegral $ B.length rest + 12
  putByteString rest

generate :: Font f => f -> B.ByteString -> Bool -> B.ByteString
generate font rawFont enableZopfli =
  let rest = toStrict $ runPut (payload font rawFont enableZopfli)
  in toStrict $ runPut $ combine font rest