opentype-0.0.1: Opentype/Fileformat/Name.hs
module Opentype.Fileformat.Name
where
import Opentype.Fileformat.Types
import Data.List (sort)
import Data.Word
import Control.Monad
import Data.Binary.Put
import Data.Foldable (for_, traverse_)
import Data.Traversable (for)
import qualified Data.ByteString as Strict
-- | This table allows multilingual strings to be associated with the
-- OpenType™ font file. These strings can represent copyright notices,
-- font names, family names, style names, and so on. To keep this
-- table short, the font manufacturer may wish to make a limited set
-- of entries in some small set of languages; later, the font can be
-- “localized” and the strings translated or added. Other parts of the
-- OpenType font file that require these strings can then refer to
-- them simply by their index number. Clients that need a particular
-- string can look it up by its platform ID, character encoding ID,
-- language ID and name ID. Note that some platforms may require
-- single byte character strings, while others may require double byte
-- strings.
--
-- For historical reasons, some applications which install fonts
-- perform version control using Macintosh platform (platform ID 1)
-- strings from the 'name' table. Because of this, it is strongly
-- recommended that the 'name' table of all fonts include Macintosh
-- platform strings and that the syntax of the version number (name id
-- 5) follows the guidelines given in the opentype specification.
--
-- The encoding for each bytestring depends on the PlatformID and
-- encodingID. This library doesn't do any conversion. For more
-- information see the opentype specification:
-- https://www.microsoft.com/typography/otspec/name.htm
data NameTable = NameTable [NameRecord]
deriving Show
data NameRecord = NameRecord {
namePlatform :: PlatformID,
nameEncoding :: Word16,
nameLanguage :: Word16,
nameID :: Word16,
nameString :: Strict.ByteString}
deriving Show
instance Ord NameRecord where
compare (NameRecord pID eID lang nID _)
(NameRecord pID2 eID2 lang2 nID2 _) =
compare (pID, eID, lang, nID) (pID2, eID2, lang2, nID2)
instance Eq NameRecord where
(NameRecord pID eID lang nID _) ==
(NameRecord pID2 eID2 lang2 nID2 _) =
(pID, eID, lang, nID) == (pID2, eID2, lang2, nID2)
putNameTable :: NameTable -> Put
putNameTable (NameTable records_) = do
putWord16be 0
putWord16be $ fromIntegral len
putWord16be $ fromIntegral $ len * 12 + 6
for_ (zip offsets records) $ \(offset, r) -> do
putPf $ namePlatform r
putWord16be $ nameEncoding r
putWord16be $ nameLanguage r
putWord16be $ nameID r
putWord16be $ fromIntegral $ Strict.length $ nameString r
putWord16be offset
traverse_ (putByteString.nameString) records
where len = length records
records = sort records_
lengths = map (fromIntegral . Strict.length . nameString) records
offsets = scanl (+) 0 lengths
readNameTable :: Strict.ByteString -> Either String NameTable
readNameTable bs = do
version <- index16 bs 0
when (version > 0) $ fail "Unsupported name table format."
len <- index16 bs 1
storage <- index16 bs 2
records <- for [0..len-1] $ \i -> do
pf <- toPf =<< index16 bs (3 + i*6)
enc <- index16 bs $ 3 + i*6 + 1
lang <- index16 bs $ 3 + i*6 + 2
nID <- index16 bs $ 3 + i*6 + 3
len2 <- index16 bs $ 3 + i*6 + 4
offset <- index16 bs $ 3 + i*6 + 5
Right (offset, len2, NameRecord pf enc lang nID)
records2 <- for records $
\(offset, len2, r) ->
if storage+offset+len2 > fromIntegral (Strict.length bs)
then Left "string storage bounds exceeded"
else Right $ r (Strict.take (fromIntegral len2) $
Strict.drop (fromIntegral $ offset+storage) bs)
return $ NameTable records2