diff options
| author | Eugen Wissner <belka@caraus.de> | 2024-02-03 11:58:47 +0100 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2024-02-03 11:58:47 +0100 |
| commit | a34b46e1b553623d5dc385fc8e235df808fbadb2 (patch) | |
| tree | 7035a9625532bf6f7f41962c4352ac2367d065f3 /src/Graphics/Fountainhead/Dumper.hs | |
| parent | 34d3ece99e438e5e81f4df6ca7a36de307e41b3e (diff) | |
| download | fountainhead-a34b46e1b553623d5dc385fc8e235df808fbadb2.tar.gz | |
Add font compression
Diffstat (limited to 'src/Graphics/Fountainhead/Dumper.hs')
| -rw-r--r-- | src/Graphics/Fountainhead/Dumper.hs | 847 |
1 files changed, 0 insertions, 847 deletions
diff --git a/src/Graphics/Fountainhead/Dumper.hs b/src/Graphics/Fountainhead/Dumper.hs deleted file mode 100644 index adda06f..0000000 --- a/src/Graphics/Fountainhead/Dumper.hs +++ /dev/null @@ -1,847 +0,0 @@ -{- 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 OverloadedStrings #-} -{-# LANGUAGE PatternGuards #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE TypeApplications #-} - --- | Outputs information about a font as text. -module Graphics.Fountainhead.Dumper - ( DumpError(..) - , dumpCmap - , dumpGlyf - , dumpHead - , dumpHmtx - , dumpHhea - , dumpLoca - , dumpName - , dumpMaxp - , dumpOs2 - , dumpPost - , dumpTables - , dumpTrueType - , dumpOffsetTable - ) where - -import Data.ByteString (ByteString) -import qualified Data.ByteString as ByteString -import qualified Data.ByteString.Char8 as Char8 -import Data.Int (Int64, Int16) -import Data.Word (Word8, Word16, Word32) -import qualified Data.IntMap as IntMap -import qualified Data.Text as Text -import qualified Data.Text.Encoding as Text -import qualified Data.Text.Lazy as Text.Lazy -import qualified Data.Text.Lazy.Builder as Text.Builder -import qualified Data.Text.Lazy.Builder.Int as Text.Builder -import qualified Data.Text.Lazy.Builder.RealFloat as Text.Builder -import Data.Vector (Vector) -import qualified Data.Vector as Vector -import Data.Void -import GHC.Records (HasField(..)) -import Graphics.Fountainhead.TrueType - ( CmapTable(..) - , CompoundGlyphDefinition(..) - , ComponentGlyphPartDescription(..) - , FontDirectory(..) - , FontDirectionHint(..) - , GASPRange(..) - , GASPTable(..) - , GlyphArgument(..) - , HeadTable(..) - , HheaTable(..) - , HmtxTable(..) - , OffsetSubtable(..) - , PostHeader(..) - , PostSubtable(..) - , PostFormat2Table(..) - , PostTable(..) - , TableDirectory(..) - , CmapEncoding(..) - , CmapSubtable(..) - , CmapFormat4Table(..) - , FontStyle(..) - , GlyphArgument(..) - , GlyphCoordinate(..) - , GlyphDefinition(..) - , GlyphDescription(..) - , GlyfTable(..) - , LongHorMetric(..) - , LocaTable(..) - , NameRecord (..) - , NameTable(..) - , IndexToLocFormat(..) - , OpenMaxpTable(..) - , MaxpTable(..) - , TrueMaxpTable(..) - , nameStringOffset - , Os2BaseFields(..) - , Os2MicrosoftFields(..) - , Os2Version1Fields(..) - , Os2Version4Fields(..) - , Os2Version5Fields(..) - , Os2Table(..) - , Panose(..) - , SimpleGlyphDefinition(..) - , CVTable(..) - , OutlineFlag(..) - , ComponentGlyphFlags(..) - , GlyphTransformationOption(..) - ) -import qualified Text.Megaparsec as Megaparsec -import Graphics.Fountainhead.Parser - ( fontDirectoryP - , parseTable - , cmapTableP - , headTableP - , hheaTableP - , hmtxTableP - , gaspTableP - , locaTableP - , maxpTableP - , nameTableP - , os2TableP - , postTableP - , cvTableP - , glyfTableP - ) -import Graphics.Fountainhead.Type - ( Fixed32(..) - , succIntegral - , ttfEpoch - , fixed2Double - ) -import Data.Foldable (Foldable(..), find) -import Data.Maybe (fromMaybe, catMaybes) -import Data.Time (LocalTime(..), NominalDiffTime, diffLocalTime, midnight) -import Data.Bits (Bits(..), (.>>.)) -import Data.Bifunctor (Bifunctor(first)) -import Data.List (intersperse) -import Prelude hiding (repeat) - -data DumpError - = DumpParseError (Megaparsec.ParseErrorBundle ByteString Void) - | DumpRequiredTableMissingError String - -data RequiredTables = RequiredTables - { hheaTable :: HheaTable - , headTable :: HeadTable - , locaTable :: LocaTable - } deriving (Eq, Show) - -paddedHexadecimal :: Integral a => a -> Text.Builder.Builder -paddedHexadecimal = ("0x" <>) - . Text.Builder.fromLazyText - . Text.Lazy.justifyRight 8 '0' - . Text.Builder.toLazyText - . Text.Builder.hexadecimal - -halfPaddedHexadecimal :: Integral a => a -> Text.Builder.Builder -halfPaddedHexadecimal = Text.Builder.fromLazyText - . Text.Lazy.justifyRight 4 '0' - . Text.Builder.toLazyText - . Text.Builder.hexadecimal - -justifyNumber :: Integral a => Int64 -> a -> Text.Builder.Builder -justifyNumber count = Text.Builder.fromLazyText - . Text.Lazy.justifyRight count ' ' - . Text.Builder.toLazyText - . Text.Builder.decimal - -newlineBuilder :: Text.Builder.Builder -newlineBuilder = Text.Builder.singleton '\n' - -dumpCaption :: String -> Text.Builder.Builder -dumpCaption headline = Text.Builder.fromString headline - <> newlineBuilder - <> Text.Builder.fromLazyText (Text.Lazy.replicate headlineLength "-") - <> newlineBuilder - where - headlineLength = fromIntegral $ Prelude.length headline - -dumpOffsetTable :: FontDirectory -> Text.Builder.Builder -dumpOffsetTable directory - = dumpCaption "Offset Table" - <> " sfnt version: 1.0\n number of tables: " - <> Text.Builder.decimal (numTables $ offsetSubtable directory) - <> newlineBuilder - <> dumpOffsetSummary (tableDirectory directory) - where - dumpOffsetSummary = mconcat . fmap dumpOffsetRow . zip [0 :: Int ..] - dumpOffsetRow (index, table) = justifyNumber 4 index - <> ". '" - <> Text.Builder.fromText (Text.decodeASCII $ tag table) - <> "' - checksum = " - <> paddedHexadecimal (getField @"checkSum" table) - <> ", offset = " - <> paddedHexadecimal (getField @"offset" table) - <> ", len = " - <> justifyNumber 9 (getField @"length" table) - <> newlineBuilder - -dumpFixed32 :: Fixed32 -> Text.Builder.Builder -dumpFixed32 (Fixed32 word) - = Text.Builder.decimal (shiftR word 16) - <> Text.Builder.singleton '.' - <> Text.Builder.decimal (word .&. 0xff00) - -dumpHmtx :: HmtxTable -> Text.Builder.Builder -dumpHmtx HmtxTable{..} = - let caption = dumpCaption "'hmtx' Table - Horizontal Metrics" - lastAccumulator = foldl' dumpHMetric (0 :: Int, caption) hMetrics - in snd $ foldl' dumpLeftSideBear lastAccumulator leftSideBearing - where - dumpLeftSideBear (index, accumulator) leftSideBearing' = - let withNewLine = dumpIndex index <> ". LSbear: " - <> justifyNumber 4 leftSideBearing' <> newlineBuilder - in (succ index, accumulator <> withNewLine) - dumpHMetric (index, accumulator) metric = - let LongHorMetric{ leftSideBearing = leftSideBearing', ..} = metric - withNewLine = dumpIndex index <> ". advWid: " - <> justifyNumber 4 advanceWidth <> ", LSBear: " - <> justifyNumber 4 leftSideBearing' <> newlineBuilder - in (succ index, accumulator <> withNewLine) - dumpIndex = justifyNumber 12 - -dumpHhea :: HheaTable -> Text.Builder.Builder -dumpHhea HheaTable{..} - = dumpCaption "'hhea' Table - Horizontal Header" - <> " 'hhea' version: " <> dumpFixed32 version <> newlineBuilder - <> " yAscender: " <> Text.Builder.decimal ascent <> newlineBuilder - <> " yDescender: " <> Text.Builder.decimal descent <> newlineBuilder - <> " yLineGap: " <> Text.Builder.decimal lineGap <> newlineBuilder - <> " advanceWidthMax: " <> Text.Builder.decimal advanceWidthMax <> newlineBuilder - <> " minLeftSideBearing: " <> Text.Builder.decimal minLeftSideBearing <> newlineBuilder - <> " minRightSideBearing: " <> Text.Builder.decimal minRightSideBearing <> newlineBuilder - <> " xMaxExtent: " <> Text.Builder.decimal xMaxExtent <> newlineBuilder - <> " caretSlopeRise: " <> Text.Builder.decimal caretSlopeRise <> newlineBuilder - <> " caretSlopeRun: " <> Text.Builder.decimal caretSlopeRun <> newlineBuilder - <> " reserved0: 0" <> newlineBuilder - <> " reserved1: 0" <> newlineBuilder - <> " reserved2: 0" <> newlineBuilder - <> " reserved3: 0" <> newlineBuilder - <> " reserved4: 0" <> newlineBuilder - <> " metricDataFormat: " <> Text.Builder.decimal metricDataFormat <> newlineBuilder - <> " numberOfHMetrics: " <> Text.Builder.decimal numOfLongHorMetrics <> newlineBuilder - -dumpHead :: HeadTable -> Text.Builder.Builder -dumpHead HeadTable{..} - = dumpCaption "'head' Table - Font Header" - <> " head version: " <> dumpFixed32 version <> newlineBuilder - <> " fontRevision: " <> dumpFixed32 fontRevision <> newlineBuilder - <> " checkSumAdjustment: " <> paddedHexadecimal checkSumAdjustment <> newlineBuilder - <> " magicNumber: " <> paddedHexadecimal magicNumber <> newlineBuilder - <> " flags: 0x" <> halfPaddedHexadecimal flags <> newlineBuilder - <> " unitsPerEm: " <> Text.Builder.decimal unitsPerEm <> newlineBuilder - <> " created: " <> "0x" <> longDateTime created <> newlineBuilder - <> " modified: " <> "0x" <> longDateTime modified <> newlineBuilder - <> " xMin: " <> Text.Builder.decimal xMin <> newlineBuilder - <> " yMin: " <> Text.Builder.decimal yMin <> newlineBuilder - <> " xMax: " <> Text.Builder.decimal xMax <> newlineBuilder - <> " yMax: " <> Text.Builder.decimal yMax <> newlineBuilder - <> " macStyle bits: " <> "0x" <> dumpFontStyle macStyle <> newlineBuilder - <> " lowestRecPPEM " <> Text.Builder.decimal lowestRecPPEM <> newlineBuilder - <> " fontDirectionHint " <> dumpFontDirectionHint fontDirectionHint <> newlineBuilder - <> " indexToLocFormat " <> dumpIndexToLocFormat indexToLocFormat <> newlineBuilder - <> " glyphDataFormat " <> Text.Builder.decimal glyphDataFormat <> newlineBuilder - -dumpIndexToLocFormat :: IndexToLocFormat -> Text.Builder.Builder -dumpIndexToLocFormat ShortOffsetIndexToLocFormat = "0" -dumpIndexToLocFormat LongOffsetIndexToLocFormat = "1" - -dumpFontDirectionHint :: FontDirectionHint -> Text.Builder.Builder -dumpFontDirectionHint = \case - MixedDirectionalGlyphs -> "0" - StronglyLeftToRightGlyphs -> "1" - LeftToRightGlyphsWithNeutrals -> "2" - StronglyRightToLeftGlyphs -> "-1" - RightToLeftGlyphsWithNeutrals -> "-2" - -dumpFontStyle :: FontStyle -> Text.Builder.Builder -dumpFontStyle FontStyle{..} = halfPaddedHexadecimal - $ foldr (go . fst) (zeroBits :: Int) - $ filter snd - $ zip [0..] [bold, italic, underline, outline, shadow, condensed, extended] - where - go bitNumber accumulator = setBit accumulator bitNumber - -longDateTime :: LocalTime -> Text.Builder.Builder -longDateTime localTime = Text.Builder.fromLazyText - $ Text.Lazy.justifyRight 16 '0' - $ Text.Builder.toLazyText - $ Text.Builder.hexadecimal - $ (truncate :: NominalDiffTime -> Int) - $ diffLocalTime localTime (LocalTime ttfEpoch midnight) - -dumpCVTable :: CVTable -> Text.Builder.Builder -dumpCVTable (CVTable cvTable) = dumpCaption "'cvt ' Table - Control Value Table" - <> "Size = " <> Text.Builder.decimal (tableSize * 2) - <> " bytes, " <> Text.Builder.decimal tableSize <> " entries\n" - <> foldMap (uncurry go) (zip [0..] cvTable) - where - tableSize = Prelude.length cvTable - go :: Int -> Int16 -> Text.Builder.Builder - go index' entry = justifyNumber 13 index' <> ". " - <> Text.Builder.decimal entry <> newlineBuilder - -dumpOs2 :: Os2Table -> Text.Builder.Builder -dumpOs2 = (dumpCaption "'OS/2' Table - OS/2 and Windows Metrics" <>) . go - where - go = \case - Os2Version0 baseFields msFields -> dumpBaseFields baseFields - <> maybe "" dumpMicrosoftFields msFields - Os2Version1 baseFields msFields extraFields -> dumpBaseFields baseFields - <> dumpMicrosoftFields msFields <> dumpVersion1Fields extraFields - Os2Version2 baseFields msFields extraFields -> dumpBaseFields baseFields - <> dumpMicrosoftFields msFields <> dumpVersion4Fields extraFields - Os2Version3 baseFields msFields extraFields -> dumpBaseFields baseFields - <> dumpMicrosoftFields msFields <> dumpVersion4Fields extraFields - Os2Version4 baseFields msFields extraFields -> dumpBaseFields baseFields - <> dumpMicrosoftFields msFields <> dumpVersion4Fields extraFields - Os2Version5 baseFields msFields extraFields -> dumpBaseFields baseFields - <> dumpMicrosoftFields msFields <> dumpVersion5Fields extraFields - dumpVersion1Fields Os2Version1Fields{..} - = " CodePage Range 1( Bits 0 - 31 ): " <> paddedHexadecimal ulCodePageRange1 <> newlineBuilder - <> " CodePage Range 2( Bits 32- 63 ): " <> paddedHexadecimal ulCodePageRange2 <> newlineBuilder - dumpVersion4Fields Os2Version4Fields{..} - = dumpVersion1Fields (Os2Version1Fields ulCodePageRange1 ulCodePageRange2) - <> " sxHeight: " <> Text.Builder.decimal sxHeight <> newlineBuilder - <> " sCapHeight: " <> Text.Builder.decimal sCapHeight <> newlineBuilder - <> " usDefaultChar: 0x" <> halfPaddedHexadecimal usDefaultChar <> newlineBuilder - <> " usBreakChar: 0x" <> halfPaddedHexadecimal usBreakChar <> newlineBuilder - <> " usMaxContext: " <> Text.Builder.decimal usMaxContext <> newlineBuilder - dumpVersion5Fields Os2Version5Fields{..} - = dumpVersion1Fields (Os2Version1Fields ulCodePageRange1 ulCodePageRange2) - <> " sxHeight: " <> Text.Builder.decimal sxHeight <> newlineBuilder - <> " sCapHeight: " <> Text.Builder.decimal sCapHeight <> newlineBuilder - <> " usDefaultChar: 0x" <> halfPaddedHexadecimal usDefaultChar <> newlineBuilder - <> " usBreakChar: 0x" <> halfPaddedHexadecimal usBreakChar <> newlineBuilder - <> " usMaxContext: " <> Text.Builder.decimal usMaxContext <> newlineBuilder - <> " usLowerOpticalPointSize: " - <> Text.Builder.decimal usLowerOpticalPointSize <> newlineBuilder - <> " usUpperOpticalPointSize: " - <> Text.Builder.decimal usUpperOpticalPointSize <> newlineBuilder - dumpMicrosoftFields Os2MicrosoftFields{..} - = " sTypoAscender: " <> Text.Builder.decimal sTypoAscender <> newlineBuilder - <> " sTypoDescender: " <> Text.Builder.decimal sTypoDescender <> newlineBuilder - <> " sTypoLineGap: " <> Text.Builder.decimal sTypoLineGap <> newlineBuilder - <> " usWinAscent: " <> Text.Builder.decimal usWinAscent <> newlineBuilder - <> " usWinDescent: " <> Text.Builder.decimal usWinDescent <> newlineBuilder - dumpBaseFields Os2BaseFields{..} - = " 'OS/2' version: " <> Text.Builder.decimal version <> newlineBuilder - <> " xAvgCharWidth: " <> Text.Builder.decimal xAvgCharWidth <> newlineBuilder - <> " usWeightClass: " <> weightClass usWeightClass <> newlineBuilder - <> " usWidthClass: " <> widthClass usWidthClass <> newlineBuilder - <> " fsType: " <> Text.Builder.decimal fsType <> newlineBuilder - <> " ySubscriptXSize: " <> Text.Builder.decimal ySubscriptXSize <> newlineBuilder - <> " ySubscriptYSize: " <> Text.Builder.decimal ySubscriptYSize <> newlineBuilder - <> " ySubscriptXOffset: " <> Text.Builder.decimal ySubscriptXOffset <> newlineBuilder - <> " ySubscriptYOffset: " <> Text.Builder.decimal ySubscriptYOffset <> newlineBuilder - <> " ySuperscriptXSize: " <> Text.Builder.decimal ySuperscriptXSize <> newlineBuilder - <> " ySuperscriptYSize: " <> Text.Builder.decimal ySuperscriptYSize <> newlineBuilder - <> " ySuperscriptXOffset: " <> Text.Builder.decimal ySuperscriptXOffset <> newlineBuilder - <> " ySuperscriptYOffset: " <> Text.Builder.decimal ySuperscriptYOffset <> newlineBuilder - <> " yStrikeoutSize: " <> Text.Builder.decimal yStrikeoutSize <> newlineBuilder - <> " yStrikeoutPosition: " <> Text.Builder.decimal yStrikeoutPosition <> newlineBuilder - <> " sFamilyClass:" <> familyClass sFamilyClass <> newlineBuilder - <> " PANOSE:" <> newlineBuilder <> dumpPanose panose - <> fold (Vector.imap dumpUnicodeRange ulUnicodeRange) - <> " achVendID: '" <> achVendID' achVendID <> "'\n" - <> " fsSelection: 0x" <> fsSelection' fsSelection <> newlineBuilder - <> " usFirstCharIndex: 0x" <> halfPaddedHexadecimal fsFirstCharIndex <> newlineBuilder - <> " usLastCharIndex: 0x" <> halfPaddedHexadecimal fsLastCharIndex <> newlineBuilder - fsSelection' value = - let description = fold - [ if testBit value 0 then "Italic " else "" - , if testBit value 5 then "Bold " else "" - ] - in halfPaddedHexadecimal value <> " '" <> description <> "'" - achVendID' = Text.Builder.fromText . Text.decodeLatin1 . ByteString.pack . fmap fromIntegral . toList - dumpUnicodeRange index value = - let bits = index * 32 - parens = "( Bits " <> Text.Builder.decimal bits <> " - " - <> Text.Builder.decimal (bits + 31) <> " ):" - in " Unicode Range: " <> Text.Builder.decimal (index + 1) - <> Text.Builder.fromLazyText (Text.Lazy.justifyLeft 25 ' ' (Text.Builder.toLazyText parens)) - <> paddedHexadecimal value - <> newlineBuilder - dumpPanose Panose{..} - = " Family Kind: " <> dumpPanoseField bFamilyType - <> " Serif Style: " <> dumpPanoseField bSerifStyle - <> " Weight: " <> dumpPanoseField bWeight - <> " Proportion: " <> dumpPanoseField bProportion - <> " Contrast: " <> dumpPanoseField bContrast - <> " Stroke: " <> dumpPanoseField bStrokeVariation - <> " Arm Style: " <> dumpPanoseField bArmStyle - <> " Lettreform: " <> dumpPanoseField bLetterform - <> " Midline: " <> dumpPanoseField bMidline - <> " X-height: " <> dumpPanoseField bXHeight - dumpPanoseField field' = - let numericField = Text.Builder.fromLazyText - $ Text.Lazy.justifyLeft 8 ' ' - $ Text.Builder.toLazyText - $ Text.Builder.decimal - $ fromEnum field' - in numericField - <> Text.Builder.singleton '\'' - <> Text.Builder.fromString (show field') - <> Text.Builder.singleton '\'' - <> newlineBuilder - familyClass value = - " " <> Text.Builder.decimal (value .>>. 8) <> " subclass = " <> Text.Builder.decimal (value .&. 0x00ff) - weightClass classValue - | Just wordValue <- fWeight classValue = Text.Builder.decimal classValue <> " '" <> wordValue <> "'" - | otherwise = Text.Builder.decimal classValue - widthClass classValue - | Just wordValue <- fWidth classValue = Text.Builder.decimal classValue <> " '" <> wordValue <> "'" - | otherwise = Text.Builder.decimal classValue - fWeight 100 = Just "Thin" - fWeight 200 = Just "Extra-light" - fWeight 300 = Just "Light" - fWeight 400 = Just "Normal" - fWeight 500 = Just "Medium" - fWeight 600 = Just "Semi-bold" - fWeight 700 = Just "Bold" - fWeight 800 = Just "Extra-bold" - fWeight 900 = Just "Black" - fWeight _ = Nothing - fWidth 1 = Just "Ultra-condensed" - fWidth 2 = Just "Extra-condensed" - fWidth 3 = Just "Condensed" - fWidth 4 = Just "Semi-condensed" - fWidth 5 = Just "Medium" - fWidth 6 = Just "Semi-expanded" - fWidth 7 = Just "Expanded" - fWidth 8 = Just "Extra-expanded" - fWidth 9 = Just "Ultra-expanded" - fWidth _ = Nothing - -dumpPost :: PostTable -> Text.Builder.Builder -dumpPost PostTable{ postHeader = PostHeader{..}, postSubtable } - = dumpCaption "'post' Table - PostScript" <> newlineBuilder - <> " 'post' format: " <> dumpFixed32 format <> newlineBuilder - <> " italicAngle: " <> dumpFixed32 format <> newlineBuilder - <> " underlinePosition: " <> Text.Builder.decimal underlinePosition <> newlineBuilder - <> " underlineThichness: " <> Text.Builder.decimal underlineThickness <> newlineBuilder - <> " isFixedPitch: " <> dNumber isFixedPitch <> newlineBuilder - <> " minMemType42: " <> dNumber minMemType42 <> newlineBuilder - <> " maxMemType42: " <> dNumber maxMemType42 <> newlineBuilder - <> " minMemType1: " <> dNumber minMemType1 <> newlineBuilder - <> " maxMemType1: " <> dNumber maxMemType1 <> newlineBuilder - <> dumpPostSubtable - where - dNumber = (<> Text.Builder.singleton 'd') . Text.Builder.decimal - dumpPostSubtable = case postSubtable of - None -> "" -- Format 1 and 3 do not require a subtable. - PostFormat2 PostFormat2Table{..} - -> " Format 2.0: Non-Standard (for PostScript) TrueType Glyph Set." <> newlineBuilder - <> " numGlyphs: " <> Text.Builder.decimal (Prelude.length glyphNameIndex) - <> newlineBuilder <> fold (Vector.imap (dumpFormat2Pair names) glyphNameIndex) - PostFormat25 _ -> "Format 2.5" - PostFormat4 _ -> "Format 4.0" - dumpFormat2Pair names index glyphNameIndex' - = " Glyf " <> justifyNumber 3 index - <> " -> " <> glyphType names glyphNameIndex' - <> newlineBuilder - glyphType names glyphNameIndex' - | glyphNameIndex' >= 0 - , glyphNameIndex' <= 257 = "Mac Glyph # " <> justifyNumber 3 glyphNameIndex' - | glyphNameIndex' >= 258 - , glyphNameIndex' <= 32767 = - let nameIndex = fromIntegral $ glyphNameIndex' - 258 - in "PSGlyph Name # " <> justifyNumber 3 (succ nameIndex) <> ", '" - <> Text.Builder.fromText (Text.decodeASCII (names Vector.! nameIndex)) - <> Text.Builder.singleton '\'' - | otherwise = "Reserved" - -dumpCmap :: CmapTable -> Text.Builder.Builder -dumpCmap CmapTable{..} - = dumpCaption "'cmap' Table - Character to Glyph Index Mapping Table" - <> " 'cmap' version: " <> Text.Builder.decimal version <> newlineBuilder - <> " number of encodings: " <> Text.Builder.decimal encodingsLength <> newlineBuilder - <> " number of subtables: " <> Text.Builder.decimal (Prelude.length subtables) <> newlineBuilder - <> newlineBuilder - <> snd (foldr dumpCmapEncoding (pred encodingsLength, "") encodings) <> newlineBuilder - <> snd (foldr dumpCmapSubTable (pred subTablesLength, "") subtables) <> newlineBuilder - where - encodingsLength = Prelude.length encodings - subTablesLength = IntMap.size subtables - dumpCmapEncoding CmapEncoding{..} (index, accumulator) = - let findSubTableIndex = Text.Builder.decimal - . Prelude.length - . filter ((< offset) . fromIntegral) - . IntMap.keys - summary = "Encoding " <> Text.Builder.decimal index - <> ". PlatformID: " <> Text.Builder.decimal platformID <> newlineBuilder - <> " EcodingID: " <> Text.Builder.decimal platformSpecificID <> newlineBuilder - <> " SubTable: " <> findSubTableIndex subtables - <> ", Offset: " <> paddedHexadecimal offset <> newlineBuilder - in (pred index, summary <> newlineBuilder <> accumulator) - dumpCmapSubTable currentSubTable (index, accumulator) = - let contents = "SubTable " <> Text.Builder.decimal index - <> ". " <> dumpCmapSubTableFormat currentSubTable - in (pred index, contents <> accumulator) - dumpCmapSubTableFormat = \case - (CmapFormat0 _) -> "Format 0" - (CmapFormat2 _) -> "Format 2" - (CmapFormat4 CmapFormat4Table{..}) -> - let segCount = Vector.length startCode - dumpSegment' = dumpSegment segCount glyphIndexArray - in "Format 4 - Segment mapping to delta values\n\ - \ Length: 994\n\ - \ Version: 0\n\ - \ segCount: " - <> Text.Builder.decimal segCount - <> newlineBuilder <> " searchRange: " - <> Text.Builder.decimal searchRange - <> newlineBuilder <> " entrySelector: " - <> Text.Builder.decimal entrySelector - <> newlineBuilder <> " rangeShift: " - <> Text.Builder.decimal (segCount * 2 - fromIntegral searchRange) - <> newlineBuilder - <> fold (Vector.izipWith4 (dumpSegmentSummary segCount) startCode endCode idDelta idRangeOffset) - <> " Number of glyphIndex " - <> Text.Builder.decimal (Vector.length glyphIndexArray) <> newlineBuilder - <> fold (Vector.imap dumpGlyphAtIndex glyphIndexArray) - <> fold (Vector.izipWith4 dumpSegment' startCode endCode idDelta idRangeOffset) - (CmapFormat6 _) -> "Format 6" - (CmapFormat8 _) -> "Format 8" - (CmapFormat10 _) -> "Format 10" - (CmapFormat12 _) -> "Format 12" - (CmapFormat13 _) -> "Format 13" - (CmapFormat14 _) -> "Format 14" - dumpSegment :: Int -> Vector Word16 -> Int -> Word16 -> Word16 -> Word16 -> Word16 -> Text.Builder.Builder - dumpSegment segCount glyphIndexArray' segmentIndex startCode' endCode' idDelta' idRangeOffset' = - let charRange = [startCode'..endCode'] - dumpSegmentCharIndex' = - dumpSegmentCharIndex segCount glyphIndexArray' segmentIndex idDelta' idRangeOffset' startCode' - in "Segment " <> Text.Builder.decimal segmentIndex <> ":\n" - <> foldMap dumpSegmentCharIndex' charRange - dumpSegmentCharIndex segCount glyphIndexArray' segmentIndex idDelta' idRangeOffset' startCode' charCode = - let calculateGlyphIndex' = - calculateGlyphIndex charCode segmentIndex segCount glyphIndexArray' idRangeOffset' idDelta' startCode' - in " Char 0x" - <> halfPaddedHexadecimal charCode <> " -> Index " - <> Text.Builder.decimal calculateGlyphIndex' - <> newlineBuilder - dumpSegmentSummary segCount index startCode' endCode' idDelta' idRangeOffset' - = " Seg " <> justifyNumber 5 index - <> " : St = " <> halfPaddedHexadecimal startCode' - <> ", En = " <> halfPaddedHexadecimal endCode' - <> ", D = " <> justifyNumber 6 idDelta' - <> ", RO = " <> justifyNumber 6 idRangeOffset' - <> ", gId# = " <> dumpGlyphId index segCount idRangeOffset' - <> newlineBuilder - dumpGlyphId segmentIndex segCount idRangeOffset' - = maybe "N/A" Text.Builder.decimal - $ calculateGlyphId segmentIndex segCount idRangeOffset' - calculateGlyphIndex :: Word16 -> Int -> Int -> Vector Word16 -> Word16 -> Word16 -> Word16 -> Int - calculateGlyphIndex c segmentIndex segCount glyphIndexArray' idRangeOffset' idDelta' startCode' = - let defaultIndex = fromIntegral $ c + idDelta' - addOffset = fromIntegral - . fromMaybe 0 - . (glyphIndexArray' Vector.!?) - . (+ fromIntegral (c - startCode')) - in maybe defaultIndex addOffset - $ calculateGlyphId segmentIndex segCount idRangeOffset' - calculateGlyphId segmentIndex segCount idRangeOffset' - | idRangeOffset' == 0 = Nothing - | otherwise = Just $ segmentIndex - segCount + (fromIntegral idRangeOffset' `div` 2) - dumpGlyphAtIndex index element = " glyphIdArray[" <> Text.Builder.decimal index <> "] = " - <> Text.Builder.decimal element <> newlineBuilder - -dumpLoca :: LocaTable -> Text.Builder.Builder -dumpLoca table = - dumpCaption "'loca' Table - Index to Location" - <> go table - where - go (LongLocaTable elements) = dumpElements elements - go (ShortLocaTable elements) = dumpElements - $ (* 2) - . (fromIntegral :: Word16 -> Word32) - <$> elements - dumpElements elements = - case Vector.unsnoc elements of - Just (init', last') - -> foldMap dumpLocaLine (Vector.indexed init') - <> " Ended at " <> paddedHexadecimal last' <> newlineBuilder - Nothing -> mempty - dumpLocaLine :: Integral a => (Int, a) -> Text.Builder.Builder - dumpLocaLine (index, element) - = " Idx " <> justifyNumber 6 index - <> " -> GlyphOffset " <> paddedHexadecimal element <> newlineBuilder - -dumpName :: NameTable -> Text.Builder.Builder -dumpName table'@NameTable{..} = dumpCaption "'name' Table - Naming Table" - <> " Format: " <> Text.Builder.decimal format <> newlineBuilder - <> " Number of Record: " <> Text.Builder.decimal (Prelude.length nameRecord) <> newlineBuilder - <> " Storage offset: " <> Text.Builder.decimal (nameStringOffset table') <> newlineBuilder - <> foldMap go (zip3 [0 :: Int ..] nameRecord variable) - where - go (index, NameRecord{ length = length', ..}, variable') - = "Name table " <> justifyNumber 3 index <> "." - <> " PlatformID: " <> Text.Builder.decimal platformID <> newlineBuilder - <> " EncodingID: " <> Text.Builder.decimal platformSpecificID <> newlineBuilder - <> " LanguageID: " <> Text.Builder.decimal languageID <> newlineBuilder - <> " NameID: " <> Text.Builder.decimal nameID <> newlineBuilder - <> " Length: " <> Text.Builder.decimal length' <> newlineBuilder - <> " Offset: " <> Text.Builder.decimal offset <> newlineBuilder - <> foldMap (" " <>) (dumpHexString $ ByteString.unpack variable') - -dumpHexString :: [Word8] -> [Text.Builder.Builder] -dumpHexString byteCodes - | null byteCodes = [dumpHexLine " > " byteCodes] - | Prelude.length byteCodes < 10 = [dumpHexLine " > " byteCodes] - | otherwise = dumpHexLine " > " byteCodes - : dumpHexString (drop 10 byteCodes) - where - dumpHexLine separator variable' = - let firstTen = take 10 variable' - digits = fold $ intersperse (Text.Builder.singleton ' ') $ hexByte <$> firstTen - printables = foldMap printableByte firstTen - in digits - <> Text.Builder.fromText (Text.replicate (10 - Prelude.length firstTen) " ") - <> separator - <> printables - <> newlineBuilder - printableByte :: Word8 -> Text.Builder.Builder - printableByte code - | code >= 0x20 - , code < 0x7f = Text.Builder.singleton $ toEnum $ fromIntegral code - | otherwise = Text.Builder.singleton '.' - -hexByte :: Integral a => a -> Text.Builder.Builder -hexByte = Text.Builder.fromLazyText - . Text.Lazy.justifyRight 2 '0' - . Text.Builder.toLazyText - . Text.Builder.hexadecimal - -dumpMaxp :: MaxpTable -> Text.Builder.Builder -dumpMaxp (TrueMaxp TrueMaxpTable{..}) - = dumpCaption "'maxp' Table - Maximum Profile" - <> " 'maxp' version: " <> dumpFixed32 version <> newlineBuilder - <> " numGlyphs: " <> Text.Builder.decimal numGlyphs <> newlineBuilder - <> " maxPoints: " <> Text.Builder.decimal maxPoints <> newlineBuilder - <> " maxContours: " <> Text.Builder.decimal maxContours <> newlineBuilder - <> " maxCompositePoints: " <> Text.Builder.decimal maxComponentPoints <> newlineBuilder - <> " maxCompositeContours: " <> Text.Builder.decimal maxComponentContours <> newlineBuilder - <> " maxZones: " <> Text.Builder.decimal maxZones <> newlineBuilder - <> " maxTwilightPoints: " <> Text.Builder.decimal maxTwilightPoints <> newlineBuilder - <> " maxStorage: " <> Text.Builder.decimal maxStorage <> newlineBuilder - <> " maxFunctionDefs: " <> Text.Builder.decimal maxFunctionDefs <> newlineBuilder - <> " maxInstructionDefs: " <> Text.Builder.decimal maxInstructionDefs <> newlineBuilder - <> " maxStackElements: " <> Text.Builder.decimal maxStackElements <> newlineBuilder - <> " maxSizeOfInstructions: " <> Text.Builder.decimal maxSizeOfInstructions <> newlineBuilder - <> " maxComponentElements: " <> Text.Builder.decimal maxComponentElements <> newlineBuilder - <> " maxCompoenetDepth: " <> Text.Builder.decimal maxComponentDepth <> newlineBuilder -dumpMaxp (OpenMaxp OpenMaxpTable{..}) - = dumpCaption "'maxp' Table - Maximum Profile" - <> " 'maxp' version: " <> dumpFixed32 version <> newlineBuilder <> newlineBuilder - <> " numGlyphs: " <> Text.Builder.decimal numGlyphs <> newlineBuilder - -dumpGASP :: GASPTable -> Text.Builder.Builder -dumpGASP GASPTable{..} = dumpCaption "'gasp' Table - Grid-fitting And Scan-conversion Procedure" - <> "'gasp' version: " <> Text.Builder.decimal version <> newlineBuilder - <> "numRanges: " <> Text.Builder.decimal (Prelude.length gaspRange) <> newlineBuilder - <> foldMap dumpGASPRange (zip [0..] gaspRange) - where - dumpGASPRange :: (Int, GASPRange) -> Text.Builder.Builder - dumpGASPRange (index', GASPRange{..}) = newlineBuilder - <> " gasp Range " <> Text.Builder.decimal index' <> newlineBuilder - <> " rangeMaxPPEM: " <> Text.Builder.decimal rangeMaxPPEM <> newlineBuilder - <> " rangeGaspBehavior: 0x" <> halfPaddedHexadecimal rangeGaspBehavior <> newlineBuilder - -dumpGlyf :: GlyfTable -> Text.Builder.Builder -dumpGlyf (GlyfTable glyfDescriptions) = dumpCaption "'glyf' Table - Glyf data" - <> foldMap go (Vector.indexed glyfDescriptions) - where - go (glyphIndex, GlyphDescription{..}) - = "Glyph " <> justifyNumber 6 glyphIndex <> Text.Builder.singleton '.' <> newlineBuilder - <> " numberOfContours: " <> Text.Builder.decimal numberOfContours <> newlineBuilder - <> " xMin: " <> Text.Builder.decimal xMin <> newlineBuilder - <> " yMin: " <> Text.Builder.decimal yMin <> newlineBuilder - <> " xMax: " <> Text.Builder.decimal xMax <> newlineBuilder - <> " yMax: " <> Text.Builder.decimal yMax <> newlineBuilder - <> newlineBuilder <> dumpGlyphDefinition definition <> newlineBuilder - dumpEndPoint (endPointIndex, endPoint) - = " " <> justifyNumber 2 endPointIndex - <> ": " <> Text.Builder.decimal endPoint <> newlineBuilder - dumpGlyphDefinition (SimpleGlyph SimpleGlyphDefinition{..}) - = " EndPoints" <> newlineBuilder - <> " ---------" <> newlineBuilder - <> foldMap dumpEndPoint (Vector.indexed endPtsOfContours) <> newlineBuilder - <> " Length of Instructions: " - <> Text.Builder.decimal (Vector.length instructions) <> newlineBuilder - <> newlineBuilder <> " Flags" <> newlineBuilder - <> " -----" <> newlineBuilder - <> fst (Vector.foldl' foldFlag ("", 0) flags) <> newlineBuilder - <> " Coordinates" <> newlineBuilder - <> " -----------" <> newlineBuilder - <> fst (Vector.ifoldl' foldCoordinate mempty coordinates) - dumpGlyphDefinition (CompoundGlyph CompoundGlyphDefinition{..}) - = foldMap (dumpCompoundGlyph $ Vector.length components) (Vector.indexed components) - <> newlineBuilder <> " Length of Instructions: " - <> Text.Builder.decimal (Vector.length instructions) <> newlineBuilder - dumpCompoundGlyph :: Int -> (Int, ComponentGlyphPartDescription) -> Text.Builder.Builder - dumpCompoundGlyph componentsLength (componentIndex, description) = - let moreComponents = succ componentIndex < componentsLength - compoundFlags = dumpCompoundFlags moreComponents description - ComponentGlyphPartDescription{..} = description - in " " <> Text.Builder.decimal componentIndex - <> ": Flags: 0x" <> compoundFlags <> newlineBuilder - <> " Glyf Index: " <> Text.Builder.decimal glyphIndex <> newlineBuilder - <> " X" <> dumpArgument argument1 <> newlineBuilder - <> " Y" <> dumpArgument argument2 <> newlineBuilder - <> dumpTransformationOption transformationOption - <> " Others: " <> dumpOtherFlags flags <> newlineBuilder - <> newlineBuilder -- TODO - dumpTransformationOption GlyphNoScale = "" - dumpTransformationOption (GlyphScale scale) = - " X,Y Scale: " <> Text.Builder.realFloat (fixed2Double scale) <> newlineBuilder - dumpTransformationOption (GlyphXyScale xScale yScale) - = " X Scale: " <> Text.Builder.realFloat (fixed2Double xScale) <> newlineBuilder - <> " Y Scale: " <> Text.Builder.realFloat (fixed2Double yScale) <> newlineBuilder - dumpTransformationOption (Glyph2By2Scale xScale scale01 scale10 yScale) - = " X Scale: " <> Text.Builder.realFloat (fixed2Double xScale) <> newlineBuilder - <> " X,Y Scale: " <> Text.Builder.realFloat (fixed2Double scale01) <> newlineBuilder - <> " Y,X Scale: " <> Text.Builder.realFloat (fixed2Double scale10) <> newlineBuilder - <> " Y Scale: " <> Text.Builder.realFloat (fixed2Double yScale) <> newlineBuilder - dumpOtherFlags ComponentGlyphFlags{..} = - let roundXyToGridText = if roundXyToGrid then "Round X,Y to Grid " else " " - useMyMetricsText = if useMyMetrics then "Use My Metrics " else " " - overlapCompoundText = if overlapCompound then "Overlap " else " " - in roundXyToGridText <> overlapCompoundText <> useMyMetricsText - dumpCompoundFlags :: Bool -> ComponentGlyphPartDescription -> Text.Builder.Builder - dumpCompoundFlags moreComponents ComponentGlyphPartDescription{..} = - let setBits = glyphArgumentBits argument1 - <> componentFlagBits flags - <> transformationOptionBits transformationOption - setBits' = if moreComponents then 5 : setBits else setBits - in Text.Builder.hexadecimal - $ foldr (flip setBit) (zeroBits :: Word16) setBits' - dumpArgument (GlyphInt8Argument argument) = - " BOffset: " <> Text.Builder.decimal argument - dumpArgument (GlyphInt16Argument argument) = - " WOffset: " <> Text.Builder.decimal argument - dumpArgument (GlyphWord8Argument argument) = - " BPoint: " <> Text.Builder.decimal argument - dumpArgument (GlyphWord16Argument argument) = - " WPoint: " <> Text.Builder.decimal argument - glyphArgumentBits (GlyphInt16Argument _) = [0, 1] - glyphArgumentBits (GlyphWord16Argument _) = [0] - glyphArgumentBits (GlyphInt8Argument _) = [1] - glyphArgumentBits (GlyphWord8Argument _) = [] - componentFlagBits ComponentGlyphFlags{..} = catMaybes - [ if roundXyToGrid then Just 2 else Nothing - , if weHaveInstructions then Just 8 else Nothing - , if useMyMetrics then Just 9 else Nothing - , if overlapCompound then Just 10 else Nothing - ] - transformationOptionBits GlyphScale{} = [3] - transformationOptionBits GlyphXyScale{} = [6] - transformationOptionBits Glyph2By2Scale{} = [7] - transformationOptionBits GlyphNoScale = [] - dumpFlag lineValue coordinateIndex - = " " <> justifyNumber 2 coordinateIndex <> lineValue - foldFlag :: (Text.Builder.Builder, Int) -> OutlineFlag -> (Text.Builder.Builder, Int) - foldFlag (accumulator, coordinateIndex) OutlineFlag{..} = - let lineValue = ": " - <> (if thisYIsSame then "YDual " else " ") - <> (if thisXIsSame then "XDual " else " ") - <> (if repeat > 0 then "Repeat " else " ") - <> (if yShortVector then "Y-Short " else " ") - <> (if xShortVector then "X-Short " else " ") - <> (if onCurve then "On" else "Off") - <> newlineBuilder - repeatN = succIntegral repeat - repeatedLines = fold - $ Vector.cons accumulator - $ dumpFlag lineValue - <$> Vector.enumFromN coordinateIndex repeatN - in (repeatedLines, coordinateIndex + repeatN) - foldCoordinate - :: (Text.Builder.Builder, GlyphCoordinate) - -> Int - -> GlyphCoordinate - -> (Text.Builder.Builder, GlyphCoordinate) - foldCoordinate (accumulator, absCoordinate) coordinateIndex relCoordinate = - let nextAbs = relCoordinate <> absCoordinate - newLine = " " <> justifyNumber 2 coordinateIndex - <> " Rel " <> dumpCoordinate relCoordinate - <> " -> Abs " <> dumpCoordinate nextAbs - <> newlineBuilder - in (accumulator <> newLine, nextAbs) - dumpCoordinate GlyphCoordinate{..} - = "(" <> justifyNumber 7 coordinateX <> ", " - <> justifyNumber 7 coordinateY <> ")" - -dumpTables - :: Megaparsec.State ByteString Void - -> FontDirectory - -> Either DumpError Text.Builder.Builder -dumpTables processedState directory@FontDirectory{..} - = parseRequired >>= traverseDirectory - where - traverseDirectory parsedRequired = - let initial = Right $ dumpOffsetTable directory - in foldl' (go parsedRequired) initial tableDirectory - parseRequired = do - requiredHhea <- findRequired "hhea" hheaTableP - requiredHead@HeadTable{ indexToLocFormat } <- - findRequired "head" headTableP - requiredLoca <- findRequired "loca" (locaTableP indexToLocFormat) - pure $ RequiredTables - { hheaTable = requiredHhea - , headTable = requiredHead - , locaTable = requiredLoca - } - findRequired tableName parser = - let missingError = Left $ DumpRequiredTableMissingError tableName - parseFound tableEntry = parseTable tableEntry parser processedState - in maybe missingError (first DumpParseError . parseFound) - $ find ((== Char8.pack tableName) . getField @"tag") tableDirectory - go _ (Left accumulator) _ = Left accumulator - go parsedRequired (Right accumulator) tableEntry - = maybe (Right accumulator) (concatDump accumulator . first DumpParseError) - $ dumpSubTable parsedRequired tableEntry - concatDump accumulator builderDump = ((accumulator <> newlineBuilder) <>) - <$> builderDump - dumpSubTable RequiredTables{..} tableEntry = - case getField @"tag" tableEntry of - "cmap" -> Just $ dumpCmap <$> parseTable tableEntry cmapTableP processedState - "head" -> Just $ Right $ dumpHead headTable - "hhea" -> Just $ Right $ dumpHhea hheaTable - "hmtx" -> Just $ dumpHmtx - <$> parseTable tableEntry (hmtxTableP $ getField @"numOfLongHorMetrics" hheaTable) processedState - "loca" -> Just $ Right $ dumpLoca locaTable - "maxp" -> Just $ dumpMaxp <$> parseTable tableEntry maxpTableP processedState - "name" -> Just $ dumpName <$> parseTable tableEntry nameTableP processedState - "post" -> Just $ dumpPost <$> parseTable tableEntry postTableP processedState - "OS/2" -> Just $ dumpOs2 <$> parseTable tableEntry os2TableP processedState - "cvt " -> Just $ dumpCVTable <$> parseTable tableEntry cvTableP processedState - "gasp" -> Just $ dumpGASP <$> parseTable tableEntry gaspTableP processedState - "glyf" -> Just $ dumpGlyf <$> parseTable tableEntry (glyfTableP locaTable) processedState - _ -> Nothing - -dumpTrueType :: ByteString -> FilePath -> Either DumpError Text.Builder.Builder -dumpTrueType ttfContents fontFile = - let initialState = Megaparsec.State - { stateInput = ttfContents - , stateOffset = 0 - , statePosState = Megaparsec.PosState - { pstateInput = ttfContents - , pstateOffset = 0 - , pstateSourcePos = Megaparsec.initialPos fontFile - , pstateTabWidth = Megaparsec.defaultTabWidth - , pstateLinePrefix = "" - } - , stateParseErrors = [] - } - (processedState, initialResult) = Megaparsec.runParser' fontDirectoryP initialState - - in first DumpParseError initialResult >>= dumpTables processedState |
