2023-11-10 11:57:08 +01:00
|
|
|
{- 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/. -}
|
|
|
|
|
2023-03-13 10:51:25 +01:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
|
|
|
{-# LANGUAGE ExplicitForAll #-}
|
|
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
|
|
{-# LANGUAGE PatternGuards #-}
|
|
|
|
{-# LANGUAGE TypeApplications #-}
|
|
|
|
|
|
|
|
-- | Font parser.
|
|
|
|
module Graphics.Fountainhead.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"
|