Extract metrics from the OS/2 table

This commit is contained in:
Eugen Wissner 2024-02-12 14:05:39 +01:00
parent 41b5c14e2f
commit ca70d648a9
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
2 changed files with 46 additions and 4 deletions

View File

@ -5,6 +5,7 @@
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE PatternSynonyms #-}
module Graphics.Fountainhead.Metrics module Graphics.Fountainhead.Metrics
( FontBBox(..) ( FontBBox(..)
@ -24,9 +25,14 @@ import Graphics.Fountainhead.TrueType
, HheaTable(..) , HheaTable(..)
, NameRecord(..) , NameRecord(..)
, NameTable(..) , NameTable(..)
, Os2BaseFields(..)
, Os2Version4Fields(..)
, Os2Version5Fields(..)
, Os2Table(..)
, PostHeader(..) , PostHeader(..)
, PostTable(..) , PostTable(..)
, findTableByTag , findTableByTag
, pattern Os2Version4CommonFields
) )
import Graphics.Fountainhead.Parser import Graphics.Fountainhead.Parser
( ParseErrorBundle ( ParseErrorBundle
@ -35,14 +41,14 @@ import Graphics.Fountainhead.Parser
, parseTable , parseTable
, headTableP , headTableP
, hheaTableP , hheaTableP
, os2TableP
, postTableP , postTableP
) )
import qualified Text.Megaparsec as Megaparsec import qualified Text.Megaparsec as Megaparsec
import Data.Bifunctor (Bifunctor(..)) import Data.Bifunctor (Bifunctor(..))
import Data.Int (Int16, Int32) import Data.Int (Int16)
import Data.Word (Word16) import Data.Word (Word16)
import GHC.Records (HasField(..)) import GHC.Records (HasField(..))
import Graphics.Fountainhead.Type (Fixed32(..))
type Number = Float type Number = Float
@ -99,6 +105,7 @@ data MetricsError
= MetricsParseError ParseErrorBundle = MetricsParseError ParseErrorBundle
| MetricsRequiredTableMissingError String | MetricsRequiredTableMissingError String
| MetricsNameRecordNotFound Word16 | MetricsNameRecordNotFound Word16
| UnsupportedOs2VersionError
deriving Eq deriving Eq
instance Show MetricsError instance Show MetricsError
@ -108,6 +115,8 @@ instance Show MetricsError
"Required table " <> tableName <> " is missing." "Required table " <> tableName <> " is missing."
show (MetricsNameRecordNotFound nameId) = show (MetricsNameRecordNotFound nameId) =
"Name record with ID " <> show nameId <> " was not found." "Name record with ID " <> show nameId <> " was not found."
show UnsupportedOs2VersionError =
"OS/2 version 1 does not contain cap height."
collectMetrics :: FilePath -> ByteString -> Either MetricsError FontDescriptor collectMetrics :: FilePath -> ByteString -> Either MetricsError FontDescriptor
collectMetrics fontFile ttfContents = collectMetrics fontFile ttfContents =
@ -138,18 +147,32 @@ collectMetrics fontFile ttfContents =
PostTable{ postHeader } <- first MetricsParseError PostTable{ postHeader } <- first MetricsParseError
$ parseTable postEntry postTableP processedState $ parseTable postEntry postTableP processedState
(capHeight, weightClass) <- getCapHeight processedState initialResult
pure $ FontDescriptor pure $ FontDescriptor
{ fontName = variableText nameRecord variable psNameIndex { fontName = variableText nameRecord variable psNameIndex
, flags = [] , flags = []
, stemV = 1 , stemV = calculateStemV $ fromIntegral weightClass
, missingWidth = 0 , missingWidth = 0
, fontBBox = calculateBoundingBox scale headTable , fontBBox = calculateBoundingBox scale headTable
, italicAngle = realToFrac $ getField @"italicAngle" postHeader , italicAngle = realToFrac $ getField @"italicAngle" postHeader
, capHeight = 0 , capHeight = fromIntegral $ scalePs scale capHeight
, ascender = fromIntegral $ scalePs scale ascent , ascender = fromIntegral $ scalePs scale ascent
, descender = fromIntegral $ scalePs scale descent , descender = fromIntegral $ scalePs scale descent
} }
where where
calculateStemV weightClass = 10 + 220 * (weightClass - 50) / 900
getCapHeight processedState initialResult = do
os2Entry <- maybeMetricsError (MetricsRequiredTableMissingError "OS/2")
$ findTableByTag "OS/2" initialResult
os2Table <- first MetricsParseError
$ parseTable os2Entry os2TableP processedState
case os2Table of
Os2Version4CommonFields Os2BaseFields{ usWeightClass } Os2Version4Fields{ sCapHeight } ->
Right (sCapHeight, usWeightClass)
Os2Version5 Os2BaseFields{ usWeightClass } _ Os2Version5Fields{ sCapHeight } ->
Right (sCapHeight, usWeightClass)
_ -> Left UnsupportedOs2VersionError
calculateBoundingBox scale HeadTable{ xMin, xMax, yMin, yMax } = calculateBoundingBox scale HeadTable{ xMin, xMax, yMin, yMax } =
let xMin' = fromIntegral $ scalePs scale xMin let xMin' = fromIntegral $ scalePs scale xMin
yMin' = fromIntegral $ scalePs scale yMin yMin' = fromIntegral $ scalePs scale yMin

View File

@ -4,7 +4,10 @@
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}
-- | Types representing a TrueType font. -- | Types representing a TrueType font.
module Graphics.Fountainhead.TrueType module Graphics.Fountainhead.TrueType
@ -85,6 +88,7 @@ module Graphics.Fountainhead.TrueType
, findTableByTag , findTableByTag
, unLocaTable , unLocaTable
, nameStringOffset , nameStringOffset
, pattern Os2Version4CommonFields
) where ) where
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
@ -545,6 +549,21 @@ data Os2Table
| Os2Version5 Os2BaseFields Os2MicrosoftFields Os2Version5Fields | Os2Version5 Os2BaseFields Os2MicrosoftFields Os2Version5Fields
deriving (Eq, Show) deriving (Eq, Show)
pattern Os2Version4CommonFields :: Os2BaseFields -> Os2Version4Fields -> Os2Table
pattern Os2Version4CommonFields baseFields versionFields <-
(os2Version4CommonFields -> Just (baseFields, versionFields))
{-# COMPLETE Os2Version4CommonFields, Os2Version0, Os2Version1, Os2Version5 #-}
os2Version4CommonFields :: Os2Table -> Maybe (Os2BaseFields, Os2Version4Fields)
os2Version4CommonFields = \case
Os2Version0{} -> Nothing
Os2Version1{} -> Nothing
Os2Version2 baseFields _ versionFields -> Just (baseFields, versionFields)
Os2Version3 baseFields _ versionFields -> Just (baseFields, versionFields)
Os2Version4 baseFields _ versionFields -> Just (baseFields, versionFields)
Os2Version5{} -> Nothing
data Os2Version1Fields = Os2Version1Fields data Os2Version1Fields = Os2Version1Fields
{ ulCodePageRange1 :: Word32 { ulCodePageRange1 :: Word32
, ulCodePageRange2 :: Word32 , ulCodePageRange2 :: Word32