{- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. -} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE ExplicitForAll #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE TypeApplications #-} -- | Font parser. module Graphics.Fountainhead.Parser ( Parser , cmapTableP , cvTableP , f2Dot14P , fixedP , fontDirectoryP , fpgmTableP , glyfTableP , hdmxTableP , headTableP , hheaTableP , hmtxTableP , longDateTimeP , longLocaTableP , maxpTableP , nameTableP , os2TableP , panoseP , parseTable , pascalStringP , postTableP , prepTableP , shortLocaTableP , word24P ) where import Control.Applicative (Alternative(..)) import Control.Monad (foldM) import Data.Bits (Bits(..)) import Data.ByteString (ByteString) import qualified Data.ByteString as ByteString import qualified Data.ByteString.Builder as ByteString.Builder import qualified Data.ByteString.Lazy as ByteString.Lazy import Data.Foldable (Foldable(..)) import Data.Int (Int8, Int16) import Data.IntMap (IntMap) import qualified Data.IntMap as IntMap import Data.Functor (($>)) import Data.List (nub, sort, sortOn, nubBy, sortBy) import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.List.NonEmpty as NonEmpty import Data.Maybe (fromMaybe) import Data.Time ( LocalTime(..) , TimeOfDay(..) , addDays , secondsToDiffTime , timeToTimeOfDay ) import Data.Time.Calendar.OrdinalDate (fromOrdinalDate) import Data.Vector (Vector) import qualified Data.Vector as Vector import Data.Void (Void) import Data.Word (Word8, Word16, Word32) import GHC.Records (HasField(..)) import Graphics.Fountainhead.TrueType ( BArmStyle(..) , BContrast(..) , BFamilyType(..) , BMidline(..) , BLetterform(..) , BProportion(..) , BSerifStyle(..) , BStrokeVariation(..) , BWeight(..) , BXHeight(..) , CVTable(..) , CmapSubtable(..) , CmapTable(..) , CmapEncoding(..) , CmapFormat0Table(..) , CmapFormat2Subheader(..) , CmapFormat2Table(..) , CmapFormat4Table(..) , CmapFormat6Table(..) , CmapGroup(..) , CmapFormat8Table(..) , CmapFormat10Table(..) , CmapFormat12Table(..) , CmapFormat13Table , CmapFormat14Table(..) , ComponentGlyphFlags(..) , ComponentGlyphPartDescription(..) , CompoundGlyphDefinition(..) , FpgmTable(..) , FontDirectionHint(..) , FontDirectory(..) , FontStyle(..) , GlyfTable(..) , GlyphArgument(..) , GlyphCoordinate(..) , GlyphDefinition(..) , GlyphDescription(..) , GlyphTransformationOption(..) , HdmxTable(..) , DeviceRecord(..) , HeadTable(..) , HheaTable(..) , HmtxTable(..) , LocaTable(..) , LongHorMetric(..) , MaxpTable(..) , NameRecord(..) , NameTable(..) , OffsetSubtable(..) , OutlineFlag(..) , OpenMaxpTable(..) , Os2BaseFields(..) , Os2MicrosoftFields(..) , Os2Version1Fields(..) , Os2Version4Fields(..) , Os2Version5Fields(..) , Os2Table(..) , Panose(..) , PostFormat2Table(..) , PostHeader(..) , PostSubtable(..) , PostTable(..) , PrepTable(..) , SimpleGlyphDefinition(..) , TableDirectory(..) , TrueMaxpTable(..) , UVSOffset(..) , UVSMapping(..) , UnicodeValueRange(..) , VariationSelectorMap , unLocaTable ) import Graphics.Fountainhead.Type (F2Dot14(..), Fixed32(..)) import Text.Megaparsec (()) import qualified Text.Megaparsec as Megaparsec import qualified Text.Megaparsec.Byte.Binary as Megaparsec.Binary type Parser = Megaparsec.Parsec Void ByteString -- * Font directory offsetSubtableP :: Parser OffsetSubtable offsetSubtableP = OffsetSubtable <$> Megaparsec.Binary.word32be <*> (fromIntegral <$> Megaparsec.Binary.word16be) <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be tagP :: Parser ByteString tagP = ByteString.Lazy.toStrict . ByteString.Builder.toLazyByteString . ByteString.Builder.word32BE <$> Megaparsec.Binary.word32be tableDirectoryP :: Parser TableDirectory tableDirectoryP = TableDirectory <$> tagP <*> Megaparsec.Binary.word32be <*> (fromIntegral <$> Megaparsec.Binary.word32be) <*> (fromIntegral <$> Megaparsec.Binary.word32be) fontDirectoryP :: Parser FontDirectory fontDirectoryP = do offsetSubtable'@OffsetSubtable{ numTables } <- offsetSubtableP tableDirectories <- Megaparsec.count numTables tableDirectoryP pure $ FontDirectory { offsetSubtable = offsetSubtable' , tableDirectory = tableDirectories } -- * Name table nameTableP :: Parser NameTable nameTableP = do format' <- Megaparsec.Binary.word16be count' <- fromIntegral <$> Megaparsec.Binary.word16be stringOffset' <- fromIntegral <$> Megaparsec.Binary.word16be nameRecord' <- Megaparsec.count count' nameRecordP -- 12 is the size of a single record, 6 is the header size. let padding = stringOffset' - count' * 12 - 6 Megaparsec.skipCount padding Megaparsec.Binary.word8 variable' <- Megaparsec.takeRest pure $ NameTable { format = format' , nameRecord = nameRecord' , variable = parseVariable variable' <$> nameRecord' } where parseVariable variable' NameRecord{ offset, length } = ByteString.take length $ ByteString.drop offset variable' nameRecordP :: Parser NameRecord nameRecordP = NameRecord <$> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> (fromIntegral <$> Megaparsec.Binary.word16be) <*> (fromIntegral <$> Megaparsec.Binary.word16be) -- * 'cvt ' table cvTableP :: Parser CVTable cvTableP = CVTable <$> Megaparsec.many Megaparsec.Binary.int16be <* Megaparsec.eof -- * Maximum profile table trueMaxpTableP :: Parser TrueMaxpTable trueMaxpTableP = Megaparsec.chunk (ByteString.pack [0, 1, 0, 0]) *> subparser where subparser = TrueMaxpTable (Fixed32 0x00010000) <$> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be openMaxpTableP :: Parser OpenMaxpTable openMaxpTableP = Megaparsec.chunk (ByteString.pack [0, 0, 0x50, 0]) *> subparser where subparser = OpenMaxpTable (Fixed32 0x00005000) <$> Megaparsec.Binary.word16be <* Megaparsec.eof maxpTableP :: Parser MaxpTable maxpTableP = TrueMaxp <$> trueMaxpTableP <|> OpenMaxp <$> openMaxpTableP -- * Horizontal header table hheaTableP :: Parser HheaTable hheaTableP = HheaTable <$> fixedP <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <* Megaparsec.Binary.int16be <* Megaparsec.Binary.int16be <* Megaparsec.Binary.int16be <* Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.word16be <* Megaparsec.eof -- * Font header table headTableP :: Parser HeadTable headTableP = HeadTable <$> fixedP <*> fixedP <*> Megaparsec.Binary.word32be <*> Megaparsec.Binary.word32be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> longDateTimeP <*> longDateTimeP <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> fontStyleP <*> Megaparsec.Binary.word16be <*> fontDirectionHintP <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <* Megaparsec.eof fontStyleP :: Parser FontStyle fontStyleP = go <$> Megaparsec.Binary.word16be where go fontStyle' = FontStyle { bold = testBit fontStyle' 0 , italic = testBit fontStyle' 1 , underline = testBit fontStyle' 2 , outline = testBit fontStyle' 3 , shadow = testBit fontStyle' 4 , condensed = testBit fontStyle' 5 , extended = testBit fontStyle' 6 } fontDirectionHintP :: Parser FontDirectionHint fontDirectionHintP = (Megaparsec.chunk (ByteString.pack [0, 0]) $> MixedDirectionalGlyphs) <|> (Megaparsec.chunk (ByteString.pack [0, 1]) $> StronglyLeftToRightGlyphs) <|> (Megaparsec.chunk (ByteString.pack [0, 2]) $> LeftToRightGlyphsWithNeutrals) <|> (Megaparsec.chunk (ByteString.pack [0xff, 0xff]) $> StronglyRightToLeftGlyphs) <|> (Megaparsec.chunk (ByteString.pack [0xff, 0xfe]) $> RightToLeftGlyphsWithNeutrals) -- * Glyph data location table longLocaTableP :: Parser LocaTable longLocaTableP = LongLocaTable <$> vectorP Megaparsec.Binary.word32be "loca table, long version" shortLocaTableP :: Parser LocaTable shortLocaTableP = ShortLocaTable <$> vectorP Megaparsec.Binary.word16be "loca table, short version" -- * Horizontal metrics table longHorMetricP :: Parser LongHorMetric longHorMetricP = LongHorMetric <$> Megaparsec.Binary.word16be <*> Megaparsec.Binary.int16be hmtxTableP :: Int -> Parser HmtxTable hmtxTableP numOfLongHorMetrics = HmtxTable <$> countP numOfLongHorMetrics longHorMetricP <*> Megaparsec.many Megaparsec.Binary.int16be -- * Glyph name and PostScript font table postTableP :: Parser PostTable postTableP = do header'@PostHeader{ format } <- postHeaderP subtable' <- case format of Fixed32 0x00010000 -> pure None Fixed32 0x00020000 -> PostFormat2 <$> postFormat2TableP Fixed32 0x00025000 -> PostFormat25 <$> postFormat25TableP Fixed32 0x00030000 -> pure None Fixed32 0x00040000 -> PostFormat4 <$> postFormat4TableP _ -> fail $ "Unsupported post table format: " <> show format Megaparsec.eof pure $ PostTable { postHeader = header' , postSubtable = subtable' } postFormat2TableP :: Parser PostFormat2Table postFormat2TableP = do numberOfGlyphs <- fromIntegral <$> Megaparsec.Binary.word16be glyphNameIndex' <- Megaparsec.count numberOfGlyphs Megaparsec.Binary.word16be rest <- Megaparsec.many pascalStringP pure $ PostFormat2Table { glyphNameIndex = Vector.fromList glyphNameIndex' , names = Vector.fromList rest } postFormat25TableP :: Parser (Vector Int8) postFormat25TableP = Megaparsec.Binary.word16be >>= fmap Vector.fromList . flip Megaparsec.count Megaparsec.Binary.int8 . fromIntegral postFormat4TableP :: Parser (Vector Word16) postFormat4TableP = Vector.fromList <$> Megaparsec.many Megaparsec.Binary.word16be postHeaderP :: Parser PostHeader postHeaderP = PostHeader <$> fixedP <*> fixedP <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.word32be <*> Megaparsec.Binary.word32be <*> Megaparsec.Binary.word32be <*> Megaparsec.Binary.word32be <*> Megaparsec.Binary.word32be -- * Font program table fpgmTableP :: Parser FpgmTable fpgmTableP = FpgmTable <$> vectorP Megaparsec.Binary.word8 -- * Prep table prepTableP :: Parser PrepTable prepTableP = PrepTable <$> vectorP Megaparsec.Binary.word8 -- * Horizontal device metrics table deviceRecordP :: Int -> Parser DeviceRecord deviceRecordP size = do pixelSize' <- Megaparsec.Binary.word8 maximumWidth' <- Megaparsec.Binary.word8 widths' <- vectorNP size Megaparsec.Binary.word8 let paddingLength = 4 - ((Vector.length widths' + 2) `mod` 4) Megaparsec.skipCount paddingLength $ Megaparsec.chunk $ ByteString.pack [0] pure $ DeviceRecord { pixelSize = pixelSize' , maximumWidth = maximumWidth' , widths = widths' } hdmxTableP :: Parser HdmxTable hdmxTableP = do Megaparsec.chunk $ ByteString.pack [0, 0] numberOfDeviceRecords <- fromIntegral <$> Megaparsec.Binary.int16be sizeOfDeviceRecord <- fromIntegral <$> Megaparsec.Binary.int32be records' <- Megaparsec.count numberOfDeviceRecords $ deviceRecordP sizeOfDeviceRecord Megaparsec.eof >> pure (HdmxTable 0 records') -- * Glyph outline table glyphDescriptionP :: Parser GlyphDescription glyphDescriptionP = do numberOfContours' <- fromIntegral <$> Megaparsec.Binary.int16be "numberOfContours" xMin' <- Megaparsec.Binary.int16be "xMin" yMin' <- Megaparsec.Binary.int16be "yMin" xMax' <- Megaparsec.Binary.int16be "xMax" yMax' <- Megaparsec.Binary.int16be "yMax" glyphDefinition <- if numberOfContours' >= 0 then SimpleGlyph <$> simpleGlyphDefinitionP numberOfContours' else CompoundGlyph <$> compoundGlyphDefinitionP pure $ GlyphDescription { numberOfContours = numberOfContours' , xMin = xMin' , yMin = yMin' , xMax = xMax' , yMax = yMax' , definition = glyphDefinition } glyphInstructionsP :: Parser (Vector Word8) glyphInstructionsP = Megaparsec.Binary.word16be >>= flip vectorNP (Megaparsec.Binary.word8 "compound glyph instruction") . fromIntegral compoundGlyphDefinitionP :: Parser CompoundGlyphDefinition compoundGlyphDefinitionP = do components' <- componentGlyphPartDescriptionP mempty let instructions' = if Vector.any (weHaveInstructions . getField @"flags") components' then glyphInstructionsP else pure mempty CompoundGlyphDefinition components' <$> instructions' componentGlyphPartDescriptionP ::Vector ComponentGlyphPartDescription -> Parser (Vector ComponentGlyphPartDescription) componentGlyphPartDescriptionP accumulator = do flags' <- Megaparsec.Binary.word16be "flags" glyphIndex' <- Megaparsec.Binary.word16be "glyphIndex" let arg1And2AreWords = testBit flags' 0 argsAreXyValues = testBit flags' 1 weHaveAScale = testBit flags' 3 weHaveAnXAndYScale = testBit flags' 6 weHaveATwoByTwo = testBit flags' 7 argument1 <- glyphArgumentP arg1And2AreWords argsAreXyValues "argument1" argument2 <- glyphArgumentP arg1And2AreWords argsAreXyValues "argument2" transformationOption' <- transformationOptionP weHaveAScale weHaveAnXAndYScale weHaveATwoByTwo "transformation option" let updated = Vector.snoc accumulator $ ComponentGlyphPartDescription { flags = ComponentGlyphFlags { roundXyToGrid = testBit flags' 2 , weHaveInstructions = testBit flags' 8 , useMyMetrics = testBit flags' 9 , overlapCompound = testBit flags' 10 } , glyphIndex = glyphIndex' , argument1 = argument1 , argument2 = argument2 , transformationOption = transformationOption' } -- MORE_COMPONENTS. if testBit flags' 5 then componentGlyphPartDescriptionP updated else pure updated transformationOptionP :: Bool -> Bool -> Bool -> Parser GlyphTransformationOption transformationOptionP True _ _ = GlyphScale <$> f2Dot14P "scale" transformationOptionP _ True _ = GlyphXyScale <$> f2Dot14P <*> f2Dot14P "xy-scale" transformationOptionP _ _ True = Glyph2By2Scale <$> f2Dot14P <*> f2Dot14P <*> f2Dot14P <*> f2Dot14P "2 by 2 transformation" transformationOptionP _ _ _ = pure GlyphNoScale glyphArgumentP :: Bool -> Bool -> Parser GlyphArgument glyphArgumentP True True = GlyphInt16Argument <$> Megaparsec.Binary.int16be "int16 argument" glyphArgumentP True False = GlyphWord16Argument <$> Megaparsec.Binary.word16be "uint16 argument" glyphArgumentP False True = GlyphInt8Argument <$> Megaparsec.Binary.int8 "int8 argument" glyphArgumentP False False = GlyphWord8Argument <$> Megaparsec.Binary.word8 "uint8 argument" simpleGlyphDefinitionP :: Int -> Parser SimpleGlyphDefinition simpleGlyphDefinitionP numberOfContours' = do endPtsOfContours' <- vectorNP numberOfContours' Megaparsec.Binary.word16be "endPtsOfContours" let numberOfPoints = if Vector.null endPtsOfContours' then 0 else fromIntegral $ Vector.last endPtsOfContours' instructionLength <- fromIntegral <$> Megaparsec.Binary.word16be "instructionLength" instructions' <- vectorNP instructionLength (Megaparsec.Binary.word8 "simple glyph instruction") flags' <- flagsP numberOfPoints mempty "flags" xs <- Vector.foldM (coordinateP xFlagPair) mempty flags' ys <- Vector.foldM (coordinateP yFlagPair) mempty flags' pure $ SimpleGlyphDefinition { endPtsOfContours = endPtsOfContours' , instructions = instructions' , coordinates = mkCoordinate <$> Vector.zip3 xs ys flags' } where mkCoordinate (x, y, OutlineFlag{ onCurve }) = GlyphCoordinate x y onCurve xFlagPair :: OutlineFlag -> (Bool, Bool) xFlagPair OutlineFlag{ xShortVector, thisXIsSame } = (xShortVector, thisXIsSame) yFlagPair :: OutlineFlag -> (Bool, Bool) yFlagPair OutlineFlag{ yShortVector, thisYIsSame } = (yShortVector, thisYIsSame) coordinateP :: (OutlineFlag -> (Bool, Bool)) -> Vector Int16 -> OutlineFlag -> Parser (Vector Int16) coordinateP get accumulator first = case get first of (True, True) -> Vector.snoc accumulator . fromIntegral <$> Megaparsec.Binary.word8 "1 byte long positive coordinate" (True, False) -> Vector.snoc accumulator . negate . fromIntegral <$> Megaparsec.Binary.word8 "1 byte long negative coordinate" (False, False) -> Vector.snoc accumulator <$> Megaparsec.Binary.int16be "2 bytes long coordinate" (False, True) -> pure $ Vector.snoc accumulator 0 flagsP :: Int -> Vector OutlineFlag -> Parser (Vector OutlineFlag) flagsP remaining accumulator | remaining < 0 = pure accumulator | otherwise = do flag <- Megaparsec.Binary.word8 "outline flags" let flag' = OutlineFlag { onCurve = testBit flag 0 , xShortVector = testBit flag 1 , yShortVector = testBit flag 2 , thisXIsSame = testBit flag 4 , thisYIsSame = testBit flag 5 } repeat = testBit flag 3 repeatN <- if repeat then (1 +) . fromIntegral <$> Megaparsec.Binary.word8 "flag repeat count" else pure 1 flagsP (remaining - repeatN) $ accumulator <> Vector.replicate repeatN flag' glyfTableP :: LocaTable -> Parser GlyfTable glyfTableP locaTable | locaTable' <- unLocaTable locaTable , not $ Vector.null locaTable' = let locaInit = Vector.init locaTable' offsets = traverse go $ nubBy duplicate $ sortOn fst $ filter filterNullLength $ Vector.toList $ Vector.zip locaInit $ Vector.tail locaTable' in GlyfTable . Vector.generate (Vector.length locaInit) . generateTable locaInit . IntMap.fromList <$> offsets | otherwise = pure $ GlyfTable mempty where filterNullLength (x, y) = x /= y duplicate x y = fst x == fst y generateTable :: Vector Word32 -> IntMap GlyphDescription -> Int -> GlyphDescription generateTable locaInit offsetMap index = offsetMap IntMap.! fromIntegral (locaInit Vector.! index) go (locaOffset, nextOffset) = do startOffset <- Megaparsec.getOffset result <- glyphDescriptionP endOffset <- Megaparsec.getOffset flip Megaparsec.skipCount Megaparsec.Binary.word8 $ fromIntegral nextOffset - fromIntegral locaOffset - endOffset + startOffset pure (fromIntegral locaOffset, result) -- * Character to glyph mapping table cmapTableP :: Parser CmapTable cmapTableP = do initialOffset <- Megaparsec.getOffset version' <- Megaparsec.Binary.word16be numberSubtables <- fromIntegral <$> Megaparsec.Binary.word16be encodings' <- sortOn (getField @"offset") <$> Megaparsec.count numberSubtables cmapHeaderP parsedSubtables <- Megaparsec.some (subtableAtOffset initialOffset) Megaparsec.eof pure $ CmapTable { version = version' , encodings = encodings' , subtables = IntMap.fromList parsedSubtables } where subtableAtOffset initialOffset = do currentOffset <- flip (-) initialOffset <$> Megaparsec.getOffset parsedSubtable <- cmapFormatTableP pure (currentOffset, parsedSubtable) cmapHeaderP :: Parser CmapEncoding cmapHeaderP = CmapEncoding <$> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word32be cmapFormatTableP :: Parser CmapSubtable cmapFormatTableP = do format' <- Megaparsec.Binary.word16be case format' of 0 -> CmapFormat0 <$> cmapFormat0TableP 2 -> CmapFormat2 <$> cmapFormat2TableP 4 -> CmapFormat4 <$> cmapFormat4TableP 6 -> CmapFormat6 <$> cmapFormat6TableP 8 -> CmapFormat8 <$> cmapFormat8TableP 10 -> CmapFormat10 <$> cmapFormat10TableP 12 -> CmapFormat12 <$> cmapFormat12TableP 13 -> CmapFormat13 <$> cmapFormat13TableP 14 -> CmapFormat14 <$> cmapFormat14TableP _ -> fail $ "Unsupported format " <> show format' <> "." cmapFormat14TableP :: Parser CmapFormat14Table cmapFormat14TableP = do initialOffset <- (+ (-2)) <$> Megaparsec.getOffset Megaparsec.Binary.word32be -- Length. numVarSelectorRecords <- fromIntegral <$> Megaparsec.Binary.word32be variationSelectorRecords' <- sortBy sortOffset . fold <$> Megaparsec.count numVarSelectorRecords variationSelectorRecordP let parseByOffset' = parseByOffset initialOffset CmapFormat14Table <$> foldM parseByOffset' IntMap.empty variationSelectorRecords' where parseByOffset :: Int -> VariationSelectorMap -> UVSOffset Word32 Word32 -> Parser VariationSelectorMap parseByOffset _ accumulator uvsOffset' | uvsOffset uvsOffset' == 0 = pure accumulator parseByOffset tableOffset accumulator (DefaultUVSOffset varSelector' relativeOffset) -- If the records at this offset were already parsed, look at any parsed -- record and duplicate it updating the varSelector. The same logic -- applies for the next pattern match, but for non-default UVS. | Just member@(DefaultUVSOffset _ record :| _) <- IntMap.lookup (fromIntegral relativeOffset) accumulator = let newRecord = DefaultUVSOffset varSelector' record NonEmpty.<| member relativeOffset' = fromIntegral relativeOffset in pure $ IntMap.insert relativeOffset' newRecord accumulator | otherwise = do currentOffset <- Megaparsec.getOffset let emptyBytes = tableOffset + fromIntegral relativeOffset - currentOffset relativeOffset' = fromIntegral relativeOffset Megaparsec.takeP Nothing emptyBytes entryCount <- fromIntegral <$> Megaparsec.Binary.word32be valueRanges <- vectorNP entryCount unicodeValueRangeP pure $ IntMap.insert relativeOffset' (DefaultUVSOffset varSelector' valueRanges :| []) accumulator parseByOffset tableOffset accumulator (NonDefaultUVSOffset varSelector' relativeOffset) | Just member@(NonDefaultUVSOffset _ record :| _) <- IntMap.lookup (fromIntegral relativeOffset) accumulator = let newRecord = NonDefaultUVSOffset varSelector' record NonEmpty.<| member relativeOffset' = fromIntegral relativeOffset in pure $ IntMap.insert relativeOffset' newRecord accumulator | otherwise = do currentOffset <- Megaparsec.getOffset let emptyBytes = tableOffset + fromIntegral relativeOffset - currentOffset Megaparsec.takeP Nothing emptyBytes entryCount <- fromIntegral <$> Megaparsec.Binary.word32be flip (IntMap.insert $ fromIntegral relativeOffset) accumulator . pure . NonDefaultUVSOffset varSelector' <$> vectorNP entryCount uvsMappingP sortOffset x y = compare (uvsOffset x) (uvsOffset y) uvsOffset :: forall a. UVSOffset a a -> a uvsOffset (DefaultUVSOffset _ x) = x uvsOffset (NonDefaultUVSOffset _ x) = x variationSelectorRecordP :: Parser [UVSOffset Word32 Word32] variationSelectorRecordP = do varSelector' <- word24P defaultUVSOffset' <- Megaparsec.Binary.word32be nonDefaultUVSOffset' <- Megaparsec.Binary.word32be pure [ DefaultUVSOffset varSelector' defaultUVSOffset' , NonDefaultUVSOffset varSelector' nonDefaultUVSOffset' ] uvsMappingP :: Parser UVSMapping uvsMappingP = UVSMapping <$> word24P <*> Megaparsec.Binary.word16be unicodeValueRangeP :: Parser UnicodeValueRange unicodeValueRangeP = UnicodeValueRange <$> word24P <*> Megaparsec.Binary.word8 cmapFormat13TableP :: Parser CmapFormat13Table cmapFormat13TableP = cmapFormat12TableP cmapFormat12TableP :: Parser CmapFormat12Table cmapFormat12TableP = do Megaparsec.takeP Nothing 6 -- Reserved and length. language' <- Megaparsec.Binary.word32be nGroups <- fromIntegral <$> Megaparsec.Binary.word32be groups' <- vectorNP nGroups cmapGroupP pure $ CmapFormat12Table { language = language' , groups = groups' } cmapFormat10TableP :: Parser CmapFormat10Table cmapFormat10TableP = do Megaparsec.takeP Nothing 2 -- Reserved. length' <- fromIntegral <$> Megaparsec.Binary.word32be language' <- Megaparsec.Binary.word32be startCharCode' <- Megaparsec.Binary.word32be numChars' <- Megaparsec.Binary.word32be let remainingLength = div (length' - 24) 2 glyphs' <- vectorNP remainingLength Megaparsec.Binary.word16be pure $ CmapFormat10Table { language = language' , startCharCode = startCharCode' , numChars = numChars' , glyphs = glyphs' } cmapFormat8TableP :: Parser CmapFormat8Table cmapFormat8TableP = do Megaparsec.takeP Nothing 6 -- Reserved and length. language' <- Megaparsec.Binary.word32be is32' <- Megaparsec.takeP Nothing 65536 nGroups <- fromIntegral <$> Megaparsec.Binary.word32be groups' <- vectorNP nGroups cmapGroupP pure $ CmapFormat8Table { language = language' , is32 = ByteString.unpack is32' , groups = groups' } cmapGroupP :: Parser CmapGroup cmapGroupP = CmapGroup <$> Megaparsec.Binary.word32be <*> Megaparsec.Binary.word32be <*> Megaparsec.Binary.word32be cmapFormat6TableP :: Parser CmapFormat6Table cmapFormat6TableP = do Megaparsec.Binary.word16be -- Length. language' <- Megaparsec.Binary.word16be firstCode' <- Megaparsec.Binary.word16be entryCount' <- fromIntegral <$> Megaparsec.Binary.word16be glyphIndexArray' <- vectorNP entryCount' Megaparsec.Binary.word16be pure $ CmapFormat6Table { language = language' , firstCode = firstCode' , glyphIndexArray = glyphIndexArray' } cmapFormat4TableP :: Parser CmapFormat4Table cmapFormat4TableP = do length' <- fromIntegral <$> Megaparsec.Binary.word16be language' <- Megaparsec.Binary.word16be segCount <- fromIntegral . (`div` 2) <$> Megaparsec.Binary.word16be searchRange' <- Megaparsec.Binary.word16be entrySelector' <- Megaparsec.Binary.word16be rangeShift' <- Megaparsec.Binary.word16be endCode' <- vectorNP segCount Megaparsec.Binary.word16be rangeShift' <- Megaparsec.Binary.word16be -- reservedPad 0. startCode' <- vectorNP segCount Megaparsec.Binary.word16be idDelta' <- vectorNP segCount Megaparsec.Binary.word16be idRangeOffset' <- vectorNP segCount Megaparsec.Binary.word16be let glyphIndexLength = div (length' - 16 - segCount * 8) 2 glyphIndexArray' <- vectorNP glyphIndexLength Megaparsec.Binary.word16be pure $ CmapFormat4Table { language = language' , searchRange = searchRange' , entrySelector = entrySelector' , rangeShift = rangeShift' , endCode = endCode' , startCode = startCode' , idDelta = idDelta' , idRangeOffset = idRangeOffset' , glyphIndexArray = glyphIndexArray' } cmapFormat2TableP :: Parser CmapFormat2Table cmapFormat2TableP = do length' <- fromIntegral <$> Megaparsec.Binary.word16be language' <- Megaparsec.Binary.word16be subHeaderKeys' <- vectorNP 256 Megaparsec.Binary.word16be let maxIndex = succ $ fromIntegral $ Vector.maximum $ fmap (`div` 8) subHeaderKeys' subHeaders' <- vectorNP maxIndex cmapFormat2SubheaderP let glyphIndexLength = div (length' - 518 - maxIndex * 8) 2 glyphIndexArray' <- vectorNP glyphIndexLength Megaparsec.Binary.word16be pure $ CmapFormat2Table { language = language' , subHeaderKeys = subHeaderKeys' , subHeaders = subHeaders' , glyphIndexArray = glyphIndexArray' } cmapFormat2SubheaderP :: Parser CmapFormat2Subheader cmapFormat2SubheaderP = CmapFormat2Subheader <$> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.word16be cmapFormat0TableP :: Parser CmapFormat0Table cmapFormat0TableP = CmapFormat0Table <$> Megaparsec.Binary.word16be <* Megaparsec.Binary.word16be <*> vectorNP 256 Megaparsec.Binary.word8 -- * Generic parsing utilities word24P :: Parser Word32 word24P = foldr unstep 0 . ByteString.unpack <$> Megaparsec.takeP (Just "word24") 3 where unstep b a = a `shiftL` 8 .|. fromIntegral b f2Dot14P :: Parser F2Dot14 f2Dot14P = F2Dot14 <$> Megaparsec.Binary.int16be vectorP :: forall a. Parser a -> Parser (Vector a) vectorP = fmap Vector.fromList . Megaparsec.many vectorNP :: forall a. Int -> Parser a -> Parser (Vector a) vectorNP size = fmap Vector.fromList . Megaparsec.count size pascalStringP :: Parser ByteString pascalStringP = Megaparsec.Binary.word8 >>= fmap ByteString.pack . flip Megaparsec.count Megaparsec.Binary.word8 . fromIntegral countP :: forall a. Int -> Parser a -> Parser (NonEmpty a) countP number parser = (:|) <$> parser <*> Megaparsec.count (number - 1) parser longDateTimeP :: Parser LocalTime longDateTimeP = go <$> Megaparsec.Binary.int64be where go totalSeconds = let (totalDays, secondsOfDay) = totalSeconds `divMod` (3600 * 24) epoch = fromOrdinalDate 1904 1 in LocalTime { localDay = addDays (fromIntegral totalDays) epoch , localTimeOfDay = timeToTimeOfDay $ secondsToDiffTime $ fromIntegral secondsOfDay } fixedP :: Parser Fixed32 fixedP = Fixed32 . fromIntegral <$> Megaparsec.Binary.word32be parseTable :: TableDirectory -> Parser a -> Megaparsec.State ByteString Void -> Either (Megaparsec.ParseErrorBundle ByteString Void) a parseTable TableDirectory{ offset, length } parser state = snd $ Megaparsec.runParser' parser $ state { Megaparsec.stateInput = stateInput , Megaparsec.stateOffset = stateOffset , Megaparsec.statePosState = posState { Megaparsec.pstateInput = stateInput , Megaparsec.pstateOffset = stateOffset } } where posState = Megaparsec.statePosState state stateInput = ByteString.take length $ ByteString.drop (offset - Megaparsec.stateOffset state) $ Megaparsec.stateInput state stateOffset = offset -- * OS/2 table os2TableP :: Parser Os2Table os2TableP = do baseFields <- os2BaseFieldsP result <- case getField @"version" baseFields of 0 -> Os2Version0 baseFields <$> Megaparsec.optional os2MicrosoftFieldsP 1 -> Os2Version1 baseFields <$> os2MicrosoftFieldsP <*> os2Version1FieldsP 2 -> Os2Version2 baseFields <$> os2MicrosoftFieldsP <*> os2Version4FieldsP 3 -> Os2Version3 baseFields <$> os2MicrosoftFieldsP <*> os2Version4FieldsP 4 -> Os2Version4 baseFields <$> os2MicrosoftFieldsP <*> os2Version4FieldsP 5 -> Os2Version5 baseFields <$> os2MicrosoftFieldsP <*> os2Version5FieldsP unsupportedVersion -> fail $ "Unsupported OS/2 version: " <> show unsupportedVersion Megaparsec.eof pure result os2BaseFieldsP :: Parser Os2BaseFields os2BaseFieldsP = Os2BaseFields <$> Megaparsec.Binary.word16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> panoseP <*> vectorNP 4 Megaparsec.Binary.word32be <*> vectorNP 4 Megaparsec.Binary.int8 <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be os2MicrosoftFieldsP :: Parser Os2MicrosoftFields os2MicrosoftFieldsP = Os2MicrosoftFields <$> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be os2Version1FieldsP :: Parser Os2Version1Fields os2Version1FieldsP = Os2Version1Fields <$> Megaparsec.Binary.word32be <*> Megaparsec.Binary.word32be os2Version4FieldsP :: Parser Os2Version4Fields os2Version4FieldsP = Os2Version4Fields <$> Megaparsec.Binary.word32be <*> Megaparsec.Binary.word32be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be os2Version5FieldsP :: Parser Os2Version5Fields os2Version5FieldsP = Os2Version5Fields <$> Megaparsec.Binary.word32be <*> Megaparsec.Binary.word32be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.int16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be <*> Megaparsec.Binary.word16be panoseP :: Parser Panose panoseP = Panose <$> bFamilyTypeP <*> bSerifStyleP <*> bWeightP <*> bProportionP <*> bContrastP <*> bStrokeVariationP <*> bArmStyleP <*> bLetterformP <*> bMidlineP <*> bXHeightP bFamilyTypeP :: Parser BFamilyType bFamilyTypeP = (Megaparsec.single 0 $> AnyFamilyType) <|> (Megaparsec.single 1 $> NoFitFamilyType) <|> (Megaparsec.single 2 $> TextAndDisplayFamilyType) <|> (Megaparsec.single 3 $> ScriptFamilyType) <|> (Megaparsec.single 4 $> DecorativeFamilyType) <|> (Megaparsec.single 5 $> PictorialFamilyType) "bFamilyType" bSerifStyleP :: Parser BSerifStyle bSerifStyleP = (Megaparsec.single 0 $> AnySerifStyle) <|> (Megaparsec.single 1 $> NoFitSerifStyle) <|> (Megaparsec.single 2 $> CoveSerifStyle) <|> (Megaparsec.single 3 $> ObtuseCoveSerifStyle) <|> (Megaparsec.single 4 $> SquareCoveSerifStyle) <|> (Megaparsec.single 5 $> ObtuseSquareCoveSerifStyle) <|> (Megaparsec.single 6 $> SquareSerifStyle) <|> (Megaparsec.single 7 $> ThinSerifStyle) <|> (Megaparsec.single 8 $> BoneSerifStyle) <|> (Megaparsec.single 9 $> ExaggeratedSerifStyle) <|> (Megaparsec.single 10 $> TriangleSerifStyle) <|> (Megaparsec.single 11 $> NormalSansSerifStyle) <|> (Megaparsec.single 12 $> ObtuseSansSerifStyle) <|> (Megaparsec.single 13 $> PerpSansSerifStyle) <|> (Megaparsec.single 14 $> FlaredSerifStyle) <|> (Megaparsec.single 15 $> RoundedSerifStyle) "bSerifStyle" bWeightP :: Parser BWeight bWeightP = (Megaparsec.single 0 $> AnyWeight) <|> (Megaparsec.single 1 $> NoFitWeight) <|> (Megaparsec.single 2 $> VeryLightWeight) <|> (Megaparsec.single 3 $> LightWeight) <|> (Megaparsec.single 4 $> ThinWeight) <|> (Megaparsec.single 5 $> BookWeight) <|> (Megaparsec.single 6 $> MediumWeight) <|> (Megaparsec.single 7 $> DemiWeight) <|> (Megaparsec.single 8 $> BoldWeight) <|> (Megaparsec.single 9 $> HeavyWeight) <|> (Megaparsec.single 10 $> BlackWeight) <|> (Megaparsec.single 11 $> NordWeight) "bWeight" bProportionP :: Parser BProportion bProportionP = (Megaparsec.single 0 $> AnyProportion) <|> (Megaparsec.single 1 $> NoFitProportion) <|> (Megaparsec.single 2 $> OldStyleProportion) <|> (Megaparsec.single 3 $> ModernProportion) <|> (Megaparsec.single 4 $> EvenWidthProportion) <|> (Megaparsec.single 5 $> ExpandedProportion) <|> (Megaparsec.single 6 $> CondensedProportion) <|> (Megaparsec.single 7 $> VeryExpandedProportion) <|> (Megaparsec.single 8 $> VeryCondensedProportion) <|> (Megaparsec.single 9 $> MonospacedProportion) "bProportion" bContrastP :: Parser BContrast bContrastP = (Megaparsec.single 0 $> AnyContrast) <|> (Megaparsec.single 1 $> NoFitContrast) <|> (Megaparsec.single 2 $> NoneContrast) <|> (Megaparsec.single 3 $> VeryLowContrast) <|> (Megaparsec.single 4 $> LowContrast) <|> (Megaparsec.single 5 $> MediumLowContrast) <|> (Megaparsec.single 6 $> MediumContrast) <|> (Megaparsec.single 7 $> MediumHighContrast) <|> (Megaparsec.single 8 $> HighContrast) <|> (Megaparsec.single 9 $> VeryHighContrast) "bContrast" bStrokeVariationP :: Parser BStrokeVariation bStrokeVariationP = (Megaparsec.single 0 $> AnyStrokeVariatoon) <|> (Megaparsec.single 1 $> NoFitStrokeVariatoon) <|> (Megaparsec.single 2 $> GradualDiagonalStrokeVariatoon) <|> (Megaparsec.single 3 $> GradualTransitionalStrokeVariatoon) <|> (Megaparsec.single 4 $> GradualVerticalStrokeVariatoon) <|> (Megaparsec.single 5 $> GradualHorizontalStrokeVariatoon) <|> (Megaparsec.single 6 $> RapidVerticalStrokeVariatoon) <|> (Megaparsec.single 7 $> RapidHorizontalStrokeVariatoon) <|> (Megaparsec.single 8 $> InstantVerticalStrokeVariatoon) "bStrokeVariation" bArmStyleP :: Parser BArmStyle bArmStyleP = (Megaparsec.single 0 $> AnyArmStyle) <|> (Megaparsec.single 1 $> NoFitArmStyle) <|> (Megaparsec.single 2 $> StraightArmsHorizontalArmStyle) <|> (Megaparsec.single 3 $> StraightArmsWedgeArmStyle) <|> (Megaparsec.single 4 $> StraightArmsVerticalArmStyle) <|> (Megaparsec.single 5 $> StraightArmsSingleSerifArmStyle) <|> (Megaparsec.single 6 $> StraightArmsDoubleSerifArmStyle) <|> (Megaparsec.single 7 $> NonStraightArmsHorizontalArmStyle) <|> (Megaparsec.single 8 $> NonStraightArmsWedgeArmStyle) <|> (Megaparsec.single 9 $> NonStraightArmsVerticalArmStyle) <|> (Megaparsec.single 10 $> NonStraightArmsSingleSerifArmStyle) <|> (Megaparsec.single 11 $> NonStraightArmsDoubleSerifArmStyle) "bArmStyle" bLetterformP :: Parser BLetterform bLetterformP = (Megaparsec.single 0 $> AnyLetterform) <|> (Megaparsec.single 1 $> NoFitLetterform) <|> (Megaparsec.single 2 $> NormalContactLetterform) <|> (Megaparsec.single 3 $> NormalWeightedLetterform) <|> (Megaparsec.single 4 $> NormalBoxedLetterform) <|> (Megaparsec.single 5 $> NormalFlattenedLetterform) <|> (Megaparsec.single 6 $> NormalRoundedLetterform) <|> (Megaparsec.single 7 $> NormalOffCenterLetterform) <|> (Megaparsec.single 8 $> NormalSquareLetterform) <|> (Megaparsec.single 9 $> ObliqueContactLetterform) <|> (Megaparsec.single 10 $> ObliqueWeightedLetterform) <|> (Megaparsec.single 11 $> ObliqueBoxedLetterform) <|> (Megaparsec.single 12 $> ObliqueFlattenedLetterform) <|> (Megaparsec.single 13 $> ObliqueRoundedLetterform) <|> (Megaparsec.single 14 $> ObliqueOffCenterLetterform) <|> (Megaparsec.single 15 $> ObliqueSquareLetterform) "bLetterform" bXHeightP :: Parser BXHeight bXHeightP = (Megaparsec.single 0 $> AnyXHeight) <|> (Megaparsec.single 1 $> NoFitXHeight) <|> (Megaparsec.single 2 $> ConstantSmallXHeight) <|> (Megaparsec.single 3 $> ConstantStandardXHeight) <|> (Megaparsec.single 4 $> ConstantLargeXHeight) <|> (Megaparsec.single 5 $> DuckingSmallXHeight) <|> (Megaparsec.single 6 $> DuckingStandardXHeight) <|> (Megaparsec.single 7 $> DuckingLargeXHeight) "bXHeight" bMidlineP :: Parser BMidline bMidlineP = (Megaparsec.single 0 $> AnyMidline) <|> (Megaparsec.single 1 $> NoFitMidline) <|> (Megaparsec.single 2 $> StandardTrimmedMidline) <|> (Megaparsec.single 3 $> StandardPointedMidline) <|> (Megaparsec.single 4 $> StandardSerifedMidline) <|> (Megaparsec.single 5 $> HighTrimmedMidline) <|> (Megaparsec.single 6 $> HighPointedMidline) <|> (Megaparsec.single 7 $> HighSerifedMidline) <|> (Megaparsec.single 8 $> ConstantTrimmedMidline) <|> (Megaparsec.single 9 $> ConstantPointedMidline) <|> (Megaparsec.single 10 $> ConstantSerifedMidline) <|> (Megaparsec.single 11 $> LowTrimmedMidline) <|> (Megaparsec.single 12 $> LowPointedMidline) <|> (Megaparsec.single 13 $> LowSerifedMidline) "bMidline"