2020-05-14 17:04:34 +01:00
""" cffLib: read/write Adobe CFF fonts
2024-09-17 17:23:44 +01:00
OpenType fonts with PostScript outlines embed a completely independent
font file in Adobe ' s *Compact Font Format*. So dealing with OpenType fonts
2020-05-14 17:04:34 +01:00
requires also dealing with CFF . This module allows you to read and write
fonts written in the CFF format .
In 2016 , OpenType 1.8 introduced the ` CFF2 < https : / / docs . microsoft . com / en - us / typography / opentype / spec / cff2 > ` _
format which , along with other changes , extended the CFF format to deal with
the demands of variable fonts . This module parses both original CFF and CFF2 .
"""
1999-12-16 21:34:53 +00:00
2013-09-17 16:59:39 -04:00
from fontTools . misc import sstruct
2000-01-16 22:14:02 +00:00
from fontTools . misc import psCharStrings
2017-05-19 16:55:10 +09:00
from fontTools . misc . arrayTools import unionRect , intRect
2022-12-13 11:26:36 +00:00
from fontTools . misc . textTools import (
bytechr ,
byteord ,
bytesjoin ,
tobytes ,
tostr ,
safeEval ,
)
2017-01-12 15:23:12 -08:00
from fontTools . ttLib import TTFont
2017-06-21 10:32:58 +01:00
from fontTools . ttLib . tables . otBase import OTTableWriter
from fontTools . ttLib . tables . otBase import OTTableReader
2017-01-12 15:23:12 -08:00
from fontTools . ttLib . tables import otTables as ot
2021-03-29 11:45:58 +02:00
from io import BytesIO
2013-11-27 17:27:45 -05:00
import struct
2016-01-24 14:50:57 +00:00
import logging
2017-01-12 15:23:12 -08:00
import re
2016-01-24 14:50:57 +00:00
# mute cffLib debug messages when running ttx in verbose mode
DEBUG = logging . DEBUG - 1
log = logging . getLogger ( __name__ )
2002-05-17 07:06:32 +00:00
1999-12-16 21:34:53 +00:00
cffHeaderFormat = """
major : B
minor : B
hdrSize : B
"""
2017-06-21 10:32:58 +01:00
maxStackLimit = 513
2017-05-16 19:32:27 -07:00
# maxstack operator has been deprecated. max stack is now always 513.
2017-06-21 10:32:58 +01:00
2013-11-28 14:26:58 -05:00
class CFFFontSet ( object ) :
2022-12-13 11:26:36 +00:00
""" A CFF font " file " can contain more than one font, although this is
extremely rare ( and not allowed within OpenType fonts ) .
This class is the entry point for parsing a CFF table . To actually
manipulate the data inside the CFF font , you will want to access the
` ` CFFFontSet ` ` ' s :class:`TopDict` object. To do this, a ``CFFFontSet``
object can either be treated as a dictionary ( with appropriate
` ` keys ( ) ` ` and ` ` values ( ) ` ` methods ) mapping font names to : class : ` TopDict `
objects , or as a list .
. . code : : python
from fontTools import ttLib
tt = ttLib . TTFont ( " Tests/cffLib/data/LinLibertine_RBI.otf " )
tt [ " CFF " ] . cff
# <fontTools.cffLib.CFFFontSet object at 0x101e24c90>
tt [ " CFF " ] . cff [ 0 ] # Here's your actual font data
# <fontTools.cffLib.TopDict object at 0x1020f1fd0>
"""
def decompile ( self , file , otFont , isCFF2 = None ) :
""" Parse a binary CFF file into an internal representation. ``file``
should be a file handle object . ` ` otFont ` ` is the top - level
: py : class : ` fontTools . ttLib . ttFont . TTFont ` object containing this CFF file .
If ` ` isCFF2 ` ` is passed and set to ` ` True ` ` or ` ` False ` ` , then the
library makes an assertion that the CFF header is of the appropriate
version .
"""
self . otFont = otFont
sstruct . unpack ( cffHeaderFormat , file . read ( 3 ) , self )
if isCFF2 is not None :
# called from ttLib: assert 'major' as read from file matches the
# expected version
expected_major = 2 if isCFF2 else 1
if self . major != expected_major :
raise ValueError (
" Invalid CFF ' major ' version: expected %d , found %d "
% ( expected_major , self . major )
)
else :
# use 'major' version from file to determine if isCFF2
assert self . major in ( 1 , 2 ) , " Unknown CFF format "
isCFF2 = self . major == 2
if not isCFF2 :
self . offSize = struct . unpack ( " B " , file . read ( 1 ) ) [ 0 ]
file . seek ( self . hdrSize )
self . fontNames = list ( tostr ( s ) for s in Index ( file , isCFF2 = isCFF2 ) )
self . topDictIndex = TopDictIndex ( file , isCFF2 = isCFF2 )
self . strings = IndexedStrings ( file )
else : # isCFF2
self . topDictSize = struct . unpack ( " >H " , file . read ( 2 ) ) [ 0 ]
file . seek ( self . hdrSize )
self . fontNames = [ " CFF2Font " ]
cff2GetGlyphOrder = otFont . getGlyphOrder
# in CFF2, offsetSize is the size of the TopDict data.
self . topDictIndex = TopDictIndex (
file , cff2GetGlyphOrder , self . topDictSize , isCFF2 = isCFF2
)
self . strings = None
self . GlobalSubrs = GlobalSubrsIndex ( file , isCFF2 = isCFF2 )
self . topDictIndex . strings = self . strings
self . topDictIndex . GlobalSubrs = self . GlobalSubrs
def __len__ ( self ) :
return len ( self . fontNames )
def keys ( self ) :
return list ( self . fontNames )
def values ( self ) :
return self . topDictIndex
def __getitem__ ( self , nameOrIndex ) :
""" Return TopDict instance identified by name (str) or index (int
or any object that implements ` __index__ ` ) .
"""
if hasattr ( nameOrIndex , " __index__ " ) :
index = nameOrIndex . __index__ ( )
elif isinstance ( nameOrIndex , str ) :
name = nameOrIndex
try :
index = self . fontNames . index ( name )
except ValueError :
raise KeyError ( nameOrIndex )
else :
raise TypeError ( nameOrIndex )
return self . topDictIndex [ index ]
def compile ( self , file , otFont , isCFF2 = None ) :
""" Write the object back into binary representation onto the given file.
` ` file ` ` should be a file handle object . ` ` otFont ` ` is the top - level
: py : class : ` fontTools . ttLib . ttFont . TTFont ` object containing this CFF file .
If ` ` isCFF2 ` ` is passed and set to ` ` True ` ` or ` ` False ` ` , then the
library makes an assertion that the CFF header is of the appropriate
version .
"""
self . otFont = otFont
if isCFF2 is not None :
# called from ttLib: assert 'major' value matches expected version
expected_major = 2 if isCFF2 else 1
if self . major != expected_major :
raise ValueError (
" Invalid CFF ' major ' version: expected %d , found %d "
% ( expected_major , self . major )
)
else :
# use current 'major' value to determine output format
assert self . major in ( 1 , 2 ) , " Unknown CFF format "
isCFF2 = self . major == 2
if otFont . recalcBBoxes and not isCFF2 :
for topDict in self . topDictIndex :
topDict . recalcFontBBox ( )
if not isCFF2 :
strings = IndexedStrings ( )
else :
strings = None
writer = CFFWriter ( isCFF2 )
topCompiler = self . topDictIndex . getCompiler ( strings , self , isCFF2 = isCFF2 )
if isCFF2 :
self . hdrSize = 5
writer . add ( sstruct . pack ( cffHeaderFormat , self ) )
# Note: topDictSize will most likely change in CFFWriter.toFile().
self . topDictSize = topCompiler . getDataLength ( )
writer . add ( struct . pack ( " >H " , self . topDictSize ) )
else :
self . hdrSize = 4
self . offSize = 4 # will most likely change in CFFWriter.toFile().
writer . add ( sstruct . pack ( cffHeaderFormat , self ) )
writer . add ( struct . pack ( " B " , self . offSize ) )
if not isCFF2 :
fontNames = Index ( )
for name in self . fontNames :
fontNames . append ( name )
writer . add ( fontNames . getCompiler ( strings , self , isCFF2 = isCFF2 ) )
writer . add ( topCompiler )
if not isCFF2 :
writer . add ( strings . getCompiler ( ) )
writer . add ( self . GlobalSubrs . getCompiler ( strings , self , isCFF2 = isCFF2 ) )
for topDict in self . topDictIndex :
if not hasattr ( topDict , " charset " ) or topDict . charset is None :
charset = otFont . getGlyphOrder ( )
topDict . charset = charset
children = topCompiler . getChildren ( strings )
for child in children :
writer . add ( child )
writer . toFile ( file )
def toXML ( self , xmlWriter ) :
""" Write the object into XML representation onto the given
: class : ` fontTools . misc . xmlWriter . XMLWriter ` .
. . code : : python
writer = xmlWriter . XMLWriter ( sys . stdout )
tt [ " CFF " ] . cff . toXML ( writer )
"""
xmlWriter . simpletag ( " major " , value = self . major )
xmlWriter . newline ( )
xmlWriter . simpletag ( " minor " , value = self . minor )
xmlWriter . newline ( )
for fontName in self . fontNames :
xmlWriter . begintag ( " CFFFont " , name = tostr ( fontName ) )
xmlWriter . newline ( )
font = self [ fontName ]
font . toXML ( xmlWriter )
xmlWriter . endtag ( " CFFFont " )
xmlWriter . newline ( )
xmlWriter . newline ( )
xmlWriter . begintag ( " GlobalSubrs " )
xmlWriter . newline ( )
self . GlobalSubrs . toXML ( xmlWriter )
xmlWriter . endtag ( " GlobalSubrs " )
xmlWriter . newline ( )
def fromXML ( self , name , attrs , content , otFont = None ) :
""" Reads data from the XML element into the ``CFFFontSet`` object. """
self . otFont = otFont
# set defaults. These will be replaced if there are entries for them
# in the XML file.
if not hasattr ( self , " major " ) :
self . major = 1
if not hasattr ( self , " minor " ) :
self . minor = 0
if name == " CFFFont " :
if self . major == 1 :
if not hasattr ( self , " offSize " ) :
# this will be recalculated when the cff is compiled.
self . offSize = 4
if not hasattr ( self , " hdrSize " ) :
self . hdrSize = 4
if not hasattr ( self , " GlobalSubrs " ) :
self . GlobalSubrs = GlobalSubrsIndex ( )
if not hasattr ( self , " fontNames " ) :
self . fontNames = [ ]
self . topDictIndex = TopDictIndex ( )
fontName = attrs [ " name " ]
self . fontNames . append ( fontName )
topDict = TopDict ( GlobalSubrs = self . GlobalSubrs )
topDict . charset = None # gets filled in later
elif self . major == 2 :
if not hasattr ( self , " hdrSize " ) :
self . hdrSize = 5
if not hasattr ( self , " GlobalSubrs " ) :
self . GlobalSubrs = GlobalSubrsIndex ( )
if not hasattr ( self , " fontNames " ) :
self . fontNames = [ " CFF2Font " ]
cff2GetGlyphOrder = self . otFont . getGlyphOrder
topDict = TopDict (
GlobalSubrs = self . GlobalSubrs , cff2GetGlyphOrder = cff2GetGlyphOrder
)
self . topDictIndex = TopDictIndex ( None , cff2GetGlyphOrder )
self . topDictIndex . append ( topDict )
for element in content :
if isinstance ( element , str ) :
continue
name , attrs , content = element
topDict . fromXML ( name , attrs , content )
if hasattr ( topDict , " VarStore " ) and topDict . FDArray [ 0 ] . vstore is None :
fdArray = topDict . FDArray
for fontDict in fdArray :
if hasattr ( fontDict , " Private " ) :
fontDict . Private . vstore = topDict . VarStore
elif name == " GlobalSubrs " :
subrCharStringClass = psCharStrings . T2CharString
if not hasattr ( self , " GlobalSubrs " ) :
self . GlobalSubrs = GlobalSubrsIndex ( )
for element in content :
if isinstance ( element , str ) :
continue
name , attrs , content = element
subr = subrCharStringClass ( )
subr . fromXML ( name , attrs , content )
self . GlobalSubrs . append ( subr )
elif name == " major " :
self . major = int ( attrs [ " value " ] )
elif name == " minor " :
self . minor = int ( attrs [ " value " ] )
def convertCFFToCFF2 ( self , otFont ) :
2024-05-16 16:27:31 -07:00
from . CFFToCFF2 import _convertCFFToCFF2
2024-05-15 17:22:24 -07:00
2024-05-16 16:27:31 -07:00
_convertCFFToCFF2 ( self , otFont )
2024-05-15 17:22:24 -07:00
2024-05-16 08:10:24 -07:00
def convertCFF2ToCFF ( self , otFont ) :
2024-05-16 16:27:31 -07:00
from . CFF2ToCFF import _convertCFF2ToCFF
2024-05-15 17:22:24 -07:00
2024-05-16 16:27:31 -07:00
_convertCFF2ToCFF ( self , otFont )
2024-05-15 17:22:24 -07:00
2022-12-13 11:26:36 +00:00
def desubroutinize ( self ) :
2024-05-21 12:34:28 -07:00
from . transforms import desubroutinize
desubroutinize ( self )
2021-11-17 05:12:21 +02:00
2024-05-21 12:49:41 -07:00
def remove_hints ( self ) :
from . transforms import remove_hints
remove_hints ( self )
def remove_unused_subroutines ( self ) :
from . transforms import remove_unused_subroutines
remove_unused_subroutines ( self )
2017-06-21 10:32:58 +01:00
2013-11-28 14:26:58 -05:00
class CFFWriter ( object ) :
2022-12-13 11:26:36 +00:00
""" Helper class for serializing CFF data to binary. Used by
: meth : ` CFFFontSet . compile ` . """
def __init__ ( self , isCFF2 ) :
self . data = [ ]
self . isCFF2 = isCFF2
def add ( self , table ) :
self . data . append ( table )
def toFile ( self , file ) :
lastPosList = None
count = 1
while True :
log . log ( DEBUG , " CFFWriter.toFile() iteration: %d " , count )
count = count + 1
pos = 0
posList = [ pos ]
for item in self . data :
if hasattr ( item , " getDataLength " ) :
endPos = pos + item . getDataLength ( )
if isinstance ( item , TopDictIndexCompiler ) and item . isCFF2 :
self . topDictSize = item . getDataLength ( )
else :
endPos = pos + len ( item )
if hasattr ( item , " setPos " ) :
item . setPos ( pos , endPos )
pos = endPos
posList . append ( pos )
if posList == lastPosList :
break
lastPosList = posList
log . log ( DEBUG , " CFFWriter.toFile() writing to file. " )
begin = file . tell ( )
if self . isCFF2 :
self . data [ 1 ] = struct . pack ( " >H " , self . topDictSize )
else :
self . offSize = calcOffSize ( lastPosList [ - 1 ] )
self . data [ 1 ] = struct . pack ( " B " , self . offSize )
posList = [ 0 ]
for item in self . data :
if hasattr ( item , " toFile " ) :
item . toFile ( file )
else :
file . write ( item )
posList . append ( file . tell ( ) - begin )
assert posList == lastPosList
2002-05-23 21:50:36 +00:00
def calcOffSize ( largestOffset ) :
2022-12-13 11:26:36 +00:00
if largestOffset < 0x100 :
offSize = 1
elif largestOffset < 0x10000 :
offSize = 2
elif largestOffset < 0x1000000 :
offSize = 3
else :
offSize = 4
return offSize
2002-05-23 21:50:36 +00:00
2013-11-28 14:26:58 -05:00
class IndexCompiler ( object ) :
2022-12-13 11:26:36 +00:00
""" Base class for writing CFF `INDEX data <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#5-index-data>`_
to binary . """
def __init__ ( self , items , strings , parent , isCFF2 = None ) :
if isCFF2 is None and hasattr ( parent , " isCFF2 " ) :
isCFF2 = parent . isCFF2
assert isCFF2 is not None
self . isCFF2 = isCFF2
self . items = self . getItems ( items , strings )
self . parent = parent
def getItems ( self , items , strings ) :
return items
def getOffsets ( self ) :
# An empty INDEX contains only the count field.
if self . items :
pos = 1
offsets = [ pos ]
for item in self . items :
if hasattr ( item , " getDataLength " ) :
pos = pos + item . getDataLength ( )
else :
pos = pos + len ( item )
offsets . append ( pos )
else :
offsets = [ ]
return offsets
def getDataLength ( self ) :
if self . isCFF2 :
countSize = 4
else :
countSize = 2
if self . items :
lastOffset = self . getOffsets ( ) [ - 1 ]
offSize = calcOffSize ( lastOffset )
dataLength = (
countSize
+ 1 # count
+ ( len ( self . items ) + 1 ) * offSize # offSize
+ lastOffset # the offsets
- 1 # size of object data
)
else :
# count. For empty INDEX tables, this is the only entry.
dataLength = countSize
return dataLength
def toFile ( self , file ) :
offsets = self . getOffsets ( )
if self . isCFF2 :
writeCard32 ( file , len ( self . items ) )
else :
writeCard16 ( file , len ( self . items ) )
# An empty INDEX contains only the count field.
if self . items :
offSize = calcOffSize ( offsets [ - 1 ] )
writeCard8 ( file , offSize )
offSize = - offSize
pack = struct . pack
for offset in offsets :
binOffset = pack ( " >l " , offset ) [ offSize : ]
assert len ( binOffset ) == - offSize
file . write ( binOffset )
for item in self . items :
if hasattr ( item , " toFile " ) :
item . toFile ( file )
else :
data = tobytes ( item , encoding = " latin1 " )
file . write ( data )
2002-05-23 21:50:36 +00:00
class IndexedStringsCompiler ( IndexCompiler ) :
2022-12-13 11:26:36 +00:00
def getItems ( self , items , strings ) :
return items . strings
2002-05-23 21:50:36 +00:00
class TopDictIndexCompiler ( IndexCompiler ) :
2022-12-13 11:26:36 +00:00
""" Helper class for writing the TopDict to binary. """
def getItems ( self , items , strings ) :
out = [ ]
for item in items :
out . append ( item . getCompiler ( strings , self ) )
return out
def getChildren ( self , strings ) :
children = [ ]
for topDict in self . items :
children . extend ( topDict . getChildren ( strings ) )
return children
def getOffsets ( self ) :
if self . isCFF2 :
offsets = [ 0 , self . items [ 0 ] . getDataLength ( ) ]
return offsets
else :
return super ( TopDictIndexCompiler , self ) . getOffsets ( )
def getDataLength ( self ) :
if self . isCFF2 :
dataLength = self . items [ 0 ] . getDataLength ( )
return dataLength
else :
return super ( TopDictIndexCompiler , self ) . getDataLength ( )
def toFile ( self , file ) :
if self . isCFF2 :
self . items [ 0 ] . toFile ( file )
else :
super ( TopDictIndexCompiler , self ) . toFile ( file )
2017-01-12 15:23:12 -08:00
2002-05-23 21:50:36 +00:00
2003-08-22 19:53:32 +00:00
class FDArrayIndexCompiler ( IndexCompiler ) :
2022-12-13 11:26:36 +00:00
""" Helper class for writing the
` Font DICT INDEX < https : / / docs . microsoft . com / en - us / typography / opentype / spec / cff2 #10-font-dict-index-font-dicts-and-fdselect>`_
to binary . """
def getItems ( self , items , strings ) :
out = [ ]
for item in items :
out . append ( item . getCompiler ( strings , self ) )
return out
def getChildren ( self , strings ) :
children = [ ]
for fontDict in self . items :
children . extend ( fontDict . getChildren ( strings ) )
return children
def toFile ( self , file ) :
offsets = self . getOffsets ( )
if self . isCFF2 :
writeCard32 ( file , len ( self . items ) )
else :
writeCard16 ( file , len ( self . items ) )
offSize = calcOffSize ( offsets [ - 1 ] )
writeCard8 ( file , offSize )
offSize = - offSize
pack = struct . pack
for offset in offsets :
binOffset = pack ( " >l " , offset ) [ offSize : ]
assert len ( binOffset ) == - offSize
file . write ( binOffset )
for item in self . items :
if hasattr ( item , " toFile " ) :
item . toFile ( file )
else :
file . write ( item )
def setPos ( self , pos , endPos ) :
self . parent . rawDict [ " FDArray " ] = pos
2003-08-22 19:53:32 +00:00
2002-05-23 21:50:36 +00:00
class GlobalSubrsCompiler ( IndexCompiler ) :
2022-12-13 11:26:36 +00:00
""" Helper class for writing the `global subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
to binary . """
2017-07-19 18:18:58 +01:00
2022-12-13 11:26:36 +00:00
def getItems ( self , items , strings ) :
out = [ ]
for cs in items :
cs . compile ( self . isCFF2 )
out . append ( cs . bytecode )
return out
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2002-05-23 21:50:36 +00:00
class SubrsCompiler ( GlobalSubrsCompiler ) :
2022-12-13 11:26:36 +00:00
""" Helper class for writing the `local subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
to binary . """
def setPos ( self , pos , endPos ) :
offset = pos - self . parent . pos
self . parent . rawDict [ " Subrs " ] = offset
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2002-05-23 21:50:36 +00:00
class CharStringsCompiler ( GlobalSubrsCompiler ) :
2022-12-13 11:26:36 +00:00
""" Helper class for writing the `CharStrings INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
to binary . """
2017-03-09 22:39:14 -08:00
2022-12-13 11:26:36 +00:00
def getItems ( self , items , strings ) :
out = [ ]
for cs in items :
cs . compile ( self . isCFF2 )
out . append ( cs . bytecode )
return out
def setPos ( self , pos , endPos ) :
self . parent . rawDict [ " CharStrings " ] = pos
2002-05-23 21:50:36 +00:00
2013-11-28 14:26:58 -05:00
class Index ( object ) :
2022-12-13 11:26:36 +00:00
""" This class represents what the CFF spec calls an INDEX (an array of
variable - sized objects ) . ` Index ` items can be addressed and set using
Python list indexing . """
compilerClass = IndexCompiler
def __init__ ( self , file = None , isCFF2 = None ) :
self . items = [ ]
2024-05-16 08:10:24 -07:00
self . offsets = offsets = [ ]
2022-12-13 11:26:36 +00:00
name = self . __class__ . __name__
if file is None :
return
self . _isCFF2 = isCFF2
log . log ( DEBUG , " loading %s at %s " , name , file . tell ( ) )
self . file = file
if isCFF2 :
count = readCard32 ( file )
else :
count = readCard16 ( file )
if count == 0 :
return
self . items = [ None ] * count
offSize = readCard8 ( file )
log . log ( DEBUG , " index count: %s offSize: %s " , count , offSize )
assert offSize < = 4 , " offSize too large: %s " % offSize
pad = b " \0 " * ( 4 - offSize )
for index in range ( count + 1 ) :
chunk = file . read ( offSize )
chunk = pad + chunk
( offset , ) = struct . unpack ( " >L " , chunk )
offsets . append ( int ( offset ) )
self . offsetBase = file . tell ( ) - 1
file . seek ( self . offsetBase + offsets [ - 1 ] ) # pretend we've read the whole lot
log . log ( DEBUG , " end of %s at %s " , name , file . tell ( ) )
def __len__ ( self ) :
return len ( self . items )
def __getitem__ ( self , index ) :
item = self . items [ index ]
if item is not None :
return item
offset = self . offsets [ index ] + self . offsetBase
size = self . offsets [ index + 1 ] - self . offsets [ index ]
file = self . file
file . seek ( offset )
data = file . read ( size )
assert len ( data ) == size
item = self . produceItem ( index , data , file , offset )
self . items [ index ] = item
return item
def __setitem__ ( self , index , item ) :
self . items [ index ] = item
def produceItem ( self , index , data , file , offset ) :
return data
def append ( self , item ) :
""" Add an item to an INDEX. """
self . items . append ( item )
def getCompiler ( self , strings , parent , isCFF2 = None ) :
return self . compilerClass ( self , strings , parent , isCFF2 = isCFF2 )
def clear ( self ) :
""" Empty the INDEX. """
del self . items [ : ]
2019-02-07 01:58:22 +01:00
2002-05-15 07:41:30 +00:00
2002-05-23 21:50:36 +00:00
class GlobalSubrsIndex ( Index ) :
2022-12-13 11:26:36 +00:00
""" This index contains all the global subroutines in the font. A global
subroutine is a set of ` ` CharString ` ` data which is accessible to any
glyph in the font , and are used to store repeated instructions - for
example , components may be encoded as global subroutines , but so could
hinting instructions .
Remember that when interpreting a ` ` callgsubr ` ` instruction ( or indeed
a ` ` callsubr ` ` instruction ) that you will need to add the " subroutine
number bias " to number given:
. . code : : python
tt = ttLib . TTFont ( " Almendra-Bold.otf " )
u = tt [ " CFF " ] . cff [ 0 ] . CharStrings [ " udieresis " ]
u . decompile ( )
u . toXML ( XMLWriter ( sys . stdout ) )
# <some stuff>
# -64 callgsubr <-- Subroutine which implements the dieresis mark
# <other stuff>
tt [ " CFF " ] . cff [ 0 ] . GlobalSubrs [ - 64 ] # <-- WRONG
# <T2CharString (bytecode) at 103451d10>
tt [ " CFF " ] . cff [ 0 ] . GlobalSubrs [ - 64 + 107 ] # <-- RIGHT
# <T2CharString (source) at 103451390>
( " The bias applied depends on the number of subrs (gsubrs). If the number of
subrs ( gsubrs ) is less than 1240 , the bias is 107. Otherwise if it is less
than 33900 , it is 1131 ; otherwise it is 32768. " ,
` Subroutine Operators < https : / / docs . microsoft . com / en - us / typography / opentype / otspec180 / cff2charstr #section4.4>`)
"""
compilerClass = GlobalSubrsCompiler
subrClass = psCharStrings . T2CharString
charStringClass = psCharStrings . T2CharString
def __init__ (
self ,
file = None ,
globalSubrs = None ,
private = None ,
fdSelect = None ,
fdArray = None ,
isCFF2 = None ,
) :
super ( GlobalSubrsIndex , self ) . __init__ ( file , isCFF2 = isCFF2 )
self . globalSubrs = globalSubrs
self . private = private
if fdSelect :
self . fdSelect = fdSelect
if fdArray :
self . fdArray = fdArray
def produceItem ( self , index , data , file , offset ) :
if self . private is not None :
private = self . private
elif hasattr ( self , " fdArray " ) and self . fdArray is not None :
if hasattr ( self , " fdSelect " ) and self . fdSelect is not None :
fdIndex = self . fdSelect [ index ]
else :
fdIndex = 0
private = self . fdArray [ fdIndex ] . Private
else :
private = None
return self . subrClass ( data , private = private , globalSubrs = self . globalSubrs )
def toXML ( self , xmlWriter ) :
""" Write the subroutines index into XML representation onto the given
: class : ` fontTools . misc . xmlWriter . XMLWriter ` .
. . code : : python
writer = xmlWriter . XMLWriter ( sys . stdout )
tt [ " CFF " ] . cff [ 0 ] . GlobalSubrs . toXML ( writer )
"""
xmlWriter . comment (
" The ' index ' attribute is only for humans; " " it is ignored when parsed. "
)
xmlWriter . newline ( )
for i in range ( len ( self ) ) :
subr = self [ i ]
if subr . needsDecompilation ( ) :
xmlWriter . begintag ( " CharString " , index = i , raw = 1 )
else :
xmlWriter . begintag ( " CharString " , index = i )
xmlWriter . newline ( )
subr . toXML ( xmlWriter )
xmlWriter . endtag ( " CharString " )
xmlWriter . newline ( )
def fromXML ( self , name , attrs , content ) :
if name != " CharString " :
return
subr = self . subrClass ( )
subr . fromXML ( name , attrs , content )
self . append ( subr )
def getItemAndSelector ( self , index ) :
sel = None
if hasattr ( self , " fdSelect " ) :
sel = self . fdSelect [ index ]
return self [ index ] , sel
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2002-05-23 21:50:36 +00:00
class SubrsIndex ( GlobalSubrsIndex ) :
2022-12-13 11:26:36 +00:00
""" This index contains a glyph ' s local subroutines. A local subroutine is a
private set of ` ` CharString ` ` data which is accessible only to the glyph to
which the index is attached . """
2020-05-14 17:04:34 +01:00
2022-12-13 11:26:36 +00:00
compilerClass = SubrsCompiler
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2002-05-17 07:06:32 +00:00
class TopDictIndex ( Index ) :
2022-12-13 11:26:36 +00:00
""" This index represents the array of ``TopDict`` structures in the font
( again , usually only one entry is present ) . Hence the following calls are
equivalent :
. . code : : python
tt [ " CFF " ] . cff [ 0 ]
# <fontTools.cffLib.TopDict object at 0x102ed6e50>
tt [ " CFF " ] . cff . topDictIndex [ 0 ]
# <fontTools.cffLib.TopDict object at 0x102ed6e50>
"""
compilerClass = TopDictIndexCompiler
def __init__ ( self , file = None , cff2GetGlyphOrder = None , topSize = 0 , isCFF2 = None ) :
self . cff2GetGlyphOrder = cff2GetGlyphOrder
if file is not None and isCFF2 :
self . _isCFF2 = isCFF2
self . items = [ ]
name = self . __class__ . __name__
log . log ( DEBUG , " loading %s at %s " , name , file . tell ( ) )
self . file = file
count = 1
self . items = [ None ] * count
self . offsets = [ 0 , topSize ]
self . offsetBase = file . tell ( )
# pretend we've read the whole lot
file . seek ( self . offsetBase + topSize )
log . log ( DEBUG , " end of %s at %s " , name , file . tell ( ) )
else :
super ( TopDictIndex , self ) . __init__ ( file , isCFF2 = isCFF2 )
def produceItem ( self , index , data , file , offset ) :
top = TopDict (
self . strings ,
file ,
offset ,
self . GlobalSubrs ,
self . cff2GetGlyphOrder ,
isCFF2 = self . _isCFF2 ,
)
top . decompile ( data )
return top
def toXML ( self , xmlWriter ) :
for i in range ( len ( self ) ) :
xmlWriter . begintag ( " FontDict " , index = i )
xmlWriter . newline ( )
self [ i ] . toXML ( xmlWriter )
xmlWriter . endtag ( " FontDict " )
xmlWriter . newline ( )
2002-05-17 07:06:32 +00:00
2017-06-21 10:32:58 +01:00
2017-05-10 10:37:55 -07:00
class FDArrayIndex ( Index ) :
2022-12-13 11:26:36 +00:00
compilerClass = FDArrayIndexCompiler
def toXML ( self , xmlWriter ) :
for i in range ( len ( self ) ) :
xmlWriter . begintag ( " FontDict " , index = i )
xmlWriter . newline ( )
self [ i ] . toXML ( xmlWriter )
xmlWriter . endtag ( " FontDict " )
xmlWriter . newline ( )
def produceItem ( self , index , data , file , offset ) :
fontDict = FontDict (
self . strings ,
file ,
offset ,
self . GlobalSubrs ,
isCFF2 = self . _isCFF2 ,
vstore = self . vstore ,
)
fontDict . decompile ( data )
return fontDict
def fromXML ( self , name , attrs , content ) :
if name != " FontDict " :
return
fontDict = FontDict ( )
for element in content :
if isinstance ( element , str ) :
continue
name , attrs , content = element
fontDict . fromXML ( name , attrs , content )
self . append ( fontDict )
2003-08-22 19:53:32 +00:00
2017-06-21 10:32:58 +01:00
class VarStoreData ( object ) :
2022-12-13 11:26:36 +00:00
def __init__ ( self , file = None , otVarStore = None ) :
self . file = file
self . data = None
self . otVarStore = otVarStore
self . font = TTFont ( ) # dummy font for the decompile function.
def decompile ( self ) :
if self . file :
# read data in from file. Assume position is correct.
length = readCard16 ( self . file )
self . data = self . file . read ( length )
globalState = { }
reader = OTTableReader ( self . data , globalState )
self . otVarStore = ot . VarStore ( )
self . otVarStore . decompile ( reader , self . font )
2024-05-13 22:45:54 -06:00
self . data = None
2022-12-13 11:26:36 +00:00
return self
def compile ( self ) :
writer = OTTableWriter ( )
self . otVarStore . compile ( writer , self . font )
# Note that this omits the initial Card16 length from the CFF2
# VarStore data block
self . data = writer . getAllData ( )
def writeXML ( self , xmlWriter , name ) :
self . otVarStore . toXML ( xmlWriter , self . font )
def xmlRead ( self , name , attrs , content , parent ) :
self . otVarStore = ot . VarStore ( )
for element in content :
if isinstance ( element , tuple ) :
name , attrs , content = element
self . otVarStore . fromXML ( name , attrs , content , self . font )
else :
pass
return None
def __len__ ( self ) :
return len ( self . data )
def getNumRegions ( self , vsIndex ) :
if vsIndex is None :
vsIndex = 0
varData = self . otVarStore . VarData [ vsIndex ]
numRegions = varData . VarRegionCount
return numRegions
2017-03-09 21:30:28 -08:00
2003-08-22 19:53:32 +00:00
2017-07-19 18:18:58 +01:00
class FDSelect ( object ) :
2022-12-13 11:26:36 +00:00
def __init__ ( self , file = None , numGlyphs = None , format = None ) :
if file :
# read data in from file
self . format = readCard8 ( file )
if self . format == 0 :
from array import array
self . gidArray = array ( " B " , file . read ( numGlyphs ) ) . tolist ( )
elif self . format == 3 :
gidArray = [ None ] * numGlyphs
nRanges = readCard16 ( file )
fd = None
prev = None
for i in range ( nRanges ) :
first = readCard16 ( file )
if prev is not None :
for glyphID in range ( prev , first ) :
gidArray [ glyphID ] = fd
prev = first
fd = readCard8 ( file )
if prev is not None :
first = readCard16 ( file )
for glyphID in range ( prev , first ) :
gidArray [ glyphID ] = fd
self . gidArray = gidArray
elif self . format == 4 :
gidArray = [ None ] * numGlyphs
nRanges = readCard32 ( file )
fd = None
prev = None
for i in range ( nRanges ) :
first = readCard32 ( file )
if prev is not None :
for glyphID in range ( prev , first ) :
gidArray [ glyphID ] = fd
prev = first
fd = readCard16 ( file )
if prev is not None :
first = readCard32 ( file )
for glyphID in range ( prev , first ) :
gidArray [ glyphID ] = fd
self . gidArray = gidArray
else :
assert False , " unsupported FDSelect format: %s " % format
else :
# reading from XML. Make empty gidArray, and leave format as passed in.
# format is None will result in the smallest representation being used.
self . format = format
self . gidArray = [ ]
def __len__ ( self ) :
return len ( self . gidArray )
def __getitem__ ( self , index ) :
return self . gidArray [ index ]
def __setitem__ ( self , index , fdSelectValue ) :
self . gidArray [ index ] = fdSelectValue
def append ( self , fdSelectValue ) :
self . gidArray . append ( fdSelectValue )
2015-04-26 02:01:01 -04:00
2017-06-21 10:32:58 +01:00
2013-11-28 14:26:58 -05:00
class CharStrings ( object ) :
2022-12-13 11:26:36 +00:00
""" The ``CharStrings`` in the font represent the instructions for drawing
each glyph . This object presents a dictionary interface to the font ' s
CharStrings , indexed by glyph name :
. . code : : python
tt [ " CFF " ] . cff [ 0 ] . CharStrings [ " a " ]
# <T2CharString (bytecode) at 103451e90>
See : class : ` fontTools . misc . psCharStrings . T1CharString ` and
: class : ` fontTools . misc . psCharStrings . T2CharString ` for how to decompile ,
compile and interpret the glyph drawing instructions in the returned objects .
"""
def __init__ (
self ,
file ,
charset ,
globalSubrs ,
private ,
fdSelect ,
fdArray ,
isCFF2 = None ,
varStore = None ,
) :
self . globalSubrs = globalSubrs
self . varStore = varStore
if file is not None :
self . charStringsIndex = SubrsIndex (
file , globalSubrs , private , fdSelect , fdArray , isCFF2 = isCFF2
)
self . charStrings = charStrings = { }
for i in range ( len ( charset ) ) :
charStrings [ charset [ i ] ] = i
# read from OTF file: charStrings.values() are indices into
# charStringsIndex.
self . charStringsAreIndexed = 1
else :
self . charStrings = { }
# read from ttx file: charStrings.values() are actual charstrings
self . charStringsAreIndexed = 0
self . private = private
if fdSelect is not None :
self . fdSelect = fdSelect
if fdArray is not None :
self . fdArray = fdArray
def keys ( self ) :
return list ( self . charStrings . keys ( ) )
def values ( self ) :
if self . charStringsAreIndexed :
return self . charStringsIndex
else :
return list ( self . charStrings . values ( ) )
def has_key ( self , name ) :
return name in self . charStrings
__contains__ = has_key
def __len__ ( self ) :
return len ( self . charStrings )
def __getitem__ ( self , name ) :
charString = self . charStrings [ name ]
if self . charStringsAreIndexed :
charString = self . charStringsIndex [ charString ]
return charString
def __setitem__ ( self , name , charString ) :
if self . charStringsAreIndexed :
index = self . charStrings [ name ]
self . charStringsIndex [ index ] = charString
else :
self . charStrings [ name ] = charString
def getItemAndSelector ( self , name ) :
if self . charStringsAreIndexed :
index = self . charStrings [ name ]
return self . charStringsIndex . getItemAndSelector ( index )
else :
if hasattr ( self , " fdArray " ) :
if hasattr ( self , " fdSelect " ) :
sel = self . charStrings [ name ] . fdSelectIndex
else :
sel = 0
else :
sel = None
return self . charStrings [ name ] , sel
def toXML ( self , xmlWriter ) :
names = sorted ( self . keys ( ) )
for name in names :
charStr , fdSelectIndex = self . getItemAndSelector ( name )
if charStr . needsDecompilation ( ) :
raw = [ ( " raw " , 1 ) ]
else :
raw = [ ]
if fdSelectIndex is None :
xmlWriter . begintag ( " CharString " , [ ( " name " , name ) ] + raw )
else :
xmlWriter . begintag (
" CharString " ,
[ ( " name " , name ) , ( " fdSelectIndex " , fdSelectIndex ) ] + raw ,
)
xmlWriter . newline ( )
charStr . toXML ( xmlWriter )
xmlWriter . endtag ( " CharString " )
xmlWriter . newline ( )
def fromXML ( self , name , attrs , content ) :
for element in content :
if isinstance ( element , str ) :
continue
name , attrs , content = element
if name != " CharString " :
continue
fdID = - 1
if hasattr ( self , " fdArray " ) :
try :
fdID = safeEval ( attrs [ " fdSelectIndex " ] )
except KeyError :
fdID = 0
private = self . fdArray [ fdID ] . Private
else :
private = self . private
glyphName = attrs [ " name " ]
charStringClass = psCharStrings . T2CharString
charString = charStringClass ( private = private , globalSubrs = self . globalSubrs )
charString . fromXML ( name , attrs , content )
if fdID > = 0 :
charString . fdSelectIndex = fdID
self [ glyphName ] = charString
2002-05-15 07:41:30 +00:00
2002-05-17 18:36:07 +00:00
def readCard8 ( file ) :
2022-12-13 11:26:36 +00:00
return byteord ( file . read ( 1 ) )
2002-05-17 18:36:07 +00:00
2017-06-21 10:32:58 +01:00
2002-05-17 18:36:07 +00:00
def readCard16 ( file ) :
2022-12-13 11:26:36 +00:00
( value , ) = struct . unpack ( " >H " , file . read ( 2 ) )
return value
2002-05-17 18:36:07 +00:00
2017-06-21 10:32:58 +01:00
2017-01-12 15:23:12 -08:00
def readCard32 ( file ) :
2022-12-13 11:26:36 +00:00
( value , ) = struct . unpack ( " >L " , file . read ( 4 ) )
return value
2017-01-12 15:23:12 -08:00
2017-06-21 10:32:58 +01:00
2002-05-23 21:50:36 +00:00
def writeCard8 ( file , value ) :
2022-12-13 11:26:36 +00:00
file . write ( bytechr ( value ) )
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2002-05-23 21:50:36 +00:00
def writeCard16 ( file , value ) :
2022-12-13 11:26:36 +00:00
file . write ( struct . pack ( " >H " , value ) )
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2017-01-12 15:23:12 -08:00
def writeCard32 ( file , value ) :
2022-12-13 11:26:36 +00:00
file . write ( struct . pack ( " >L " , value ) )
2017-01-12 15:23:12 -08:00
2017-06-21 10:32:58 +01:00
2002-05-23 21:50:36 +00:00
def packCard8 ( value ) :
2022-12-13 11:26:36 +00:00
return bytechr ( value )
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2002-05-23 21:50:36 +00:00
def packCard16 ( value ) :
2022-12-13 11:26:36 +00:00
return struct . pack ( " >H " , value )
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2019-08-01 21:28:46 +02:00
def packCard32 ( value ) :
2022-12-13 11:26:36 +00:00
return struct . pack ( " >L " , value )
2019-08-01 21:28:46 +02:00
2002-05-16 18:17:32 +00:00
def buildOperatorDict ( table ) :
2022-12-13 11:26:36 +00:00
d = { }
for op , name , arg , default , conv in table :
d [ op ] = ( name , arg )
return d
2002-05-15 07:41:30 +00:00
2017-06-21 10:32:58 +01:00
2002-05-23 21:50:36 +00:00
def buildOpcodeDict ( table ) :
2022-12-13 11:26:36 +00:00
d = { }
for op , name , arg , default , conv in table :
if isinstance ( op , tuple ) :
op = bytechr ( op [ 0 ] ) + bytechr ( op [ 1 ] )
else :
op = bytechr ( op )
d [ name ] = ( op , arg )
return d
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2002-05-16 18:17:32 +00:00
def buildOrder ( table ) :
2022-12-13 11:26:36 +00:00
l = [ ]
for op , name , arg , default , conv in table :
l . append ( name )
return l
2002-05-15 07:41:30 +00:00
2017-06-21 10:32:58 +01:00
2002-05-16 18:17:32 +00:00
def buildDefaults ( table ) :
2022-12-13 11:26:36 +00:00
d = { }
for op , name , arg , default , conv in table :
if default is not None :
d [ name ] = default
return d
2002-05-16 18:17:32 +00:00
2017-06-21 10:32:58 +01:00
2002-05-16 18:17:32 +00:00
def buildConverters ( table ) :
2022-12-13 11:26:36 +00:00
d = { }
for op , name , arg , default , conv in table :
d [ name ] = conv
return d
2002-05-16 18:17:32 +00:00
2018-02-23 02:01:35 +09:00
class SimpleConverter ( object ) :
2022-12-13 11:26:36 +00:00
def read ( self , parent , value ) :
if not hasattr ( parent , " file " ) :
return self . _read ( parent , value )
file = parent . file
pos = file . tell ( )
try :
return self . _read ( parent , value )
finally :
file . seek ( pos )
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def _read ( self , parent , value ) :
return value
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def write ( self , parent , value ) :
return value
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def xmlWrite ( self , xmlWriter , name , value ) :
xmlWriter . simpletag ( name , value = value )
xmlWriter . newline ( )
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def xmlRead ( self , name , attrs , content , parent ) :
return attrs [ " value " ]
2002-05-24 09:58:04 +00:00
2017-06-21 10:32:58 +01:00
2013-11-28 07:10:53 -05:00
class ASCIIConverter ( SimpleConverter ) :
2022-12-13 11:26:36 +00:00
def _read ( self , parent , value ) :
return tostr ( value , encoding = " ascii " )
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def write ( self , parent , value ) :
return tobytes ( value , encoding = " ascii " )
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def xmlWrite ( self , xmlWriter , name , value ) :
xmlWriter . simpletag ( name , value = tostr ( value , encoding = " ascii " ) )
xmlWriter . newline ( )
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def xmlRead ( self , name , attrs , content , parent ) :
return tobytes ( attrs [ " value " ] , encoding = ( " ascii " ) )
2013-11-28 07:10:53 -05:00
2017-06-21 10:32:58 +01:00
2002-09-09 14:18:39 +00:00
class Latin1Converter ( SimpleConverter ) :
2022-12-13 11:26:36 +00:00
def _read ( self , parent , value ) :
return tostr ( value , encoding = " latin1 " )
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def write ( self , parent , value ) :
return tobytes ( value , encoding = " latin1 " )
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def xmlWrite ( self , xmlWriter , name , value ) :
value = tostr ( value , encoding = " latin1 " )
if name in [ " Notice " , " Copyright " ] :
value = re . sub ( r " [ \ r \ n] \ s+ " , " " , value )
xmlWriter . simpletag ( name , value = value )
xmlWriter . newline ( )
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def xmlRead ( self , name , attrs , content , parent ) :
return tobytes ( attrs [ " value " ] , encoding = ( " latin1 " ) )
2002-09-09 14:18:39 +00:00
2002-05-24 09:58:04 +00:00
def parseNum ( s ) :
2022-12-13 11:26:36 +00:00
try :
value = int ( s )
except :
value = float ( s )
return value
2002-05-24 09:58:04 +00:00
2017-06-21 10:32:58 +01:00
2017-01-12 15:23:12 -08:00
def parseBlendList ( s ) :
2022-12-13 11:26:36 +00:00
valueList = [ ]
for element in s :
if isinstance ( element , str ) :
continue
name , attrs , content = element
blendList = attrs [ " value " ] . split ( )
blendList = [ eval ( val ) for val in blendList ]
valueList . append ( blendList )
if len ( valueList ) == 1 :
valueList = valueList [ 0 ]
return valueList
2017-03-09 21:30:28 -08:00
2017-06-21 10:32:58 +01:00
2002-05-24 09:58:04 +00:00
class NumberConverter ( SimpleConverter ) :
2022-12-13 11:26:36 +00:00
def xmlWrite ( self , xmlWriter , name , value ) :
if isinstance ( value , list ) :
xmlWriter . begintag ( name )
xmlWriter . newline ( )
xmlWriter . indent ( )
blendValue = " " . join ( [ str ( val ) for val in value ] )
xmlWriter . simpletag ( kBlendDictOpName , value = blendValue )
xmlWriter . newline ( )
xmlWriter . dedent ( )
xmlWriter . endtag ( name )
xmlWriter . newline ( )
else :
xmlWriter . simpletag ( name , value = value )
xmlWriter . newline ( )
def xmlRead ( self , name , attrs , content , parent ) :
valueString = attrs . get ( " value " , None )
if valueString is None :
value = parseBlendList ( content )
else :
value = parseNum ( attrs [ " value " ] )
return value
2002-05-24 09:58:04 +00:00
2017-06-21 10:32:58 +01:00
2002-05-24 09:58:04 +00:00
class ArrayConverter ( SimpleConverter ) :
2022-12-13 11:26:36 +00:00
def xmlWrite ( self , xmlWriter , name , value ) :
if value and isinstance ( value [ 0 ] , list ) :
xmlWriter . begintag ( name )
xmlWriter . newline ( )
xmlWriter . indent ( )
for valueList in value :
blendValue = " " . join ( [ str ( val ) for val in valueList ] )
xmlWriter . simpletag ( kBlendDictOpName , value = blendValue )
xmlWriter . newline ( )
xmlWriter . dedent ( )
xmlWriter . endtag ( name )
xmlWriter . newline ( )
else :
value = " " . join ( [ str ( val ) for val in value ] )
xmlWriter . simpletag ( name , value = value )
xmlWriter . newline ( )
def xmlRead ( self , name , attrs , content , parent ) :
valueString = attrs . get ( " value " , None )
if valueString is None :
valueList = parseBlendList ( content )
else :
values = valueString . split ( )
valueList = [ parseNum ( value ) for value in values ]
return valueList
2017-01-12 15:23:12 -08:00
2017-06-21 10:32:58 +01:00
2002-05-24 09:58:04 +00:00
class TableConverter ( SimpleConverter ) :
2022-12-13 11:26:36 +00:00
def xmlWrite ( self , xmlWriter , name , value ) :
xmlWriter . begintag ( name )
xmlWriter . newline ( )
value . toXML ( xmlWriter )
xmlWriter . endtag ( name )
xmlWriter . newline ( )
def xmlRead ( self , name , attrs , content , parent ) :
ob = self . getClass ( ) ( )
for element in content :
if isinstance ( element , str ) :
continue
name , attrs , content = element
ob . fromXML ( name , attrs , content )
return ob
2002-05-17 18:36:07 +00:00
2017-06-21 10:32:58 +01:00
2002-05-24 09:58:04 +00:00
class PrivateDictConverter ( TableConverter ) :
2022-12-13 11:26:36 +00:00
def getClass ( self ) :
return PrivateDict
def _read ( self , parent , value ) :
size , offset = value
file = parent . file
isCFF2 = parent . _isCFF2
try :
vstore = parent . vstore
except AttributeError :
vstore = None
priv = PrivateDict ( parent . strings , file , offset , isCFF2 = isCFF2 , vstore = vstore )
file . seek ( offset )
data = file . read ( size )
assert len ( data ) == size
priv . decompile ( data )
return priv
def write ( self , parent , value ) :
return ( 0 , 0 ) # dummy value
2017-06-21 10:32:58 +01:00
2002-05-24 09:58:04 +00:00
class SubrsConverter ( TableConverter ) :
2022-12-13 11:26:36 +00:00
def getClass ( self ) :
return SubrsIndex
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def _read ( self , parent , value ) :
file = parent . file
isCFF2 = parent . _isCFF2
file . seek ( parent . offset + value ) # Offset(self)
return SubrsIndex ( file , isCFF2 = isCFF2 )
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def write ( self , parent , value ) :
return 0 # dummy value
2002-05-16 18:17:32 +00:00
2017-06-21 10:32:58 +01:00
2002-05-24 09:58:04 +00:00
class CharStringsConverter ( TableConverter ) :
2022-12-13 11:26:36 +00:00
def _read ( self , parent , value ) :
file = parent . file
isCFF2 = parent . _isCFF2
charset = parent . charset
varStore = getattr ( parent , " VarStore " , None )
globalSubrs = parent . GlobalSubrs
if hasattr ( parent , " FDArray " ) :
fdArray = parent . FDArray
if hasattr ( parent , " FDSelect " ) :
fdSelect = parent . FDSelect
else :
fdSelect = None
private = None
else :
fdSelect , fdArray = None , None
private = parent . Private
file . seek ( value ) # Offset(0)
charStrings = CharStrings (
file ,
charset ,
globalSubrs ,
private ,
fdSelect ,
fdArray ,
isCFF2 = isCFF2 ,
varStore = varStore ,
)
return charStrings
def write ( self , parent , value ) :
return 0 # dummy value
def xmlRead ( self , name , attrs , content , parent ) :
if hasattr ( parent , " FDArray " ) :
# if it is a CID-keyed font, then the private Dict is extracted from the
# parent.FDArray
fdArray = parent . FDArray
if hasattr ( parent , " FDSelect " ) :
fdSelect = parent . FDSelect
else :
fdSelect = None
private = None
else :
# if it is a name-keyed font, then the private dict is in the top dict,
# and
# there is no fdArray.
private , fdSelect , fdArray = parent . Private , None , None
charStrings = CharStrings (
None ,
None ,
parent . GlobalSubrs ,
private ,
fdSelect ,
fdArray ,
varStore = getattr ( parent , " VarStore " , None ) ,
)
charStrings . fromXML ( name , attrs , content )
return charStrings
2002-05-16 18:17:32 +00:00
2017-06-21 10:32:58 +01:00
2018-02-23 02:01:35 +09:00
class CharsetConverter ( SimpleConverter ) :
2022-12-13 11:26:36 +00:00
def _read ( self , parent , value ) :
isCID = hasattr ( parent , " ROS " )
if value > 2 :
numGlyphs = parent . numGlyphs
file = parent . file
file . seek ( value )
log . log ( DEBUG , " loading charset at %s " , value )
format = readCard8 ( file )
if format == 0 :
charset = parseCharset0 ( numGlyphs , file , parent . strings , isCID )
elif format == 1 or format == 2 :
charset = parseCharset ( numGlyphs , file , parent . strings , isCID , format )
else :
raise NotImplementedError
assert len ( charset ) == numGlyphs
log . log ( DEBUG , " charset end at %s " , file . tell ( ) )
# make sure glyph names are unique
allNames = { }
newCharset = [ ]
for glyphName in charset :
if glyphName in allNames :
# make up a new glyphName that's unique
n = allNames [ glyphName ]
while ( glyphName + " # " + str ( n ) ) in allNames :
n + = 1
allNames [ glyphName ] = n + 1
glyphName = glyphName + " # " + str ( n )
allNames [ glyphName ] = 1
newCharset . append ( glyphName )
charset = newCharset
else : # offset == 0 -> no charset data.
if isCID or " CharStrings " not in parent . rawDict :
# We get here only when processing fontDicts from the FDArray of
2024-05-24 18:04:03 -06:00
# CFF-CID fonts. Only the real topDict references the charset.
2022-12-13 11:26:36 +00:00
assert value == 0
charset = None
elif value == 0 :
charset = cffISOAdobeStrings
elif value == 1 :
charset = cffIExpertStrings
elif value == 2 :
charset = cffExpertSubsetStrings
if charset and ( len ( charset ) != parent . numGlyphs ) :
charset = charset [ : parent . numGlyphs ]
return charset
def write ( self , parent , value ) :
return 0 # dummy value
def xmlWrite ( self , xmlWriter , name , value ) :
# XXX only write charset when not in OT/TTX context, where we
# dump charset as a separate "GlyphOrder" table.
# # xmlWriter.simpletag("charset")
xmlWriter . comment ( " charset is dumped separately as the ' GlyphOrder ' element " )
xmlWriter . newline ( )
def xmlRead ( self , name , attrs , content , parent ) :
pass
1999-12-16 21:34:53 +00:00
2013-11-28 14:26:58 -05:00
class CharsetCompiler ( object ) :
2022-12-13 11:26:36 +00:00
def __init__ ( self , strings , charset , parent ) :
assert charset [ 0 ] == " .notdef "
isCID = hasattr ( parent . dictObj , " ROS " )
data0 = packCharset0 ( charset , isCID , strings )
data = packCharset ( charset , isCID , strings )
if len ( data ) < len ( data0 ) :
self . data = data
else :
self . data = data0
self . parent = parent
2015-04-26 02:01:01 -04:00
2022-12-13 11:26:36 +00:00
def setPos ( self , pos , endPos ) :
self . parent . rawDict [ " charset " ] = pos
2015-04-26 02:01:01 -04:00
2022-12-13 11:26:36 +00:00
def getDataLength ( self ) :
return len ( self . data )
2015-04-26 02:01:01 -04:00
2022-12-13 11:26:36 +00:00
def toFile ( self , file ) :
file . write ( self . data )
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2016-09-22 15:33:53 -07:00
def getStdCharSet ( charset ) :
2022-12-13 11:26:36 +00:00
# check to see if we can use a predefined charset value.
predefinedCharSetVal = None
predefinedCharSets = [
( cffISOAdobeStringCount , cffISOAdobeStrings , 0 ) ,
( cffExpertStringCount , cffIExpertStrings , 1 ) ,
( cffExpertSubsetStringCount , cffExpertSubsetStrings , 2 ) ,
]
lcs = len ( charset )
for cnt , pcs , csv in predefinedCharSets :
if predefinedCharSetVal is not None :
break
if lcs > cnt :
continue
predefinedCharSetVal = csv
for i in range ( lcs ) :
if charset [ i ] != pcs [ i ] :
predefinedCharSetVal = None
break
return predefinedCharSetVal
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2003-08-22 19:53:32 +00:00
def getCIDfromName ( name , strings ) :
2022-12-13 11:26:36 +00:00
return int ( name [ 3 : ] )
2003-08-22 19:53:32 +00:00
2017-06-21 10:32:58 +01:00
2003-08-22 19:53:32 +00:00
def getSIDfromName ( name , strings ) :
2022-12-13 11:26:36 +00:00
return strings . getSID ( name )
2003-08-22 19:53:32 +00:00
2017-06-21 10:32:58 +01:00
2003-08-22 19:53:32 +00:00
def packCharset0 ( charset , isCID , strings ) :
2022-12-13 11:26:36 +00:00
fmt = 0
data = [ packCard8 ( fmt ) ]
if isCID :
getNameID = getCIDfromName
else :
getNameID = getSIDfromName
2003-08-22 19:53:32 +00:00
2022-12-13 11:26:36 +00:00
for name in charset [ 1 : ] :
data . append ( packCard16 ( getNameID ( name , strings ) ) )
return bytesjoin ( data )
2002-05-24 10:35:13 +00:00
2003-08-22 19:53:32 +00:00
def packCharset ( charset , isCID , strings ) :
2022-12-13 11:26:36 +00:00
fmt = 1
ranges = [ ]
first = None
end = 0
if isCID :
getNameID = getCIDfromName
else :
getNameID = getSIDfromName
for name in charset [ 1 : ] :
SID = getNameID ( name , strings )
if first is None :
first = SID
elif end + 1 != SID :
nLeft = end - first
if nLeft > 255 :
fmt = 2
ranges . append ( ( first , nLeft ) )
first = SID
end = SID
if end :
nLeft = end - first
if nLeft > 255 :
fmt = 2
ranges . append ( ( first , nLeft ) )
data = [ packCard8 ( fmt ) ]
if fmt == 1 :
nLeftFunc = packCard8
else :
nLeftFunc = packCard16
for first , nLeft in ranges :
data . append ( packCard16 ( first ) + nLeftFunc ( nLeft ) )
return bytesjoin ( data )
2002-05-24 10:35:13 +00:00
2017-06-21 10:32:58 +01:00
2018-02-22 22:46:46 +09:00
def parseCharset0 ( numGlyphs , file , strings , isCID ) :
2022-12-13 11:26:36 +00:00
charset = [ " .notdef " ]
if isCID :
for i in range ( numGlyphs - 1 ) :
CID = readCard16 ( file )
charset . append ( " cid " + str ( CID ) . zfill ( 5 ) )
else :
for i in range ( numGlyphs - 1 ) :
SID = readCard16 ( file )
charset . append ( strings [ SID ] )
return charset
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2018-02-22 22:46:46 +09:00
def parseCharset ( numGlyphs , file , strings , isCID , fmt ) :
2022-12-13 11:26:36 +00:00
charset = [ " .notdef " ]
count = 1
if fmt == 1 :
nLeftFunc = readCard8
else :
nLeftFunc = readCard16
while count < numGlyphs :
first = readCard16 ( file )
nLeft = nLeftFunc ( file )
if isCID :
for CID in range ( first , first + nLeft + 1 ) :
charset . append ( " cid " + str ( CID ) . zfill ( 5 ) )
else :
for SID in range ( first , first + nLeft + 1 ) :
charset . append ( strings [ SID ] )
count = count + nLeft + 1
return charset
1999-12-16 21:34:53 +00:00
2013-11-28 14:26:58 -05:00
class EncodingCompiler ( object ) :
2022-12-13 11:26:36 +00:00
def __init__ ( self , strings , encoding , parent ) :
assert not isinstance ( encoding , str )
data0 = packEncoding0 ( parent . dictObj . charset , encoding , parent . strings )
data1 = packEncoding1 ( parent . dictObj . charset , encoding , parent . strings )
if len ( data0 ) < len ( data1 ) :
self . data = data0
else :
self . data = data1
self . parent = parent
2003-01-03 20:56:01 +00:00
2022-12-13 11:26:36 +00:00
def setPos ( self , pos , endPos ) :
self . parent . rawDict [ " Encoding " ] = pos
2015-04-26 02:01:01 -04:00
2022-12-13 11:26:36 +00:00
def getDataLength ( self ) :
return len ( self . data )
2015-04-26 02:01:01 -04:00
2022-12-13 11:26:36 +00:00
def toFile ( self , file ) :
file . write ( self . data )
2003-01-03 20:56:01 +00:00
class EncodingConverter ( SimpleConverter ) :
2022-12-13 11:26:36 +00:00
def _read ( self , parent , value ) :
if value == 0 :
return " StandardEncoding "
elif value == 1 :
return " ExpertEncoding "
else :
assert value > 1
file = parent . file
file . seek ( value )
log . log ( DEBUG , " loading Encoding at %s " , value )
fmt = readCard8 ( file )
haveSupplement = fmt & 0x80
if haveSupplement :
raise NotImplementedError ( " Encoding supplements are not yet supported " )
fmt = fmt & 0x7F
if fmt == 0 :
encoding = parseEncoding0 (
parent . charset , file , haveSupplement , parent . strings
)
elif fmt == 1 :
encoding = parseEncoding1 (
parent . charset , file , haveSupplement , parent . strings
)
return encoding
def write ( self , parent , value ) :
if value == " StandardEncoding " :
return 0
elif value == " ExpertEncoding " :
return 1
return 0 # dummy value
def xmlWrite ( self , xmlWriter , name , value ) :
if value in ( " StandardEncoding " , " ExpertEncoding " ) :
xmlWriter . simpletag ( name , name = value )
xmlWriter . newline ( )
return
xmlWriter . begintag ( name )
xmlWriter . newline ( )
for code in range ( len ( value ) ) :
glyphName = value [ code ]
if glyphName != " .notdef " :
xmlWriter . simpletag ( " map " , code = hex ( code ) , name = glyphName )
xmlWriter . newline ( )
xmlWriter . endtag ( name )
xmlWriter . newline ( )
def xmlRead ( self , name , attrs , content , parent ) :
if " name " in attrs :
return attrs [ " name " ]
encoding = [ " .notdef " ] * 256
for element in content :
if isinstance ( element , str ) :
continue
name , attrs , content = element
code = safeEval ( attrs [ " code " ] )
glyphName = attrs [ " name " ]
encoding [ code ] = glyphName
return encoding
2003-01-03 20:56:01 +00:00
2018-02-22 22:46:46 +09:00
def parseEncoding0 ( charset , file , haveSupplement , strings ) :
2022-12-13 11:26:36 +00:00
nCodes = readCard8 ( file )
encoding = [ " .notdef " ] * 256
for glyphID in range ( 1 , nCodes + 1 ) :
code = readCard8 ( file )
if code != 0 :
encoding [ code ] = charset [ glyphID ]
return encoding
2003-01-03 20:56:01 +00:00
2017-06-21 10:32:58 +01:00
2018-02-22 22:46:46 +09:00
def parseEncoding1 ( charset , file , haveSupplement , strings ) :
2022-12-13 11:26:36 +00:00
nRanges = readCard8 ( file )
encoding = [ " .notdef " ] * 256
glyphID = 1
for i in range ( nRanges ) :
code = readCard8 ( file )
nLeft = readCard8 ( file )
for glyphID in range ( glyphID , glyphID + nLeft + 1 ) :
encoding [ code ] = charset [ glyphID ]
code = code + 1
glyphID = glyphID + 1
return encoding
2003-01-03 20:56:01 +00:00
2017-06-21 10:32:58 +01:00
2003-01-03 20:56:01 +00:00
def packEncoding0 ( charset , encoding , strings ) :
2022-12-13 11:26:36 +00:00
fmt = 0
m = { }
for code in range ( len ( encoding ) ) :
name = encoding [ code ]
if name != " .notdef " :
m [ name ] = code
codes = [ ]
for name in charset [ 1 : ] :
code = m . get ( name )
codes . append ( code )
while codes and codes [ - 1 ] is None :
codes . pop ( )
data = [ packCard8 ( fmt ) , packCard8 ( len ( codes ) ) ]
for code in codes :
if code is None :
code = 0
data . append ( packCard8 ( code ) )
return bytesjoin ( data )
2003-01-03 20:56:01 +00:00
2017-06-21 10:32:58 +01:00
2003-01-03 20:56:01 +00:00
def packEncoding1 ( charset , encoding , strings ) :
2022-12-13 11:26:36 +00:00
fmt = 1
m = { }
for code in range ( len ( encoding ) ) :
name = encoding [ code ]
if name != " .notdef " :
m [ name ] = code
ranges = [ ]
first = None
end = 0
for name in charset [ 1 : ] :
code = m . get ( name , - 1 )
if first is None :
first = code
elif end + 1 != code :
nLeft = end - first
ranges . append ( ( first , nLeft ) )
first = code
end = code
nLeft = end - first
ranges . append ( ( first , nLeft ) )
# remove unencoded glyphs at the end.
while ranges and ranges [ - 1 ] [ 0 ] == - 1 :
ranges . pop ( )
data = [ packCard8 ( fmt ) , packCard8 ( len ( ranges ) ) ]
for first , nLeft in ranges :
if first == - 1 : # unencoded
first = 0
data . append ( packCard8 ( first ) + packCard8 ( nLeft ) )
return bytesjoin ( data )
2003-01-03 20:56:01 +00:00
2002-05-24 09:58:04 +00:00
class FDArrayConverter ( TableConverter ) :
2022-12-13 11:26:36 +00:00
def _read ( self , parent , value ) :
try :
vstore = parent . VarStore
except AttributeError :
vstore = None
file = parent . file
isCFF2 = parent . _isCFF2
file . seek ( value )
fdArray = FDArrayIndex ( file , isCFF2 = isCFF2 )
fdArray . vstore = vstore
fdArray . strings = parent . strings
fdArray . GlobalSubrs = parent . GlobalSubrs
return fdArray
def write ( self , parent , value ) :
return 0 # dummy value
def xmlRead ( self , name , attrs , content , parent ) :
fdArray = FDArrayIndex ( )
for element in content :
if isinstance ( element , str ) :
continue
name , attrs , content = element
fdArray . fromXML ( name , attrs , content )
return fdArray
2003-08-22 19:53:32 +00:00
2002-05-17 18:36:07 +00:00
2018-02-23 02:01:35 +09:00
class FDSelectConverter ( SimpleConverter ) :
2022-12-13 11:26:36 +00:00
def _read ( self , parent , value ) :
file = parent . file
file . seek ( value )
fdSelect = FDSelect ( file , parent . numGlyphs )
return fdSelect
2003-08-22 19:53:32 +00:00
2022-12-13 11:26:36 +00:00
def write ( self , parent , value ) :
return 0 # dummy value
2003-08-22 19:53:32 +00:00
2022-12-13 11:26:36 +00:00
# The FDSelect glyph data is written out to XML in the charstring keys,
# so we write out only the format selector
def xmlWrite ( self , xmlWriter , name , value ) :
xmlWriter . simpletag ( name , [ ( " format " , value . format ) ] )
xmlWriter . newline ( )
2003-08-22 19:53:32 +00:00
2022-12-13 11:26:36 +00:00
def xmlRead ( self , name , attrs , content , parent ) :
fmt = safeEval ( attrs [ " format " ] )
file = None
numGlyphs = None
fdSelect = FDSelect ( file , numGlyphs , fmt )
return fdSelect
2015-04-26 02:01:01 -04:00
2017-06-21 10:32:58 +01:00
2017-01-12 15:23:12 -08:00
class VarStoreConverter ( SimpleConverter ) :
2022-12-13 11:26:36 +00:00
def _read ( self , parent , value ) :
file = parent . file
file . seek ( value )
varStore = VarStoreData ( file )
varStore . decompile ( )
return varStore
2017-06-21 10:32:58 +01:00
2022-12-13 11:26:36 +00:00
def write ( self , parent , value ) :
return 0 # dummy value
2017-03-09 21:30:28 -08:00
2022-12-13 11:26:36 +00:00
def xmlWrite ( self , xmlWriter , name , value ) :
value . writeXML ( xmlWriter , name )
2017-03-09 21:30:28 -08:00
2022-12-13 11:26:36 +00:00
def xmlRead ( self , name , attrs , content , parent ) :
varStore = VarStoreData ( )
varStore . xmlRead ( name , attrs , content , parent )
return varStore
2017-03-09 21:30:28 -08:00
2003-08-22 19:53:32 +00:00
def packFDSelect0 ( fdSelectArray ) :
2022-12-13 11:26:36 +00:00
fmt = 0
data = [ packCard8 ( fmt ) ]
for index in fdSelectArray :
data . append ( packCard8 ( index ) )
return bytesjoin ( data )
2003-08-22 19:53:32 +00:00
def packFDSelect3 ( fdSelectArray ) :
2022-12-13 11:26:36 +00:00
fmt = 3
fdRanges = [ ]
lenArray = len ( fdSelectArray )
lastFDIndex = - 1
for i in range ( lenArray ) :
fdIndex = fdSelectArray [ i ]
if lastFDIndex != fdIndex :
fdRanges . append ( [ i , fdIndex ] )
lastFDIndex = fdIndex
sentinelGID = i + 1
data = [ packCard8 ( fmt ) ]
data . append ( packCard16 ( len ( fdRanges ) ) )
for fdRange in fdRanges :
data . append ( packCard16 ( fdRange [ 0 ] ) )
data . append ( packCard8 ( fdRange [ 1 ] ) )
data . append ( packCard16 ( sentinelGID ) )
return bytesjoin ( data )
2003-08-22 19:53:32 +00:00
2019-08-01 21:28:46 +02:00
def packFDSelect4 ( fdSelectArray ) :
2022-12-13 11:26:36 +00:00
fmt = 4
fdRanges = [ ]
lenArray = len ( fdSelectArray )
lastFDIndex = - 1
for i in range ( lenArray ) :
fdIndex = fdSelectArray [ i ]
if lastFDIndex != fdIndex :
fdRanges . append ( [ i , fdIndex ] )
lastFDIndex = fdIndex
sentinelGID = i + 1
data = [ packCard8 ( fmt ) ]
data . append ( packCard32 ( len ( fdRanges ) ) )
for fdRange in fdRanges :
data . append ( packCard32 ( fdRange [ 0 ] ) )
data . append ( packCard16 ( fdRange [ 1 ] ) )
data . append ( packCard32 ( sentinelGID ) )
return bytesjoin ( data )
2019-08-01 21:28:46 +02:00
2013-11-28 14:26:58 -05:00
class FDSelectCompiler ( object ) :
2022-12-13 11:26:36 +00:00
def __init__ ( self , fdSelect , parent ) :
fmt = fdSelect . format
fdSelectArray = fdSelect . gidArray
if fmt == 0 :
self . data = packFDSelect0 ( fdSelectArray )
elif fmt == 3 :
self . data = packFDSelect3 ( fdSelectArray )
elif fmt == 4 :
self . data = packFDSelect4 ( fdSelectArray )
else :
# choose smaller of the two formats
data0 = packFDSelect0 ( fdSelectArray )
data3 = packFDSelect3 ( fdSelectArray )
if len ( data0 ) < len ( data3 ) :
self . data = data0
fdSelect . format = 0
else :
self . data = data3
fdSelect . format = 3
self . parent = parent
def setPos ( self , pos , endPos ) :
self . parent . rawDict [ " FDSelect " ] = pos
def getDataLength ( self ) :
return len ( self . data )
def toFile ( self , file ) :
file . write ( self . data )
2002-05-17 18:36:07 +00:00
2017-06-21 10:32:58 +01:00
2017-01-12 15:23:12 -08:00
class VarStoreCompiler ( object ) :
2022-12-13 11:26:36 +00:00
def __init__ ( self , varStoreData , parent ) :
self . parent = parent
if not varStoreData . data :
varStoreData . compile ( )
data = [ packCard16 ( len ( varStoreData . data ) ) , varStoreData . data ]
self . data = bytesjoin ( data )
2017-01-12 15:23:12 -08:00
2022-12-13 11:26:36 +00:00
def setPos ( self , pos , endPos ) :
self . parent . rawDict [ " VarStore " ] = pos
2017-01-12 15:23:12 -08:00
2022-12-13 11:26:36 +00:00
def getDataLength ( self ) :
return len ( self . data )
2017-01-12 15:23:12 -08:00
2022-12-13 11:26:36 +00:00
def toFile ( self , file ) :
file . write ( self . data )
2002-05-17 18:36:07 +00:00
2017-06-21 10:32:58 +01:00
2002-05-24 09:58:04 +00:00
class ROSConverter ( SimpleConverter ) :
2022-12-13 11:26:36 +00:00
def xmlWrite ( self , xmlWriter , name , value ) :
registry , order , supplement = value
xmlWriter . simpletag (
name ,
[
( " Registry " , tostr ( registry ) ) ,
( " Order " , tostr ( order ) ) ,
( " Supplement " , supplement ) ,
] ,
)
xmlWriter . newline ( )
def xmlRead ( self , name , attrs , content , parent ) :
return ( attrs [ " Registry " ] , attrs [ " Order " ] , safeEval ( attrs [ " Supplement " ] ) )
2003-08-22 19:53:32 +00:00
2002-05-16 18:17:32 +00:00
topDictOperators = [
2022-12-13 11:26:36 +00:00
# opcode name argument type default converter
( 25 , " maxstack " , " number " , None , None ) ,
( ( 12 , 30 ) , " ROS " , ( " SID " , " SID " , " number " ) , None , ROSConverter ( ) ) ,
( ( 12 , 20 ) , " SyntheticBase " , " number " , None , None ) ,
( 0 , " version " , " SID " , None , None ) ,
( 1 , " Notice " , " SID " , None , Latin1Converter ( ) ) ,
( ( 12 , 0 ) , " Copyright " , " SID " , None , Latin1Converter ( ) ) ,
2023-07-10 18:12:49 +01:00
( 2 , " FullName " , " SID " , None , Latin1Converter ( ) ) ,
( ( 12 , 38 ) , " FontName " , " SID " , None , Latin1Converter ( ) ) ,
( 3 , " FamilyName " , " SID " , None , Latin1Converter ( ) ) ,
2022-12-13 11:26:36 +00:00
( 4 , " Weight " , " SID " , None , None ) ,
( ( 12 , 1 ) , " isFixedPitch " , " number " , 0 , None ) ,
( ( 12 , 2 ) , " ItalicAngle " , " number " , 0 , None ) ,
( ( 12 , 3 ) , " UnderlinePosition " , " number " , - 100 , None ) ,
( ( 12 , 4 ) , " UnderlineThickness " , " number " , 50 , None ) ,
( ( 12 , 5 ) , " PaintType " , " number " , 0 , None ) ,
( ( 12 , 6 ) , " CharstringType " , " number " , 2 , None ) ,
( ( 12 , 7 ) , " FontMatrix " , " array " , [ 0.001 , 0 , 0 , 0.001 , 0 , 0 ] , None ) ,
( 13 , " UniqueID " , " number " , None , None ) ,
( 5 , " FontBBox " , " array " , [ 0 , 0 , 0 , 0 ] , None ) ,
( ( 12 , 8 ) , " StrokeWidth " , " number " , 0 , None ) ,
( 14 , " XUID " , " array " , None , None ) ,
( ( 12 , 21 ) , " PostScript " , " SID " , None , None ) ,
( ( 12 , 22 ) , " BaseFontName " , " SID " , None , None ) ,
( ( 12 , 23 ) , " BaseFontBlend " , " delta " , None , None ) ,
( ( 12 , 31 ) , " CIDFontVersion " , " number " , 0 , None ) ,
( ( 12 , 32 ) , " CIDFontRevision " , " number " , 0 , None ) ,
( ( 12 , 33 ) , " CIDFontType " , " number " , 0 , None ) ,
( ( 12 , 34 ) , " CIDCount " , " number " , 8720 , None ) ,
( 15 , " charset " , " number " , None , CharsetConverter ( ) ) ,
( ( 12 , 35 ) , " UIDBase " , " number " , None , None ) ,
( 16 , " Encoding " , " number " , 0 , EncodingConverter ( ) ) ,
( 18 , " Private " , ( " number " , " number " ) , None , PrivateDictConverter ( ) ) ,
( ( 12 , 37 ) , " FDSelect " , " number " , None , FDSelectConverter ( ) ) ,
( ( 12 , 36 ) , " FDArray " , " number " , None , FDArrayConverter ( ) ) ,
( 17 , " CharStrings " , " number " , None , CharStringsConverter ( ) ) ,
( 24 , " VarStore " , " number " , None , VarStoreConverter ( ) ) ,
2002-05-16 18:17:32 +00:00
]
2002-05-15 07:41:30 +00:00
2017-01-12 15:23:12 -08:00
topDictOperators2 = [
2022-12-13 11:26:36 +00:00
# opcode name argument type default converter
( 25 , " maxstack " , " number " , None , None ) ,
( ( 12 , 7 ) , " FontMatrix " , " array " , [ 0.001 , 0 , 0 , 0.001 , 0 , 0 ] , None ) ,
( ( 12 , 37 ) , " FDSelect " , " number " , None , FDSelectConverter ( ) ) ,
( ( 12 , 36 ) , " FDArray " , " number " , None , FDArrayConverter ( ) ) ,
( 17 , " CharStrings " , " number " , None , CharStringsConverter ( ) ) ,
( 24 , " VarStore " , " number " , None , VarStoreConverter ( ) ) ,
2017-01-12 15:23:12 -08:00
]
2003-08-22 19:53:32 +00:00
# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
# in order for the font to compile back from xml.
2017-01-12 15:23:12 -08:00
kBlendDictOpName = " blend "
blendOp = 23
2003-08-22 19:53:32 +00:00
2002-05-16 18:17:32 +00:00
privateDictOperators = [
2022-12-13 11:26:36 +00:00
# opcode name argument type default converter
( 22 , " vsindex " , " number " , None , None ) ,
(
blendOp ,
kBlendDictOpName ,
" blendList " ,
None ,
None ,
) , # This is for reading to/from XML: it not written to CFF.
( 6 , " BlueValues " , " delta " , None , None ) ,
( 7 , " OtherBlues " , " delta " , None , None ) ,
( 8 , " FamilyBlues " , " delta " , None , None ) ,
( 9 , " FamilyOtherBlues " , " delta " , None , None ) ,
( ( 12 , 9 ) , " BlueScale " , " number " , 0.039625 , None ) ,
( ( 12 , 10 ) , " BlueShift " , " number " , 7 , None ) ,
( ( 12 , 11 ) , " BlueFuzz " , " number " , 1 , None ) ,
( 10 , " StdHW " , " number " , None , None ) ,
( 11 , " StdVW " , " number " , None , None ) ,
( ( 12 , 12 ) , " StemSnapH " , " delta " , None , None ) ,
( ( 12 , 13 ) , " StemSnapV " , " delta " , None , None ) ,
( ( 12 , 14 ) , " ForceBold " , " number " , 0 , None ) ,
( ( 12 , 15 ) , " ForceBoldThreshold " , " number " , None , None ) , # deprecated
( ( 12 , 16 ) , " lenIV " , " number " , None , None ) , # deprecated
( ( 12 , 17 ) , " LanguageGroup " , " number " , 0 , None ) ,
( ( 12 , 18 ) , " ExpansionFactor " , " number " , 0.06 , None ) ,
( ( 12 , 19 ) , " initialRandomSeed " , " number " , 0 , None ) ,
( 20 , " defaultWidthX " , " number " , 0 , None ) ,
( 21 , " nominalWidthX " , " number " , 0 , None ) ,
( 19 , " Subrs " , " number " , None , SubrsConverter ( ) ) ,
2002-05-16 18:17:32 +00:00
]
2002-05-15 07:41:30 +00:00
2017-01-12 15:23:12 -08:00
privateDictOperators2 = [
2022-12-13 11:26:36 +00:00
# opcode name argument type default converter
( 22 , " vsindex " , " number " , None , None ) ,
(
blendOp ,
kBlendDictOpName ,
" blendList " ,
None ,
None ,
) , # This is for reading to/from XML: it not written to CFF.
( 6 , " BlueValues " , " delta " , None , None ) ,
( 7 , " OtherBlues " , " delta " , None , None ) ,
( 8 , " FamilyBlues " , " delta " , None , None ) ,
( 9 , " FamilyOtherBlues " , " delta " , None , None ) ,
( ( 12 , 9 ) , " BlueScale " , " number " , 0.039625 , None ) ,
( ( 12 , 10 ) , " BlueShift " , " number " , 7 , None ) ,
( ( 12 , 11 ) , " BlueFuzz " , " number " , 1 , None ) ,
( 10 , " StdHW " , " number " , None , None ) ,
( 11 , " StdVW " , " number " , None , None ) ,
( ( 12 , 12 ) , " StemSnapH " , " delta " , None , None ) ,
( ( 12 , 13 ) , " StemSnapV " , " delta " , None , None ) ,
( ( 12 , 17 ) , " LanguageGroup " , " number " , 0 , None ) ,
( ( 12 , 18 ) , " ExpansionFactor " , " number " , 0.06 , None ) ,
( 19 , " Subrs " , " number " , None , SubrsConverter ( ) ) ,
2017-01-12 15:23:12 -08:00
]
2017-06-21 10:32:58 +01:00
2002-05-24 09:58:04 +00:00
def addConverters ( table ) :
2022-12-13 11:26:36 +00:00
for i in range ( len ( table ) ) :
op , name , arg , default , conv = table [ i ]
if conv is not None :
continue
if arg in ( " delta " , " array " ) :
conv = ArrayConverter ( )
elif arg == " number " :
conv = NumberConverter ( )
elif arg == " SID " :
conv = ASCIIConverter ( )
elif arg == " blendList " :
conv = None
else :
assert False
table [ i ] = op , name , arg , default , conv
2002-05-24 09:58:04 +00:00
2017-06-21 10:32:58 +01:00
2002-05-24 09:58:04 +00:00
addConverters ( privateDictOperators )
addConverters ( topDictOperators )
2002-05-15 07:41:30 +00:00
2002-05-16 18:17:32 +00:00
class TopDictDecompiler ( psCharStrings . DictDecompiler ) :
2022-12-13 11:26:36 +00:00
operators = buildOperatorDict ( topDictOperators )
2002-05-15 07:41:30 +00:00
2017-06-21 10:32:58 +01:00
2002-05-16 18:17:32 +00:00
class PrivateDictDecompiler ( psCharStrings . DictDecompiler ) :
2022-12-13 11:26:36 +00:00
operators = buildOperatorDict ( privateDictOperators )
2002-05-14 12:22:03 +00:00
2017-06-21 10:32:58 +01:00
2013-11-28 14:26:58 -05:00
class DictCompiler ( object ) :
2022-12-13 11:26:36 +00:00
maxBlendStack = 0
def __init__ ( self , dictObj , strings , parent , isCFF2 = None ) :
if strings :
assert isinstance ( strings , IndexedStrings )
if isCFF2 is None and hasattr ( parent , " isCFF2 " ) :
isCFF2 = parent . isCFF2
assert isCFF2 is not None
self . isCFF2 = isCFF2
self . dictObj = dictObj
self . strings = strings
self . parent = parent
rawDict = { }
for name in dictObj . order :
value = getattr ( dictObj , name , None )
if value is None :
continue
conv = dictObj . converters [ name ]
value = conv . write ( dictObj , value )
if value == dictObj . defaults . get ( name ) :
continue
rawDict [ name ] = value
self . rawDict = rawDict
def setPos ( self , pos , endPos ) :
pass
def getDataLength ( self ) :
return len ( self . compile ( " getDataLength " ) )
def compile ( self , reason ) :
log . log ( DEBUG , " -- compiling %s for %s " , self . __class__ . __name__ , reason )
rawDict = self . rawDict
data = [ ]
for name in self . dictObj . order :
value = rawDict . get ( name )
if value is None :
continue
op , argType = self . opcodes [ name ]
if isinstance ( argType , tuple ) :
l = len ( argType )
assert len ( value ) == l , " value doesn ' t match arg type "
for i in range ( l ) :
arg = argType [ i ]
v = value [ i ]
arghandler = getattr ( self , " arg_ " + arg )
data . append ( arghandler ( v ) )
else :
arghandler = getattr ( self , " arg_ " + argType )
data . append ( arghandler ( value ) )
data . append ( op )
data = bytesjoin ( data )
return data
def toFile ( self , file ) :
data = self . compile ( " toFile " )
file . write ( data )
def arg_number ( self , num ) :
if isinstance ( num , list ) :
data = [ encodeNumber ( val ) for val in num ]
data . append ( encodeNumber ( 1 ) )
data . append ( bytechr ( blendOp ) )
datum = bytesjoin ( data )
else :
datum = encodeNumber ( num )
return datum
def arg_SID ( self , s ) :
return psCharStrings . encodeIntCFF ( self . strings . getSID ( s ) )
def arg_array ( self , value ) :
data = [ ]
for num in value :
data . append ( self . arg_number ( num ) )
return bytesjoin ( data )
def arg_delta ( self , value ) :
if not value :
return b " "
val0 = value [ 0 ]
if isinstance ( val0 , list ) :
data = self . arg_delta_blend ( value )
else :
out = [ ]
last = 0
for v in value :
out . append ( v - last )
last = v
data = [ ]
for num in out :
data . append ( encodeNumber ( num ) )
return bytesjoin ( data )
def arg_delta_blend ( self , value ) :
""" A delta list with blend lists has to be *all* blend lists.
The value is a list is arranged as follows : :
[
[ V0 , d0 . . dn ]
[ V1 , d0 . . dn ]
. . .
[ Vm , d0 . . dn ]
]
` ` V ` ` is the absolute coordinate value from the default font , and ` ` d0 - dn ` `
are the delta values from the * n * regions . Each ` ` V ` ` is an absolute
coordinate from the default font .
We want to return a list : :
[
[ v0 , v1 . . vm ]
[ d0 . . dn ]
. . .
[ d0 . . dn ]
numBlends
blendOp
]
where each ` ` v ` ` is relative to the previous default font value .
"""
numMasters = len ( value [ 0 ] )
numBlends = len ( value )
numStack = ( numBlends * numMasters ) + 1
if numStack > self . maxBlendStack :
# Figure out the max number of value we can blend
# and divide this list up into chunks of that size.
numBlendValues = int ( ( self . maxBlendStack - 1 ) / numMasters )
out = [ ]
while True :
numVal = min ( len ( value ) , numBlendValues )
if numVal == 0 :
break
valList = value [ 0 : numVal ]
out1 = self . arg_delta_blend ( valList )
out . extend ( out1 )
value = value [ numVal : ]
else :
firstList = [ 0 ] * numBlends
deltaList = [ None ] * numBlends
i = 0
prevVal = 0
while i < numBlends :
# For PrivateDict BlueValues, the default font
# values are absolute, not relative.
# Must convert these back to relative coordinates
# befor writing to CFF2.
defaultValue = value [ i ] [ 0 ]
firstList [ i ] = defaultValue - prevVal
prevVal = defaultValue
deltaList [ i ] = value [ i ] [ 1 : ]
i + = 1
relValueList = firstList
for blendList in deltaList :
relValueList . extend ( blendList )
out = [ encodeNumber ( val ) for val in relValueList ]
out . append ( encodeNumber ( numBlends ) )
out . append ( bytechr ( blendOp ) )
return out
2017-01-12 15:23:12 -08:00
2002-05-23 21:50:36 +00:00
def encodeNumber ( num ) :
2022-12-13 11:26:36 +00:00
if isinstance ( num , float ) :
return psCharStrings . encodeFloat ( num )
else :
return psCharStrings . encodeIntCFF ( num )
2002-05-23 21:50:36 +00:00
class TopDictCompiler ( DictCompiler ) :
2022-12-13 11:26:36 +00:00
opcodes = buildOpcodeDict ( topDictOperators )
def getChildren ( self , strings ) :
isCFF2 = self . isCFF2
children = [ ]
if self . dictObj . cff2GetGlyphOrder is None :
if hasattr ( self . dictObj , " charset " ) and self . dictObj . charset :
if hasattr ( self . dictObj , " ROS " ) : # aka isCID
charsetCode = None
else :
charsetCode = getStdCharSet ( self . dictObj . charset )
if charsetCode is None :
children . append (
CharsetCompiler ( strings , self . dictObj . charset , self )
)
else :
self . rawDict [ " charset " ] = charsetCode
if hasattr ( self . dictObj , " Encoding " ) and self . dictObj . Encoding :
encoding = self . dictObj . Encoding
if not isinstance ( encoding , str ) :
children . append ( EncodingCompiler ( strings , encoding , self ) )
else :
if hasattr ( self . dictObj , " VarStore " ) :
varStoreData = self . dictObj . VarStore
varStoreComp = VarStoreCompiler ( varStoreData , self )
children . append ( varStoreComp )
if hasattr ( self . dictObj , " FDSelect " ) :
# I have not yet supported merging a ttx CFF-CID font, as there are
# interesting issues about merging the FDArrays. Here I assume that
# either the font was read from XML, and the FDSelect indices are all
# in the charstring data, or the FDSelect array is already fully defined.
fdSelect = self . dictObj . FDSelect
# probably read in from XML; assume fdIndex in CharString data
if len ( fdSelect ) == 0 :
charStrings = self . dictObj . CharStrings
for name in self . dictObj . charset :
fdSelect . append ( charStrings [ name ] . fdSelectIndex )
fdSelectComp = FDSelectCompiler ( fdSelect , self )
children . append ( fdSelectComp )
if hasattr ( self . dictObj , " CharStrings " ) :
items = [ ]
charStrings = self . dictObj . CharStrings
for name in self . dictObj . charset :
items . append ( charStrings [ name ] )
charStringsComp = CharStringsCompiler ( items , strings , self , isCFF2 = isCFF2 )
children . append ( charStringsComp )
if hasattr ( self . dictObj , " FDArray " ) :
# I have not yet supported merging a ttx CFF-CID font, as there are
# interesting issues about merging the FDArrays. Here I assume that the
# FDArray info is correct and complete.
fdArrayIndexComp = self . dictObj . FDArray . getCompiler ( strings , self )
children . append ( fdArrayIndexComp )
children . extend ( fdArrayIndexComp . getChildren ( strings ) )
if hasattr ( self . dictObj , " Private " ) :
privComp = self . dictObj . Private . getCompiler ( strings , self )
children . append ( privComp )
children . extend ( privComp . getChildren ( strings ) )
return children
2003-08-22 19:53:32 +00:00
2017-05-09 15:32:12 -07:00
class FontDictCompiler ( DictCompiler ) :
2022-12-13 11:26:36 +00:00
opcodes = buildOpcodeDict ( topDictOperators )
def __init__ ( self , dictObj , strings , parent , isCFF2 = None ) :
super ( FontDictCompiler , self ) . __init__ ( dictObj , strings , parent , isCFF2 = isCFF2 )
#
# We now take some effort to detect if there were any key/value pairs
# supplied that were ignored in the FontDict context, and issue a warning
# for those cases.
#
ignoredNames = [ ]
dictObj = self . dictObj
for name in sorted ( set ( dictObj . converters ) - set ( dictObj . order ) ) :
if name in dictObj . rawDict :
# The font was directly read from binary. In this
# case, we want to report *all* "useless" key/value
# pairs that are in the font, not just the ones that
# are different from the default.
ignoredNames . append ( name )
else :
# The font was probably read from a TTX file. We only
# warn about keys whos value is not the default. The
# ones that have the default value will not be written
# to binary anyway.
default = dictObj . defaults . get ( name )
if default is not None :
conv = dictObj . converters [ name ]
default = conv . read ( dictObj , default )
if getattr ( dictObj , name , None ) != default :
ignoredNames . append ( name )
if ignoredNames :
log . warning (
" Some CFF FDArray/FontDict keys were ignored upon compile: "
+ " " . join ( sorted ( ignoredNames ) )
)
def getChildren ( self , strings ) :
children = [ ]
if hasattr ( self . dictObj , " Private " ) :
privComp = self . dictObj . Private . getCompiler ( strings , self )
children . append ( privComp )
children . extend ( privComp . getChildren ( strings ) )
return children
2016-11-26 12:36:20 +01:00
2002-05-23 21:50:36 +00:00
class PrivateDictCompiler ( DictCompiler ) :
2022-12-13 11:26:36 +00:00
maxBlendStack = maxStackLimit
opcodes = buildOpcodeDict ( privateDictOperators )
2015-04-26 02:01:01 -04:00
2022-12-13 11:26:36 +00:00
def setPos ( self , pos , endPos ) :
size = endPos - pos
self . parent . rawDict [ " Private " ] = size , pos
self . pos = pos
2015-04-26 02:01:01 -04:00
2022-12-13 11:26:36 +00:00
def getChildren ( self , strings ) :
children = [ ]
if hasattr ( self . dictObj , " Subrs " ) :
children . append ( self . dictObj . Subrs . getCompiler ( strings , self ) )
return children
2002-05-23 21:50:36 +00:00
2017-06-21 10:32:58 +01:00
2013-11-28 14:26:58 -05:00
class BaseDict ( object ) :
2022-12-13 11:26:36 +00:00
def __init__ ( self , strings = None , file = None , offset = None , isCFF2 = None ) :
assert ( isCFF2 is None ) == ( file is None )
self . rawDict = { }
self . skipNames = [ ]
self . strings = strings
if file is None :
return
self . _isCFF2 = isCFF2
self . file = file
if offset is not None :
log . log ( DEBUG , " loading %s at %s " , self . __class__ . __name__ , offset )
self . offset = offset
def decompile ( self , data ) :
log . log ( DEBUG , " length %s is %d " , self . __class__ . __name__ , len ( data ) )
dec = self . decompilerClass ( self . strings , self )
dec . decompile ( data )
self . rawDict = dec . getDict ( )
self . postDecompile ( )
def postDecompile ( self ) :
pass
def getCompiler ( self , strings , parent , isCFF2 = None ) :
return self . compilerClass ( self , strings , parent , isCFF2 = isCFF2 )
def __getattr__ ( self , name ) :
if name [ : 2 ] == name [ - 2 : ] == " __ " :
# to make deepcopy() and pickle.load() work, we need to signal with
# AttributeError that dunder methods like '__deepcopy__' or '__getstate__'
# aren't implemented. For more details, see:
# https://github.com/fonttools/fonttools/pull/1488
raise AttributeError ( name )
value = self . rawDict . get ( name , None )
if value is None :
value = self . defaults . get ( name )
if value is None :
raise AttributeError ( name )
conv = self . converters [ name ]
value = conv . read ( self , value )
setattr ( self , name , value )
return value
def toXML ( self , xmlWriter ) :
for name in self . order :
if name in self . skipNames :
continue
value = getattr ( self , name , None )
# XXX For "charset" we never skip calling xmlWrite even if the
# value is None, so we always write the following XML comment:
#
# <!-- charset is dumped separately as the 'GlyphOrder' element -->
#
# Charset is None when 'CFF ' table is imported from XML into an
# empty TTFont(). By writing this comment all the time, we obtain
# the same XML output whether roundtripping XML-to-XML or
# dumping binary-to-XML
if value is None and name != " charset " :
continue
conv = self . converters [ name ]
conv . xmlWrite ( xmlWriter , name , value )
ignoredNames = set ( self . rawDict ) - set ( self . order )
if ignoredNames :
xmlWriter . comment (
" some keys were ignored: %s " % " " . join ( sorted ( ignoredNames ) )
)
xmlWriter . newline ( )
def fromXML ( self , name , attrs , content ) :
conv = self . converters [ name ]
value = conv . xmlRead ( name , attrs , content , self )
setattr ( self , name , value )
2002-05-14 12:22:03 +00:00
2002-05-16 18:17:32 +00:00
class TopDict ( BaseDict ) :
2022-12-13 11:26:36 +00:00
""" The ``TopDict`` represents the top-level dictionary holding font
information . CFF2 tables contain a restricted set of top - level entries
as described ` here < https : / / docs . microsoft . com / en - us / typography / opentype / spec / cff2 #7-top-dict-data>`_,
but CFF tables may contain a wider range of information . This information
can be accessed through attributes or through the dictionary returned
through the ` ` rawDict ` ` property :
. . code : : python
font = tt [ " CFF " ] . cff [ 0 ]
font . FamilyName
# 'Linux Libertine O'
font . rawDict [ " FamilyName " ]
# 'Linux Libertine O'
More information is available in the CFF file ' s private dictionary, accessed
via the ` ` Private ` ` property :
. . code : : python
tt [ " CFF " ] . cff [ 0 ] . Private . BlueValues
# [-15, 0, 515, 515, 666, 666]
"""
defaults = buildDefaults ( topDictOperators )
converters = buildConverters ( topDictOperators )
compilerClass = TopDictCompiler
order = buildOrder ( topDictOperators )
decompilerClass = TopDictDecompiler
def __init__ (
self ,
strings = None ,
file = None ,
offset = None ,
GlobalSubrs = None ,
cff2GetGlyphOrder = None ,
isCFF2 = None ,
) :
super ( TopDict , self ) . __init__ ( strings , file , offset , isCFF2 = isCFF2 )
self . cff2GetGlyphOrder = cff2GetGlyphOrder
self . GlobalSubrs = GlobalSubrs
if isCFF2 :
self . defaults = buildDefaults ( topDictOperators2 )
self . charset = cff2GetGlyphOrder ( )
self . order = buildOrder ( topDictOperators2 )
else :
self . defaults = buildDefaults ( topDictOperators )
self . order = buildOrder ( topDictOperators )
def getGlyphOrder ( self ) :
""" Returns a list of glyph names in the CFF font. """
return self . charset
def postDecompile ( self ) :
offset = self . rawDict . get ( " CharStrings " )
if offset is None :
return
# get the number of glyphs beforehand.
self . file . seek ( offset )
if self . _isCFF2 :
self . numGlyphs = readCard32 ( self . file )
else :
self . numGlyphs = readCard16 ( self . file )
def toXML ( self , xmlWriter ) :
if hasattr ( self , " CharStrings " ) :
self . decompileAllCharStrings ( )
if hasattr ( self , " ROS " ) :
self . skipNames = [ " Encoding " ]
if not hasattr ( self , " ROS " ) or not hasattr ( self , " CharStrings " ) :
# these values have default values, but I only want them to show up
# in CID fonts.
self . skipNames = [
" CIDFontVersion " ,
" CIDFontRevision " ,
" CIDFontType " ,
" CIDCount " ,
]
BaseDict . toXML ( self , xmlWriter )
def decompileAllCharStrings ( self ) :
# Make sure that all the Private Dicts have been instantiated.
for i , charString in enumerate ( self . CharStrings . values ( ) ) :
try :
charString . decompile ( )
except :
log . error ( " Error in charstring %s " , i )
raise
def recalcFontBBox ( self ) :
fontBBox = None
for charString in self . CharStrings . values ( ) :
bounds = charString . calcBounds ( self . CharStrings )
if bounds is not None :
if fontBBox is not None :
fontBBox = unionRect ( fontBBox , bounds )
else :
fontBBox = bounds
if fontBBox is None :
self . FontBBox = self . defaults [ " FontBBox " ] [ : ]
else :
self . FontBBox = list ( intRect ( fontBBox ) )
2017-05-18 17:58:39 +09:00
2017-07-19 18:18:58 +01:00
2017-05-09 15:32:12 -07:00
class FontDict ( BaseDict ) :
2022-12-13 11:26:36 +00:00
#
# Since fonttools used to pass a lot of fields that are not relevant in the FDArray
# FontDict, there are 'ttx' files in the wild that contain all these. These got in
# the ttx files because fonttools writes explicit values for all the TopDict default
# values. These are not actually illegal in the context of an FDArray FontDict - you
# can legally, per spec, put any arbitrary key/value pair in a FontDict - but are
# useless since current major company CFF interpreters ignore anything but the set
# listed in this file. So, we just silently skip them. An exception is Weight: this
# is not used by any interpreter, but some foundries have asked that this be
# supported in FDArray FontDicts just to preserve information about the design when
# the font is being inspected.
#
# On top of that, there are fonts out there that contain such useless FontDict values.
#
# By subclassing TopDict, we *allow* all key/values from TopDict, both when reading
# from binary or when reading from XML, but by overriding `order` with a limited
# list of names, we ensure that only the useful names ever get exported to XML and
# ever get compiled into the binary font.
#
# We override compilerClass so we can warn about "useless" key/value pairs, either
# from the original binary font or from TTX input.
#
# See:
# - https://github.com/fonttools/fonttools/issues/740
# - https://github.com/fonttools/fonttools/issues/601
# - https://github.com/adobe-type-tools/afdko/issues/137
#
defaults = { }
converters = buildConverters ( topDictOperators )
compilerClass = FontDictCompiler
orderCFF = [ " FontName " , " FontMatrix " , " Weight " , " Private " ]
orderCFF2 = [ " Private " ]
decompilerClass = TopDictDecompiler
def __init__ (
self ,
strings = None ,
file = None ,
offset = None ,
GlobalSubrs = None ,
isCFF2 = None ,
vstore = None ,
) :
super ( FontDict , self ) . __init__ ( strings , file , offset , isCFF2 = isCFF2 )
self . vstore = vstore
self . setCFF2 ( isCFF2 )
def setCFF2 ( self , isCFF2 ) :
# isCFF2 may be None.
if isCFF2 :
self . order = self . orderCFF2
self . _isCFF2 = True
else :
self . order = self . orderCFF
self . _isCFF2 = False
2017-07-19 18:18:58 +01:00
2017-01-12 15:23:12 -08:00
2002-05-16 18:17:32 +00:00
class PrivateDict ( BaseDict ) :
2022-12-13 11:26:36 +00:00
defaults = buildDefaults ( privateDictOperators )
converters = buildConverters ( privateDictOperators )
order = buildOrder ( privateDictOperators )
decompilerClass = PrivateDictDecompiler
compilerClass = PrivateDictCompiler
def __init__ ( self , strings = None , file = None , offset = None , isCFF2 = None , vstore = None ) :
super ( PrivateDict , self ) . __init__ ( strings , file , offset , isCFF2 = isCFF2 )
self . vstore = vstore
if isCFF2 :
self . defaults = buildDefaults ( privateDictOperators2 )
self . order = buildOrder ( privateDictOperators2 )
# Provide dummy values. This avoids needing to provide
# an isCFF2 state in a lot of places.
self . nominalWidthX = self . defaultWidthX = None
2024-05-14 11:56:15 -06:00
self . _isCFF2 = True
2022-12-13 11:26:36 +00:00
else :
self . defaults = buildDefaults ( privateDictOperators )
self . order = buildOrder ( privateDictOperators )
2024-05-14 11:56:15 -06:00
self . _isCFF2 = False
2022-12-13 11:26:36 +00:00
@property
def in_cff2 ( self ) :
return self . _isCFF2
def getNumRegions ( self , vi = None ) : # called from misc/psCharStrings.py
# if getNumRegions is being called, we can assume that VarStore exists.
if vi is None :
if hasattr ( self , " vsindex " ) :
vi = self . vsindex
else :
vi = 0
numRegions = self . vstore . getNumRegions ( vi )
return numRegions
2017-06-21 10:32:58 +01:00
2013-11-28 14:26:58 -05:00
class IndexedStrings ( object ) :
2022-12-13 11:26:36 +00:00
""" SID -> string mapping. """
def __init__ ( self , file = None ) :
if file is None :
strings = [ ]
else :
strings = [ tostr ( s , encoding = " latin1 " ) for s in Index ( file , isCFF2 = False ) ]
self . strings = strings
def getCompiler ( self ) :
return IndexedStringsCompiler ( self , None , self , isCFF2 = False )
def __len__ ( self ) :
return len ( self . strings )
def __getitem__ ( self , SID ) :
if SID < cffStandardStringCount :
return cffStandardStrings [ SID ]
else :
return self . strings [ SID - cffStandardStringCount ]
def getSID ( self , s ) :
if not hasattr ( self , " stringMapping " ) :
self . buildStringMapping ( )
s = tostr ( s , encoding = " latin1 " )
if s in cffStandardStringMapping :
SID = cffStandardStringMapping [ s ]
elif s in self . stringMapping :
SID = self . stringMapping [ s ]
else :
SID = len ( self . strings ) + cffStandardStringCount
self . strings . append ( s )
self . stringMapping [ s ] = SID
return SID
def getStrings ( self ) :
return self . strings
def buildStringMapping ( self ) :
self . stringMapping = { }
for index in range ( len ( self . strings ) ) :
self . stringMapping [ self . strings [ index ] ] = index + cffStandardStringCount
2002-05-14 12:22:03 +00:00
1999-12-16 21:34:53 +00:00
# The 391 Standard Strings as used in the CFF format.
# from Adobe Technical None #5176, version 1.0, 18 March 1998
2022-12-13 11:26:36 +00:00
cffStandardStrings = [
" .notdef " ,
" space " ,
" exclam " ,
" quotedbl " ,
" numbersign " ,
" dollar " ,
" percent " ,
" ampersand " ,
" quoteright " ,
" parenleft " ,
" parenright " ,
" asterisk " ,
" plus " ,
" comma " ,
" hyphen " ,
" period " ,
" slash " ,
" zero " ,
" one " ,
" two " ,
" three " ,
" four " ,
" five " ,
" six " ,
" seven " ,
" eight " ,
" nine " ,
" colon " ,
" semicolon " ,
" less " ,
" equal " ,
" greater " ,
" question " ,
" at " ,
" A " ,
" B " ,
" C " ,
" D " ,
" E " ,
" F " ,
" G " ,
" H " ,
" I " ,
" J " ,
" K " ,
" L " ,
" M " ,
" N " ,
" O " ,
" P " ,
" Q " ,
" R " ,
" S " ,
" T " ,
" U " ,
" V " ,
" W " ,
" X " ,
" Y " ,
" Z " ,
" bracketleft " ,
" backslash " ,
" bracketright " ,
" asciicircum " ,
" underscore " ,
" quoteleft " ,
" a " ,
" b " ,
" c " ,
" d " ,
" e " ,
" f " ,
" g " ,
" h " ,
" i " ,
" j " ,
" k " ,
" l " ,
" m " ,
" n " ,
" o " ,
" p " ,
" q " ,
" r " ,
" s " ,
" t " ,
" u " ,
" v " ,
" w " ,
" x " ,
" y " ,
" z " ,
" braceleft " ,
" bar " ,
" braceright " ,
" asciitilde " ,
" exclamdown " ,
" cent " ,
" sterling " ,
" fraction " ,
" yen " ,
" florin " ,
" section " ,
" currency " ,
" quotesingle " ,
" quotedblleft " ,
" guillemotleft " ,
" guilsinglleft " ,
" guilsinglright " ,
" fi " ,
" fl " ,
" endash " ,
" dagger " ,
" daggerdbl " ,
" periodcentered " ,
" paragraph " ,
" bullet " ,
" quotesinglbase " ,
" quotedblbase " ,
" quotedblright " ,
" guillemotright " ,
" ellipsis " ,
" perthousand " ,
" questiondown " ,
" grave " ,
" acute " ,
" circumflex " ,
" tilde " ,
" macron " ,
" breve " ,
" dotaccent " ,
" dieresis " ,
" ring " ,
" cedilla " ,
" hungarumlaut " ,
" ogonek " ,
" caron " ,
" emdash " ,
" AE " ,
" ordfeminine " ,
" Lslash " ,
" Oslash " ,
" OE " ,
" ordmasculine " ,
" ae " ,
" dotlessi " ,
" lslash " ,
" oslash " ,
" oe " ,
" germandbls " ,
" onesuperior " ,
" logicalnot " ,
" mu " ,
" trademark " ,
" Eth " ,
" onehalf " ,
" plusminus " ,
" Thorn " ,
" onequarter " ,
" divide " ,
" brokenbar " ,
" degree " ,
" thorn " ,
" threequarters " ,
" twosuperior " ,
" registered " ,
" minus " ,
" eth " ,
" multiply " ,
" threesuperior " ,
" copyright " ,
" Aacute " ,
" Acircumflex " ,
" Adieresis " ,
" Agrave " ,
" Aring " ,
" Atilde " ,
" Ccedilla " ,
" Eacute " ,
" Ecircumflex " ,
" Edieresis " ,
" Egrave " ,
" Iacute " ,
" Icircumflex " ,
" Idieresis " ,
" Igrave " ,
" Ntilde " ,
" Oacute " ,
" Ocircumflex " ,
" Odieresis " ,
" Ograve " ,
" Otilde " ,
" Scaron " ,
" Uacute " ,
" Ucircumflex " ,
" Udieresis " ,
" Ugrave " ,
" Yacute " ,
" Ydieresis " ,
" Zcaron " ,
" aacute " ,
" acircumflex " ,
" adieresis " ,
" agrave " ,
" aring " ,
" atilde " ,
" ccedilla " ,
" eacute " ,
" ecircumflex " ,
" edieresis " ,
" egrave " ,
" iacute " ,
" icircumflex " ,
" idieresis " ,
" igrave " ,
" ntilde " ,
" oacute " ,
" ocircumflex " ,
" odieresis " ,
" ograve " ,
" otilde " ,
" scaron " ,
" uacute " ,
" ucircumflex " ,
" udieresis " ,
" ugrave " ,
" yacute " ,
" ydieresis " ,
" zcaron " ,
" exclamsmall " ,
" Hungarumlautsmall " ,
" dollaroldstyle " ,
" dollarsuperior " ,
" ampersandsmall " ,
" Acutesmall " ,
" parenleftsuperior " ,
" parenrightsuperior " ,
" twodotenleader " ,
" onedotenleader " ,
" zerooldstyle " ,
" oneoldstyle " ,
" twooldstyle " ,
" threeoldstyle " ,
" fouroldstyle " ,
" fiveoldstyle " ,
" sixoldstyle " ,
" sevenoldstyle " ,
" eightoldstyle " ,
" nineoldstyle " ,
" commasuperior " ,
" threequartersemdash " ,
" periodsuperior " ,
" questionsmall " ,
" asuperior " ,
" bsuperior " ,
" centsuperior " ,
" dsuperior " ,
" esuperior " ,
" isuperior " ,
" lsuperior " ,
" msuperior " ,
" nsuperior " ,
" osuperior " ,
" rsuperior " ,
" ssuperior " ,
" tsuperior " ,
" ff " ,
" ffi " ,
" ffl " ,
" parenleftinferior " ,
" parenrightinferior " ,
" Circumflexsmall " ,
" hyphensuperior " ,
" Gravesmall " ,
" Asmall " ,
" Bsmall " ,
" Csmall " ,
" Dsmall " ,
" Esmall " ,
" Fsmall " ,
" Gsmall " ,
" Hsmall " ,
" Ismall " ,
" Jsmall " ,
" Ksmall " ,
" Lsmall " ,
" Msmall " ,
" Nsmall " ,
" Osmall " ,
" Psmall " ,
" Qsmall " ,
" Rsmall " ,
" Ssmall " ,
" Tsmall " ,
" Usmall " ,
" Vsmall " ,
" Wsmall " ,
" Xsmall " ,
" Ysmall " ,
" Zsmall " ,
" colonmonetary " ,
" onefitted " ,
" rupiah " ,
" Tildesmall " ,
" exclamdownsmall " ,
" centoldstyle " ,
" Lslashsmall " ,
" Scaronsmall " ,
" Zcaronsmall " ,
" Dieresissmall " ,
" Brevesmall " ,
" Caronsmall " ,
" Dotaccentsmall " ,
" Macronsmall " ,
" figuredash " ,
" hypheninferior " ,
" Ogoneksmall " ,
" Ringsmall " ,
" Cedillasmall " ,
" questiondownsmall " ,
" oneeighth " ,
" threeeighths " ,
" fiveeighths " ,
" seveneighths " ,
" onethird " ,
" twothirds " ,
" zerosuperior " ,
" foursuperior " ,
" fivesuperior " ,
" sixsuperior " ,
" sevensuperior " ,
" eightsuperior " ,
" ninesuperior " ,
" zeroinferior " ,
" oneinferior " ,
" twoinferior " ,
" threeinferior " ,
" fourinferior " ,
" fiveinferior " ,
" sixinferior " ,
" seveninferior " ,
" eightinferior " ,
" nineinferior " ,
" centinferior " ,
" dollarinferior " ,
" periodinferior " ,
" commainferior " ,
" Agravesmall " ,
" Aacutesmall " ,
" Acircumflexsmall " ,
" Atildesmall " ,
" Adieresissmall " ,
" Aringsmall " ,
" AEsmall " ,
" Ccedillasmall " ,
" Egravesmall " ,
" Eacutesmall " ,
" Ecircumflexsmall " ,
" Edieresissmall " ,
" Igravesmall " ,
" Iacutesmall " ,
" Icircumflexsmall " ,
" Idieresissmall " ,
" Ethsmall " ,
" Ntildesmall " ,
" Ogravesmall " ,
" Oacutesmall " ,
" Ocircumflexsmall " ,
" Otildesmall " ,
" Odieresissmall " ,
" OEsmall " ,
" Oslashsmall " ,
" Ugravesmall " ,
" Uacutesmall " ,
" Ucircumflexsmall " ,
" Udieresissmall " ,
" Yacutesmall " ,
" Thornsmall " ,
" Ydieresissmall " ,
" 001.000 " ,
" 001.001 " ,
" 001.002 " ,
" 001.003 " ,
" Black " ,
" Bold " ,
" Book " ,
" Light " ,
" Medium " ,
" Regular " ,
" Roman " ,
" Semibold " ,
1999-12-16 21:34:53 +00:00
]
cffStandardStringCount = 391
assert len ( cffStandardStrings ) == cffStandardStringCount
# build reverse mapping
cffStandardStringMapping = { }
for _i in range ( cffStandardStringCount ) :
2022-12-13 11:26:36 +00:00
cffStandardStringMapping [ cffStandardStrings [ _i ] ] = _i
cffISOAdobeStrings = [
" .notdef " ,
" space " ,
" exclam " ,
" quotedbl " ,
" numbersign " ,
" dollar " ,
" percent " ,
" ampersand " ,
" quoteright " ,
" parenleft " ,
" parenright " ,
" asterisk " ,
" plus " ,
" comma " ,
" hyphen " ,
" period " ,
" slash " ,
" zero " ,
" one " ,
" two " ,
" three " ,
" four " ,
" five " ,
" six " ,
" seven " ,
" eight " ,
" nine " ,
" colon " ,
" semicolon " ,
" less " ,
" equal " ,
" greater " ,
" question " ,
" at " ,
" A " ,
" B " ,
" C " ,
" D " ,
" E " ,
" F " ,
" G " ,
" H " ,
" I " ,
" J " ,
" K " ,
" L " ,
" M " ,
" N " ,
" O " ,
" P " ,
" Q " ,
" R " ,
" S " ,
" T " ,
" U " ,
" V " ,
" W " ,
" X " ,
" Y " ,
" Z " ,
" bracketleft " ,
" backslash " ,
" bracketright " ,
" asciicircum " ,
" underscore " ,
" quoteleft " ,
" a " ,
" b " ,
" c " ,
" d " ,
" e " ,
" f " ,
" g " ,
" h " ,
" i " ,
" j " ,
" k " ,
" l " ,
" m " ,
" n " ,
" o " ,
" p " ,
" q " ,
" r " ,
" s " ,
" t " ,
" u " ,
" v " ,
" w " ,
" x " ,
" y " ,
" z " ,
" braceleft " ,
" bar " ,
" braceright " ,
" asciitilde " ,
" exclamdown " ,
" cent " ,
" sterling " ,
" fraction " ,
" yen " ,
" florin " ,
" section " ,
" currency " ,
" quotesingle " ,
" quotedblleft " ,
" guillemotleft " ,
" guilsinglleft " ,
" guilsinglright " ,
" fi " ,
" fl " ,
" endash " ,
" dagger " ,
" daggerdbl " ,
" periodcentered " ,
" paragraph " ,
" bullet " ,
" quotesinglbase " ,
" quotedblbase " ,
" quotedblright " ,
" guillemotright " ,
" ellipsis " ,
" perthousand " ,
" questiondown " ,
" grave " ,
" acute " ,
" circumflex " ,
" tilde " ,
" macron " ,
" breve " ,
" dotaccent " ,
" dieresis " ,
" ring " ,
" cedilla " ,
" hungarumlaut " ,
" ogonek " ,
" caron " ,
" emdash " ,
" AE " ,
" ordfeminine " ,
" Lslash " ,
" Oslash " ,
" OE " ,
" ordmasculine " ,
" ae " ,
" dotlessi " ,
" lslash " ,
" oslash " ,
" oe " ,
" germandbls " ,
" onesuperior " ,
" logicalnot " ,
" mu " ,
" trademark " ,
" Eth " ,
" onehalf " ,
" plusminus " ,
" Thorn " ,
" onequarter " ,
" divide " ,
" brokenbar " ,
" degree " ,
" thorn " ,
" threequarters " ,
" twosuperior " ,
" registered " ,
" minus " ,
" eth " ,
" multiply " ,
" threesuperior " ,
" copyright " ,
" Aacute " ,
" Acircumflex " ,
" Adieresis " ,
" Agrave " ,
" Aring " ,
" Atilde " ,
" Ccedilla " ,
" Eacute " ,
" Ecircumflex " ,
" Edieresis " ,
" Egrave " ,
" Iacute " ,
" Icircumflex " ,
" Idieresis " ,
" Igrave " ,
" Ntilde " ,
" Oacute " ,
" Ocircumflex " ,
" Odieresis " ,
" Ograve " ,
" Otilde " ,
" Scaron " ,
" Uacute " ,
" Ucircumflex " ,
" Udieresis " ,
" Ugrave " ,
" Yacute " ,
" Ydieresis " ,
" Zcaron " ,
" aacute " ,
" acircumflex " ,
" adieresis " ,
" agrave " ,
" aring " ,
" atilde " ,
" ccedilla " ,
" eacute " ,
" ecircumflex " ,
" edieresis " ,
" egrave " ,
" iacute " ,
" icircumflex " ,
" idieresis " ,
" igrave " ,
" ntilde " ,
" oacute " ,
" ocircumflex " ,
" odieresis " ,
" ograve " ,
" otilde " ,
" scaron " ,
" uacute " ,
" ucircumflex " ,
" udieresis " ,
" ugrave " ,
" yacute " ,
" ydieresis " ,
" zcaron " ,
]
2006-10-21 13:41:18 +00:00
cffISOAdobeStringCount = 229
assert len ( cffISOAdobeStrings ) == cffISOAdobeStringCount
2022-12-13 11:26:36 +00:00
cffIExpertStrings = [
" .notdef " ,
" space " ,
" exclamsmall " ,
" Hungarumlautsmall " ,
" dollaroldstyle " ,
" dollarsuperior " ,
" ampersandsmall " ,
" Acutesmall " ,
" parenleftsuperior " ,
" parenrightsuperior " ,
" twodotenleader " ,
" onedotenleader " ,
" comma " ,
" hyphen " ,
" period " ,
" fraction " ,
" zerooldstyle " ,
" oneoldstyle " ,
" twooldstyle " ,
" threeoldstyle " ,
" fouroldstyle " ,
" fiveoldstyle " ,
" sixoldstyle " ,
" sevenoldstyle " ,
" eightoldstyle " ,
" nineoldstyle " ,
" colon " ,
" semicolon " ,
" commasuperior " ,
" threequartersemdash " ,
" periodsuperior " ,
" questionsmall " ,
" asuperior " ,
" bsuperior " ,
" centsuperior " ,
" dsuperior " ,
" esuperior " ,
" isuperior " ,
" lsuperior " ,
" msuperior " ,
" nsuperior " ,
" osuperior " ,
" rsuperior " ,
" ssuperior " ,
" tsuperior " ,
" ff " ,
" fi " ,
" fl " ,
" ffi " ,
" ffl " ,
" parenleftinferior " ,
" parenrightinferior " ,
" Circumflexsmall " ,
" hyphensuperior " ,
" Gravesmall " ,
" Asmall " ,
" Bsmall " ,
" Csmall " ,
" Dsmall " ,
" Esmall " ,
" Fsmall " ,
" Gsmall " ,
" Hsmall " ,
" Ismall " ,
" Jsmall " ,
" Ksmall " ,
" Lsmall " ,
" Msmall " ,
" Nsmall " ,
" Osmall " ,
" Psmall " ,
" Qsmall " ,
" Rsmall " ,
" Ssmall " ,
" Tsmall " ,
" Usmall " ,
" Vsmall " ,
" Wsmall " ,
" Xsmall " ,
" Ysmall " ,
" Zsmall " ,
" colonmonetary " ,
" onefitted " ,
" rupiah " ,
" Tildesmall " ,
" exclamdownsmall " ,
" centoldstyle " ,
" Lslashsmall " ,
" Scaronsmall " ,
" Zcaronsmall " ,
" Dieresissmall " ,
" Brevesmall " ,
" Caronsmall " ,
" Dotaccentsmall " ,
" Macronsmall " ,
" figuredash " ,
" hypheninferior " ,
" Ogoneksmall " ,
" Ringsmall " ,
" Cedillasmall " ,
" onequarter " ,
" onehalf " ,
" threequarters " ,
" questiondownsmall " ,
" oneeighth " ,
" threeeighths " ,
" fiveeighths " ,
" seveneighths " ,
" onethird " ,
" twothirds " ,
" zerosuperior " ,
" onesuperior " ,
" twosuperior " ,
" threesuperior " ,
" foursuperior " ,
" fivesuperior " ,
" sixsuperior " ,
" sevensuperior " ,
" eightsuperior " ,
" ninesuperior " ,
" zeroinferior " ,
" oneinferior " ,
" twoinferior " ,
" threeinferior " ,
" fourinferior " ,
" fiveinferior " ,
" sixinferior " ,
" seveninferior " ,
" eightinferior " ,
" nineinferior " ,
" centinferior " ,
" dollarinferior " ,
" periodinferior " ,
" commainferior " ,
" Agravesmall " ,
" Aacutesmall " ,
" Acircumflexsmall " ,
" Atildesmall " ,
" Adieresissmall " ,
" Aringsmall " ,
" AEsmall " ,
" Ccedillasmall " ,
" Egravesmall " ,
" Eacutesmall " ,
" Ecircumflexsmall " ,
" Edieresissmall " ,
" Igravesmall " ,
" Iacutesmall " ,
" Icircumflexsmall " ,
" Idieresissmall " ,
" Ethsmall " ,
" Ntildesmall " ,
" Ogravesmall " ,
" Oacutesmall " ,
" Ocircumflexsmall " ,
" Otildesmall " ,
" Odieresissmall " ,
" OEsmall " ,
" Oslashsmall " ,
" Ugravesmall " ,
" Uacutesmall " ,
" Ucircumflexsmall " ,
" Udieresissmall " ,
" Yacutesmall " ,
" Thornsmall " ,
" Ydieresissmall " ,
]
2006-10-21 13:41:18 +00:00
cffExpertStringCount = 166
assert len ( cffIExpertStrings ) == cffExpertStringCount
2022-12-13 11:26:36 +00:00
cffExpertSubsetStrings = [
" .notdef " ,
" space " ,
" dollaroldstyle " ,
" dollarsuperior " ,
" parenleftsuperior " ,
" parenrightsuperior " ,
" twodotenleader " ,
" onedotenleader " ,
" comma " ,
" hyphen " ,
" period " ,
" fraction " ,
" zerooldstyle " ,
" oneoldstyle " ,
" twooldstyle " ,
" threeoldstyle " ,
" fouroldstyle " ,
" fiveoldstyle " ,
" sixoldstyle " ,
" sevenoldstyle " ,
" eightoldstyle " ,
" nineoldstyle " ,
" colon " ,
" semicolon " ,
" commasuperior " ,
" threequartersemdash " ,
" periodsuperior " ,
" asuperior " ,
" bsuperior " ,
" centsuperior " ,
" dsuperior " ,
" esuperior " ,
" isuperior " ,
" lsuperior " ,
" msuperior " ,
" nsuperior " ,
" osuperior " ,
" rsuperior " ,
" ssuperior " ,
" tsuperior " ,
" ff " ,
" fi " ,
" fl " ,
" ffi " ,
" ffl " ,
" parenleftinferior " ,
" parenrightinferior " ,
" hyphensuperior " ,
" colonmonetary " ,
" onefitted " ,
" rupiah " ,
" centoldstyle " ,
" figuredash " ,
" hypheninferior " ,
" onequarter " ,
" onehalf " ,
" threequarters " ,
" oneeighth " ,
" threeeighths " ,
" fiveeighths " ,
" seveneighths " ,
" onethird " ,
" twothirds " ,
" zerosuperior " ,
" onesuperior " ,
" twosuperior " ,
" threesuperior " ,
" foursuperior " ,
" fivesuperior " ,
" sixsuperior " ,
" sevensuperior " ,
" eightsuperior " ,
" ninesuperior " ,
" zeroinferior " ,
" oneinferior " ,
" twoinferior " ,
" threeinferior " ,
" fourinferior " ,
" fiveinferior " ,
" sixinferior " ,
" seveninferior " ,
" eightinferior " ,
" nineinferior " ,
" centinferior " ,
" dollarinferior " ,
" periodinferior " ,
" commainferior " ,
]
2006-10-21 13:41:18 +00:00
cffExpertSubsetStringCount = 87
assert len ( cffExpertSubsetStrings ) == cffExpertSubsetStringCount