webify-0.1.7.0: src/Font.hs
{-# LANGUAGE RecordWildCards #-}
module Font(
Font(..)
-- parser
, Byte
, Font.Char
, UShort
, Short
, FWord
, UFWord
, ULong
, Fixed
, getFixed
, getULong
, getUShort
, getShort
, getByte
, Font.getChar
, getFWord
, getUFWord
, getUFixed
-- common font structure
, TableDirectory(..)
, Name(..)
, NameRecord(..)
, HMetric(..)
, Hmtx(..)
, OS2(..)
, Head(..)
, parseTableDirectory
, parseTableDirectories
, parseTable
, parseOS2
, parseHead
, parseName
) where
import Control.Monad
import Data.Binary.Strict.Get
import qualified Data.ByteString as B
import Data.ByteString.Char8 (unpack)
import Data.Int
import Data.Map hiding (map)
import qualified Data.Text as T
import Data.Text.Encoding
import Data.Text.Encoding.Error
import qualified Data.Vector as V
import Data.Word
import Utils
-- font data types
type Byte = Word8
type Char = Int8
type UShort = Word16
type Short = Int16
type FWord = Int16
type UFWord = Word16
type ULong = Word32
-- type Long = Int32
type Fixed = Word32
getFixed :: Get Fixed
getFixed = liftM fromIntegral getWord32be
getULong :: Get ULong
getULong = getWord32be
getUShort :: Get UShort
getUShort = getWord16be
getShort :: Get Short
getShort = liftM fromIntegral getWord16be
getByte :: Get Byte
getByte = getWord8
getChar :: Get Font.Char
getChar = liftM fromIntegral getWord8
getFWord :: Get FWord
getFWord = getShort
getUFWord :: Get UFWord
getUFWord = getUShort
-- format 2.14
getUFixed :: Get Double
getUFixed = liftM ((/ 0x4000) . fromIntegral) getShort
--- common parsing utils
data TableDirectory = TableDirectory { tDTag :: String
, tDCheckSum :: ULong
, tDOffset :: ULong
, tDLength :: ULong
, tDRawData :: B.ByteString
} deriving (Show)
data NameRecord = NameRecord { platformId :: UShort
, encodingId :: UShort
, languageId :: UShort
, nameId :: UShort
, strLength :: UShort
, strOffset :: UShort
, str :: T.Text
} deriving (Show)
data Name = Name { formatSelector :: UShort
, numberOfNameRecords :: UShort
, storageOffset :: UShort
, nameRecords :: [NameRecord]
} deriving (Show)
data HMetric = HMetric { advanceWidth :: UFWord
, lsb :: FWord
} deriving (Show)
data Hmtx = Hmtx { hMetrics :: V.Vector HMetric
, leftSideBearings :: [FWord]
} deriving (Show)
data OS2 = OS2 { os2Version :: UShort
, xAvgCharWidth :: Short
, usWeightClass :: UShort
, usWidthClass :: UShort
, fsType :: UShort
, ySubscriptXSize :: Short
, ySubscriptYSize :: Short
, ySubscriptXOffset :: Short
, ySubscriptYOffset :: Short
, ySuperscriptXSize :: Short
, ySuperscriptYSize :: Short
, ySuperscriptXOffset :: Short
, ySuperscriptYOffset :: Short
, yStrikeoutSize :: Short
, yStrikeoutPosition :: Short
, sFamilyClass :: Short
, panose :: [Byte]
, ulUnicodeRange1 :: ULong
, ulUnicodeRange2 :: ULong
, ulUnicodeRange3 :: ULong
, ulUnicodeRange4 :: ULong
, aschVendID :: [Byte]
, fsSelection :: UShort
, usFirstCharIndex :: UShort
, usLastCharIndex :: UShort
, sTypoAscender :: UShort
, sTypoDescender :: UShort
, sTypoLineGap :: UShort
, usWinAscent :: UShort
, usWinDescent :: UShort
, ulCodePageRange1 :: ULong
, ulCodePageRange2 :: ULong
} deriving (Show)
parseTableDirectory :: B.ByteString -> Get TableDirectory
parseTableDirectory font = do
tDTag <- liftM unpack $ getByteString 4
tDCheckSum <- getULong
tDOffset <- getULong
tDLength <- getULong
let tDRawData = substr (fromIntegral tDOffset) (fromIntegral tDLength) font
return TableDirectory{..}
parseTableDirectories :: B.ByteString -> Int -> Get (Map String TableDirectory)
parseTableDirectories font n = do
list <- replicateM n $ parseTableDirectory font
return $ fromList $ map (\x -> (tDTag x, x)) list
parseTable :: String -> Get a -> Map String TableDirectory -> B.ByteString -> a
parseTable name' m tds font =
fromRight $ runGet (do
skip $ fromIntegral . tDOffset $ tds ! name'
m) font
parseOS2 :: Map String TableDirectory -> B.ByteString -> OS2
parseOS2 = parseTable "OS/2" (do
os2Version <- getUShort
unless (os2Version `elem` [1..4]) (error $ "unhandled os2 version " ++ show os2Version)
xAvgCharWidth <- getShort
usWeightClass <- getUShort
usWidthClass <- getUShort
fsType <- getUShort
ySubscriptXSize <- getShort
ySubscriptYSize <- getShort
ySubscriptXOffset <- getShort
ySubscriptYOffset <- getShort
ySuperscriptXSize <- getShort
ySuperscriptYSize <- getShort
ySuperscriptXOffset <- getShort
ySuperscriptYOffset <- getShort
yStrikeoutSize <- getShort
yStrikeoutPosition <- getShort
sFamilyClass <- getShort
panose <- replicateM 10 getByte
ulUnicodeRange1 <- getULong
ulUnicodeRange2 <- getULong
ulUnicodeRange3 <- getULong
ulUnicodeRange4 <- getULong
aschVendID <- replicateM 4 getByte
fsSelection <- getUShort
usFirstCharIndex <- getUShort
usLastCharIndex <- getUShort
sTypoAscender <- getUShort
sTypoDescender <- getUShort
sTypoLineGap <- getUShort
usWinAscent <- getUShort
usWinDescent <- getUShort
ulCodePageRange1 <- getULong
ulCodePageRange2 <- getULong
return OS2 {..})
data Head = Head { headVersion :: Fixed
, fontRevision :: Fixed
, checkSumAdjusment :: ULong
, magicNumber :: ULong
, headFlags :: UShort
, unitsPerEm :: UShort
, created :: B.ByteString
, modified :: B.ByteString
, xMin :: FWord
, yMin :: FWord
, xMax :: FWord
, yMax :: FWord
, macStyle :: UShort
, lowestRecPPEM :: UShort
, fontDirectionHint :: Short
, indexToLocFormat :: Short
, glyphDataFormat :: Short
} deriving (Show)
parseHead :: Map String TableDirectory -> B.ByteString -> Head
parseHead = parseTable "head" (do
headVersion <- getFixed
fontRevision <- getFixed
checkSumAdjusment <- getULong
magicNumber <- getULong
headFlags <- getUShort
unitsPerEm <- getUShort
created <- getByteString 8
modified <- getByteString 8
xMin <- getFWord
yMin <- getFWord
xMax <- getFWord
yMax <- getFWord
macStyle <- getUShort
lowestRecPPEM <- getUShort
fontDirectionHint <- getShort
indexToLocFormat <- getShort
glyphDataFormat <- getShort
return Head{..})
parseNameRecord :: B.ByteString -> Int -> Get NameRecord
parseNameRecord font storageOffset = do
platformId <- getUShort
encodingId <- getUShort
languageId <- getUShort
nameId <- getUShort
strLength <- getUShort
strOffset <- getUShort
let str = decoder platformId encodingId $ substr
(fromIntegral ((fromIntegral storageOffset :: Int) + fromIntegral strOffset)) (fromIntegral strLength) font
return NameRecord{..}
where
decoder 3 _ = decodeUtf16BE
decoder 2 _ = decodeUtf16BE
decoder 1 _ = decodeUtf8With ignore
decoder 0 _ = decodeUtf16BE
decoder _ _ = decodeUtf16BE
parseName ::Map String TableDirectory -> B.ByteString -> Name
parseName tds font =
fromRight $ runGet (do
let tableStart = fromIntegral $ tDOffset $ tds ! "name"
skip tableStart
formatSelector <- getUShort
numberOfNameRecords <- getUShort
storageOffset <- getUShort
nameRecords <- replicateM (fromIntegral numberOfNameRecords) $ parseNameRecord font (tableStart + fromIntegral storageOffset)
return Name{..}) font
class Font a where
version :: a -> Fixed
numTables :: a -> UShort
tableDirectories :: a -> Map String TableDirectory
-- os2
os2panose :: a -> [Byte]
os2fsSelection :: a -> UShort
os2usWeightClass :: a -> UShort
os2ulUnicodeRange1 :: a -> ULong
os2ulUnicodeRange2 :: a -> ULong
os2ulUnicodeRange3 :: a -> ULong
os2ulUnicodeRange4 :: a -> ULong
os2ulCodePageRange1 :: a -> ULong
os2ulCodePageRange2 :: a -> ULong
-- head
headCheckSumAdjusment :: a -> ULong
-- name
name :: a -> Name