From a34b46e1b553623d5dc385fc8e235df808fbadb2 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sat, 3 Feb 2024 11:58:47 +0100 Subject: Add font compression --- lib/Graphics/Fountainhead/Parser.hs | 1244 +++++++++++++++++++++++++++++++++++ 1 file changed, 1244 insertions(+) create mode 100644 lib/Graphics/Fountainhead/Parser.hs (limited to 'lib/Graphics/Fountainhead/Parser.hs') diff --git a/lib/Graphics/Fountainhead/Parser.hs b/lib/Graphics/Fountainhead/Parser.hs new file mode 100644 index 0000000..31dcd0e --- /dev/null +++ b/lib/Graphics/Fountainhead/Parser.hs @@ -0,0 +1,1244 @@ +{- 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 + , ParseErrorBundle + , cmapTableP + , cvTableP + , f2Dot14P + , fixedP + , fontDirectoryP + , fpgmTableP + , gaspTableP + , glyfTableP + , hdmxTableP + , headTableP + , hheaTableP + , hmtxTableP + , locaTableP + , longDateTimeP + , longLocaTableP + , maxpTableP + , nameTableP + , os2TableP + , panoseP + , parseTable + , pascalStringP + , postTableP + , prepTableP + , shortLocaTableP + , word24P + ) where + +import Control.Applicative (Alternative(..)) +import Control.Monad (foldM, void) +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 (sortOn, nubBy, sortBy) +import Data.List.NonEmpty (NonEmpty(..)) +import qualified Data.List.NonEmpty as NonEmpty +import Data.Time + ( LocalTime(..) + , addDays + , secondsToDiffTime + , timeToTimeOfDay + ) +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(..) + , GASPRange(..) + , GASPTable(..) + , GlyfTable(..) + , GlyphArgument(..) + , GlyphCoordinate(..) + , GlyphDefinition(..) + , GlyphDescription(..) + , GlyphTransformationOption(..) + , HdmxTable(..) + , DeviceRecord(..) + , HeadTable(..) + , HheaTable(..) + , HmtxTable(..) + , IndexToLocFormat(..) + , 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(..) + , succIntegral + , ttfEpoch + ) +import Text.Megaparsec (()) +import qualified Text.Megaparsec as Megaparsec +import qualified Text.Megaparsec.Byte.Binary as Megaparsec.Binary + +type Parser = Megaparsec.Parsec Void ByteString +type ParseErrorBundle = Megaparsec.ParseErrorBundle ByteString Void + +-- * 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 = 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.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.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 + <*> indexToLocFormatP + <*> Megaparsec.Binary.word16be + <* Megaparsec.eof + where + indexToLocFormatP = do + indexToLocFormat' <- Megaparsec.Binary.int16be + case indexToLocFormat' of + 0 -> pure ShortOffsetIndexToLocFormat + 1 -> pure LongOffsetIndexToLocFormat + _ -> fail $ "Unknown loca table format indexToLocFormat: " + <> show indexToLocFormat' + +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" + +locaTableP :: IndexToLocFormat -> Parser LocaTable +locaTableP ShortOffsetIndexToLocFormat = shortLocaTableP +locaTableP LongOffsetIndexToLocFormat = longLocaTableP + +-- * Horizontal metrics table + +longHorMetricP :: Parser LongHorMetric +longHorMetricP = LongHorMetric + <$> Megaparsec.Binary.word16be + <*> Megaparsec.Binary.int16be + +hmtxTableP :: Word16 -> Parser HmtxTable +hmtxTableP numOfLongHorMetrics = HmtxTable + <$> countP (fromIntegral 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 + void $ 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 + +-- | Arguments are: WE_HAVE_A_SCALE, WE_HAVE_AN_X_AND_Y_SCALE and +-- WE_HAVE_A_TWO_BY_TWO. +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 + +-- | Arguments are: ARG_1_AND_2_ARE_WORDS and ARGS_ARE_XY_VALUES. +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 (coordinatesP xFlagPair) mempty flags' + ys <- Vector.foldM (coordinatesP yFlagPair) mempty flags' + pure $ SimpleGlyphDefinition + { endPtsOfContours = endPtsOfContours' + , instructions = instructions' + , flags = flags' + , coordinates = mkCoordinate <$> Vector.zip xs ys + } + where + mkCoordinate (x, y) = GlyphCoordinate x y + xFlagPair :: OutlineFlag -> (Bool, Bool) + xFlagPair OutlineFlag{ xShortVector, thisXIsSame } = + (xShortVector, thisXIsSame) + yFlagPair :: OutlineFlag -> (Bool, Bool) + yFlagPair OutlineFlag{ yShortVector, thisYIsSame } = + (yShortVector, thisYIsSame) + coordinateP :: Bool -> Bool -> Parser Int16 + coordinateP True True = fromIntegral + <$> Megaparsec.Binary.word8 + "1 byte long positive coordinate" + coordinateP True False = negate . fromIntegral + <$> Megaparsec.Binary.word8 + "1 byte long negative coordinate" + coordinateP False False = Megaparsec.Binary.int16be + "2 bytes long coordinate" + coordinateP False True = pure 0 + coordinatesP + :: (OutlineFlag -> (Bool, Bool)) + -> Vector Int16 + -> OutlineFlag + -> Parser (Vector Int16) + coordinatesP get accumulator first = + let parser = uncurry coordinateP $ get first + repeatN = succIntegral $ getField @"repeat" first + in (accumulator <>) <$> vectorNP repeatN parser + flagsP :: Int -> Vector OutlineFlag -> Parser (Vector OutlineFlag) + flagsP remaining accumulator + | remaining < 0 = pure accumulator + | otherwise = do + flag <- Megaparsec.Binary.word8 "outline flags" + repeatN <- + if testBit flag 3 + then fromIntegral + <$> Megaparsec.Binary.word8 + "flag repeat count" + else pure 0 + let flag' = OutlineFlag + { onCurve = testBit flag 0 + , xShortVector = testBit flag 1 + , yShortVector = testBit flag 2 + , repeat = fromIntegral repeatN + , thisXIsSame = testBit flag 4 + , thisYIsSame = testBit flag 5 + } + flagsP (remaining - repeatN - 1) + $ Vector.snoc accumulator 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 + void 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 + void $ 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 + void $ 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 + void $ 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 + void $ 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 + void $ 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 + void 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 + void $ Megaparsec.chunk $ ByteString.pack [0, 0] -- 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 = succIntegral $ 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) + in LocalTime + { localDay = addDays (fromIntegral totalDays) ttfEpoch + , localTimeOfDay = timeToTimeOfDay + $ secondsToDiffTime + $ fromIntegral secondsOfDay + } + +fixedP :: Parser Fixed32 +fixedP = Fixed32 . fromIntegral <$> Megaparsec.Binary.word32be + +parseTable + :: TableDirectory + -> Parser a + -> Megaparsec.State ByteString Void + -> Either ParseErrorBundle a +parseTable TableDirectory{ offset, length = 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 $> AnyStrokeVariation) + <|> (Megaparsec.single 1 $> NoFitStrokeVariation) + <|> (Megaparsec.single 2 $> GradualDiagonalStrokeVariation) + <|> (Megaparsec.single 3 $> GradualTransitionalStrokeVariation) + <|> (Megaparsec.single 4 $> GradualVerticalStrokeVariation) + <|> (Megaparsec.single 5 $> GradualHorizontalStrokeVariation) + <|> (Megaparsec.single 6 $> RapidVerticalStrokeVariation) + <|> (Megaparsec.single 7 $> RapidHorizontalStrokeVariation) + <|> (Megaparsec.single 8 $> InstantVerticalStrokeVariation) + "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" + +-- * Grid-fitting And Scan-conversion Procedure. + +gaspTableP :: Parser GASPTable +gaspTableP = do + version' <- Megaparsec.Binary.word16be + numberRanges <- fromIntegral <$> Megaparsec.Binary.word16be + parsedRanges <- Megaparsec.count numberRanges gaspRangeP + Megaparsec.eof + pure $ GASPTable + { version = version' + , gaspRange = parsedRanges + } + where + gaspRangeP = GASPRange + <$> Megaparsec.Binary.word16be + <*> Megaparsec.Binary.word16be -- cgit v1.2.3