2017-09-04 12:29:55 +02:00
# coding: utf-8
2002-05-11 00:59:27 +00:00
""" fontTools.ttLib.tables.otTables -- A collection of classes representing the various
OpenType subtables .
2002-05-11 10:21:36 +00:00
Most are constructed upon import from data in otData . py , all are populated with
converter objects from otConverters . py .
2002-05-11 00:59:27 +00:00
"""
2021-07-26 15:47:02 +01:00
import copy
2020-03-06 13:19:07 +00:00
from enum import IntEnum
import itertools
2021-07-26 15:47:02 +01:00
from collections import defaultdict , namedtuple
2021-03-03 18:48:08 -07:00
from fontTools . misc . roundTools import otRound
2021-08-20 00:45:43 +02:00
from fontTools . misc . textTools import bytesjoin , pad , safeEval
2020-10-07 18:45:56 +01:00
from . otBase import (
BaseTable , FormatSwitchingBaseTable , ValueRecord , CountReference ,
getFormatSwitchingBaseTableClass ,
)
2020-09-11 09:31:07 +01:00
from fontTools . feaLib . lookupDebugInfo import LookupDebugInfo , LOOKUP_DEBUG_INFO_KEY
2016-01-24 14:44:42 +00:00
import logging
2017-09-21 02:27:29 +02:00
import struct
2016-01-24 14:44:42 +00:00
log = logging . getLogger ( __name__ )
2002-05-11 00:59:27 +00:00
2017-09-04 12:29:55 +02:00
class AATStateTable ( object ) :
def __init__ ( self ) :
2017-09-05 18:32:05 +02:00
self . GlyphClasses = { } # GlyphID --> GlyphClass
2017-09-04 12:29:55 +02:00
self . States = [ ] # List of AATState, indexed by state number
2017-09-05 18:32:05 +02:00
self . PerGlyphLookups = [ ] # [{GlyphID:GlyphID}, ...]
2017-09-04 12:29:55 +02:00
class AATState ( object ) :
def __init__ ( self ) :
2017-09-05 18:32:05 +02:00
self . Transitions = { } # GlyphClass --> AATAction
class AATAction ( object ) :
_FLAGS = None
2018-09-24 20:05:57 +02:00
@staticmethod
def compileActions ( font , states ) :
return ( None , None )
2017-09-05 18:32:05 +02:00
def _writeFlagsToXML ( self , xmlWriter ) :
flags = [ f for f in self . _FLAGS if self . __dict__ [ f ] ]
if flags :
xmlWriter . simpletag ( " Flags " , value = " , " . join ( flags ) )
xmlWriter . newline ( )
if self . ReservedFlags != 0 :
xmlWriter . simpletag (
" ReservedFlags " ,
value = ' 0x %04X ' % self . ReservedFlags )
xmlWriter . newline ( )
2017-09-04 12:29:55 +02:00
2017-09-05 18:32:05 +02:00
def _setFlag ( self , flag ) :
assert flag in self . _FLAGS , " unsupported flag %s " % flag
self . __dict__ [ flag ] = True
2017-09-04 12:29:55 +02:00
2017-09-05 18:32:05 +02:00
class RearrangementMorphAction ( AATAction ) :
2017-09-04 12:29:55 +02:00
staticSize = 4
2018-09-24 20:05:57 +02:00
actionHeaderSize = 0
2017-09-05 18:32:05 +02:00
_FLAGS = [ " MarkFirst " , " DontAdvance " , " MarkLast " ]
2017-09-04 12:29:55 +02:00
_VERBS = {
0 : " no change " ,
1 : " Ax ⇒ xA " ,
2 : " xD ⇒ Dx " ,
3 : " AxD ⇒ DxA " ,
4 : " ABx ⇒ xAB " ,
5 : " ABx ⇒ xBA " ,
6 : " xCD ⇒ CDx " ,
7 : " xCD ⇒ DCx " ,
8 : " AxCD ⇒ CDxA " ,
9 : " AxCD ⇒ DCxA " ,
10 : " ABxD ⇒ DxAB " ,
11 : " ABxD ⇒ DxBA " ,
12 : " ABxCD ⇒ CDxAB " ,
13 : " ABxCD ⇒ CDxBA " ,
14 : " ABxCD ⇒ DCxAB " ,
15 : " ABxCD ⇒ DCxBA " ,
}
def __init__ ( self ) :
self . NewState = 0
self . Verb = 0
self . MarkFirst = False
self . DontAdvance = False
self . MarkLast = False
self . ReservedFlags = 0
2017-10-31 16:25:41 +01:00
def compile ( self , writer , font , actionIndex ) :
assert actionIndex is None
2017-09-04 12:29:55 +02:00
writer . writeUShort ( self . NewState )
assert self . Verb > = 0 and self . Verb < = 15 , self . Verb
flags = self . Verb | self . ReservedFlags
if self . MarkFirst : flags | = 0x8000
if self . DontAdvance : flags | = 0x4000
if self . MarkLast : flags | = 0x2000
writer . writeUShort ( flags )
2017-10-31 16:25:41 +01:00
def decompile ( self , reader , font , actionReader ) :
assert actionReader is None
2017-09-04 12:29:55 +02:00
self . NewState = reader . readUShort ( )
flags = reader . readUShort ( )
self . Verb = flags & 0xF
self . MarkFirst = bool ( flags & 0x8000 )
self . DontAdvance = bool ( flags & 0x4000 )
self . MarkLast = bool ( flags & 0x2000 )
self . ReservedFlags = flags & 0x1FF0
def toXML ( self , xmlWriter , font , attrs , name ) :
xmlWriter . begintag ( name , * * attrs )
xmlWriter . newline ( )
xmlWriter . simpletag ( " NewState " , value = self . NewState )
xmlWriter . newline ( )
2017-09-05 18:32:05 +02:00
self . _writeFlagsToXML ( xmlWriter )
2017-09-04 12:29:55 +02:00
xmlWriter . simpletag ( " Verb " , value = self . Verb )
verbComment = self . _VERBS . get ( self . Verb )
if verbComment is not None :
xmlWriter . comment ( verbComment )
xmlWriter . newline ( )
xmlWriter . endtag ( name )
xmlWriter . newline ( )
def fromXML ( self , name , attrs , content , font ) :
self . NewState = self . Verb = self . ReservedFlags = 0
self . MarkFirst = self . DontAdvance = self . MarkLast = False
content = [ t for t in content if isinstance ( t , tuple ) ]
for eltName , eltAttrs , eltContent in content :
if eltName == " NewState " :
self . NewState = safeEval ( eltAttrs [ " value " ] )
elif eltName == " Verb " :
self . Verb = safeEval ( eltAttrs [ " value " ] )
elif eltName == " ReservedFlags " :
self . ReservedFlags = safeEval ( eltAttrs [ " value " ] )
elif eltName == " Flags " :
for flag in eltAttrs [ " value " ] . split ( " , " ) :
self . _setFlag ( flag . strip ( ) )
2017-09-05 18:32:05 +02:00
class ContextualMorphAction ( AATAction ) :
staticSize = 8
2018-09-24 20:05:57 +02:00
actionHeaderSize = 0
2017-09-05 18:32:05 +02:00
_FLAGS = [ " SetMark " , " DontAdvance " ]
def __init__ ( self ) :
self . NewState = 0
self . SetMark , self . DontAdvance = False , False
self . ReservedFlags = 0
self . MarkIndex , self . CurrentIndex = 0xFFFF , 0xFFFF
2017-10-31 16:25:41 +01:00
def compile ( self , writer , font , actionIndex ) :
assert actionIndex is None
2017-09-05 18:32:05 +02:00
writer . writeUShort ( self . NewState )
flags = self . ReservedFlags
if self . SetMark : flags | = 0x8000
if self . DontAdvance : flags | = 0x4000
writer . writeUShort ( flags )
writer . writeUShort ( self . MarkIndex )
writer . writeUShort ( self . CurrentIndex )
2017-10-31 16:25:41 +01:00
def decompile ( self , reader , font , actionReader ) :
assert actionReader is None
2017-09-05 18:32:05 +02:00
self . NewState = reader . readUShort ( )
flags = reader . readUShort ( )
self . SetMark = bool ( flags & 0x8000 )
self . DontAdvance = bool ( flags & 0x4000 )
self . ReservedFlags = flags & 0x3FFF
self . MarkIndex = reader . readUShort ( )
self . CurrentIndex = reader . readUShort ( )
def toXML ( self , xmlWriter , font , attrs , name ) :
xmlWriter . begintag ( name , * * attrs )
xmlWriter . newline ( )
xmlWriter . simpletag ( " NewState " , value = self . NewState )
xmlWriter . newline ( )
self . _writeFlagsToXML ( xmlWriter )
xmlWriter . simpletag ( " MarkIndex " , value = self . MarkIndex )
xmlWriter . newline ( )
xmlWriter . simpletag ( " CurrentIndex " ,
value = self . CurrentIndex )
xmlWriter . newline ( )
xmlWriter . endtag ( name )
xmlWriter . newline ( )
def fromXML ( self , name , attrs , content , font ) :
self . NewState = self . ReservedFlags = 0
self . SetMark = self . DontAdvance = False
self . MarkIndex , self . CurrentIndex = 0xFFFF , 0xFFFF
content = [ t for t in content if isinstance ( t , tuple ) ]
for eltName , eltAttrs , eltContent in content :
if eltName == " NewState " :
self . NewState = safeEval ( eltAttrs [ " value " ] )
elif eltName == " Flags " :
for flag in eltAttrs [ " value " ] . split ( " , " ) :
self . _setFlag ( flag . strip ( ) )
elif eltName == " ReservedFlags " :
self . ReservedFlags = safeEval ( eltAttrs [ " value " ] )
elif eltName == " MarkIndex " :
self . MarkIndex = safeEval ( eltAttrs [ " value " ] )
elif eltName == " CurrentIndex " :
self . CurrentIndex = safeEval ( eltAttrs [ " value " ] )
2017-09-04 12:29:55 +02:00
2017-09-12 19:28:17 +02:00
2017-09-20 18:14:22 +02:00
class LigAction ( object ) :
def __init__ ( self ) :
self . Store = False
# GlyphIndexDelta is a (possibly negative) delta that gets
# added to the glyph ID at the top of the AAT runtime
# execution stack. It is *not* a byte offset into the
# morx table. The result of the addition, which is performed
# at run time by the shaping engine, is an index into
# the ligature components table. See 'morx' specification.
# In the AAT specification, this field is called Offset;
# but its meaning is quite different from other offsets
# in either AAT or OpenType, so we use a different name.
self . GlyphIndexDelta = 0
2017-09-12 19:28:17 +02:00
class LigatureMorphAction ( AATAction ) :
staticSize = 6
2018-09-24 20:05:57 +02:00
# 4 bytes for each of {action,ligComponents,ligatures}Offset
actionHeaderSize = 12
2017-09-20 18:14:22 +02:00
_FLAGS = [ " SetComponent " , " DontAdvance " ]
2017-09-12 19:28:17 +02:00
def __init__ ( self ) :
self . NewState = 0
self . SetComponent , self . DontAdvance = False , False
self . ReservedFlags = 0
2017-09-20 18:14:22 +02:00
self . Actions = [ ]
2017-09-12 19:28:17 +02:00
2017-10-31 16:25:41 +01:00
def compile ( self , writer , font , actionIndex ) :
assert actionIndex is not None
2017-09-21 02:27:29 +02:00
writer . writeUShort ( self . NewState )
flags = self . ReservedFlags
if self . SetComponent : flags | = 0x8000
if self . DontAdvance : flags | = 0x4000
if len ( self . Actions ) > 0 : flags | = 0x2000
writer . writeUShort ( flags )
if len ( self . Actions ) > 0 :
actions = self . compileLigActions ( )
2017-10-31 16:25:41 +01:00
writer . writeUShort ( actionIndex [ actions ] )
2017-09-21 02:27:29 +02:00
else :
writer . writeUShort ( 0 )
2017-10-31 16:25:41 +01:00
def decompile ( self , reader , font , actionReader ) :
assert actionReader is not None
2017-09-12 19:28:17 +02:00
self . NewState = reader . readUShort ( )
flags = reader . readUShort ( )
self . SetComponent = bool ( flags & 0x8000 )
self . DontAdvance = bool ( flags & 0x4000 )
2017-09-20 18:14:22 +02:00
performAction = bool ( flags & 0x2000 )
2017-09-12 19:28:17 +02:00
# As of 2017-09-12, the 'morx' specification says that
# the reserved bitmask in ligature subtables is 0x3FFF.
# However, the specification also defines a flag 0x2000,
# so the reserved value should actually be 0x1FFF.
# TODO: Report this specification bug to Apple.
self . ReservedFlags = flags & 0x1FFF
2017-10-31 16:25:41 +01:00
actionIndex = reader . readUShort ( )
2017-09-20 18:14:22 +02:00
if performAction :
self . Actions = self . _decompileLigActions (
2017-10-31 16:25:41 +01:00
actionReader , actionIndex )
2017-09-20 18:14:22 +02:00
else :
self . Actions = [ ]
2018-09-24 20:05:57 +02:00
@staticmethod
def compileActions ( font , states ) :
result , actions , actionIndex = b " " , set ( ) , { }
for state in states :
for _glyphClass , trans in state . Transitions . items ( ) :
actions . add ( trans . compileLigActions ( ) )
# Sort the compiled actions in decreasing order of
# length, so that the longer sequence come before the
# shorter ones. For each compiled action ABCD, its
# suffixes BCD, CD, and D do not be encoded separately
# (in case they occur); instead, we can just store an
# index that points into the middle of the longer
# sequence. Every compiled AAT ligature sequence is
# terminated with an end-of-sequence flag, which can
# only be set on the last element of the sequence.
# Therefore, it is sufficient to consider just the
# suffixes.
for a in sorted ( actions , key = lambda x : ( - len ( x ) , x ) ) :
if a not in actionIndex :
for i in range ( 0 , len ( a ) , 4 ) :
suffix = a [ i : ]
suffixIndex = ( len ( result ) + i ) / / 4
actionIndex . setdefault (
suffix , suffixIndex )
result + = a
result = pad ( result , 4 )
return ( result , actionIndex )
2017-09-21 02:27:29 +02:00
def compileLigActions ( self ) :
result = [ ]
for i , action in enumerate ( self . Actions ) :
last = ( i == len ( self . Actions ) - 1 )
value = action . GlyphIndexDelta & 0x3FFFFFFF
value | = 0x80000000 if last else 0
value | = 0x40000000 if action . Store else 0
result . append ( struct . pack ( " >L " , value ) )
return bytesjoin ( result )
2017-10-31 16:25:41 +01:00
def _decompileLigActions ( self , actionReader , actionIndex ) :
2017-09-20 18:14:22 +02:00
actions = [ ]
last = False
2017-10-31 16:25:41 +01:00
reader = actionReader . getSubReader (
actionReader . pos + actionIndex * 4 )
2017-09-20 18:14:22 +02:00
while not last :
value = reader . readULong ( )
last = bool ( value & 0x80000000 )
action = LigAction ( )
actions . append ( action )
action . Store = bool ( value & 0x40000000 )
delta = value & 0x3FFFFFFF
if delta > = 0x20000000 : # sign-extend 30-bit value
delta = - 0x40000000 + delta
action . GlyphIndexDelta = delta
return actions
2017-09-12 19:28:17 +02:00
2017-09-21 02:27:29 +02:00
def fromXML ( self , name , attrs , content , font ) :
self . NewState = self . ReservedFlags = 0
self . SetComponent = self . DontAdvance = False
self . ReservedFlags = 0
self . Actions = [ ]
content = [ t for t in content if isinstance ( t , tuple ) ]
for eltName , eltAttrs , eltContent in content :
if eltName == " NewState " :
self . NewState = safeEval ( eltAttrs [ " value " ] )
elif eltName == " Flags " :
for flag in eltAttrs [ " value " ] . split ( " , " ) :
self . _setFlag ( flag . strip ( ) )
elif eltName == " ReservedFlags " :
self . ReservedFlags = safeEval ( eltAttrs [ " value " ] )
elif eltName == " Action " :
action = LigAction ( )
flags = eltAttrs . get ( " Flags " , " " ) . split ( " , " )
flags = [ f . strip ( ) for f in flags ]
action . Store = " Store " in flags
action . GlyphIndexDelta = safeEval (
eltAttrs [ " GlyphIndexDelta " ] )
self . Actions . append ( action )
2017-09-12 19:28:17 +02:00
def toXML ( self , xmlWriter , font , attrs , name ) :
xmlWriter . begintag ( name , * * attrs )
xmlWriter . newline ( )
xmlWriter . simpletag ( " NewState " , value = self . NewState )
xmlWriter . newline ( )
self . _writeFlagsToXML ( xmlWriter )
2017-09-20 18:14:22 +02:00
for action in self . Actions :
attribs = [ ( " GlyphIndexDelta " , action . GlyphIndexDelta ) ]
if action . Store :
attribs . append ( ( " Flags " , " Store " ) )
xmlWriter . simpletag ( " Action " , attribs )
2017-09-12 19:28:17 +02:00
xmlWriter . newline ( )
xmlWriter . endtag ( name )
xmlWriter . newline ( )
2017-10-31 13:54:30 +01:00
class InsertionMorphAction ( AATAction ) :
staticSize = 8
2018-09-24 20:05:57 +02:00
actionHeaderSize = 4 # 4 bytes for actionOffset
2017-10-31 13:54:30 +01:00
_FLAGS = [ " SetMark " , " DontAdvance " ,
" CurrentIsKashidaLike " , " MarkedIsKashidaLike " ,
" CurrentInsertBefore " , " MarkedInsertBefore " ]
def __init__ ( self ) :
self . NewState = 0
for flag in self . _FLAGS :
setattr ( self , flag , False )
self . ReservedFlags = 0
self . CurrentInsertionAction , self . MarkedInsertionAction = [ ] , [ ]
def compile ( self , writer , font , actionIndex ) :
assert actionIndex is not None
writer . writeUShort ( self . NewState )
flags = self . ReservedFlags
if self . SetMark : flags | = 0x8000
if self . DontAdvance : flags | = 0x4000
if self . CurrentIsKashidaLike : flags | = 0x2000
if self . MarkedIsKashidaLike : flags | = 0x1000
if self . CurrentInsertBefore : flags | = 0x0800
if self . MarkedInsertBefore : flags | = 0x0400
flags | = len ( self . CurrentInsertionAction ) << 5
flags | = len ( self . MarkedInsertionAction )
writer . writeUShort ( flags )
if len ( self . CurrentInsertionAction ) > 0 :
currentIndex = actionIndex [
tuple ( self . CurrentInsertionAction ) ]
else :
currentIndex = 0xFFFF
writer . writeUShort ( currentIndex )
if len ( self . MarkedInsertionAction ) > 0 :
markedIndex = actionIndex [
tuple ( self . MarkedInsertionAction ) ]
else :
markedIndex = 0xFFFF
writer . writeUShort ( markedIndex )
def decompile ( self , reader , font , actionReader ) :
assert actionReader is not None
self . NewState = reader . readUShort ( )
flags = reader . readUShort ( )
self . SetMark = bool ( flags & 0x8000 )
self . DontAdvance = bool ( flags & 0x4000 )
self . CurrentIsKashidaLike = bool ( flags & 0x2000 )
self . MarkedIsKashidaLike = bool ( flags & 0x1000 )
self . CurrentInsertBefore = bool ( flags & 0x0800 )
self . MarkedInsertBefore = bool ( flags & 0x0400 )
self . CurrentInsertionAction = self . _decompileInsertionAction (
actionReader , font ,
index = reader . readUShort ( ) ,
count = ( ( flags & 0x03E0 ) >> 5 ) )
self . MarkedInsertionAction = self . _decompileInsertionAction (
actionReader , font ,
index = reader . readUShort ( ) ,
count = ( flags & 0x001F ) )
def _decompileInsertionAction ( self , actionReader , font , index , count ) :
if index == 0xFFFF or count == 0 :
return [ ]
reader = actionReader . getSubReader (
actionReader . pos + index * 2 )
2021-08-20 16:03:03 -06:00
return font . getGlyphNameMany ( reader . readUShortArray ( count ) )
2017-10-31 13:54:30 +01:00
def toXML ( self , xmlWriter , font , attrs , name ) :
xmlWriter . begintag ( name , * * attrs )
xmlWriter . newline ( )
xmlWriter . simpletag ( " NewState " , value = self . NewState )
xmlWriter . newline ( )
self . _writeFlagsToXML ( xmlWriter )
for g in self . CurrentInsertionAction :
xmlWriter . simpletag ( " CurrentInsertionAction " , glyph = g )
xmlWriter . newline ( )
for g in self . MarkedInsertionAction :
xmlWriter . simpletag ( " MarkedInsertionAction " , glyph = g )
xmlWriter . newline ( )
xmlWriter . endtag ( name )
xmlWriter . newline ( )
def fromXML ( self , name , attrs , content , font ) :
self . __init__ ( )
content = [ t for t in content if isinstance ( t , tuple ) ]
for eltName , eltAttrs , eltContent in content :
if eltName == " NewState " :
self . NewState = safeEval ( eltAttrs [ " value " ] )
elif eltName == " Flags " :
for flag in eltAttrs [ " value " ] . split ( " , " ) :
self . _setFlag ( flag . strip ( ) )
elif eltName == " CurrentInsertionAction " :
self . CurrentInsertionAction . append (
eltAttrs [ " glyph " ] )
elif eltName == " MarkedInsertionAction " :
self . MarkedInsertionAction . append (
eltAttrs [ " glyph " ] )
else :
assert False , eltName
2018-09-24 17:28:24 +02:00
@staticmethod
def compileActions ( font , states ) :
actions , actionIndex , result = set ( ) , { } , b " "
for state in states :
for _glyphClass , trans in state . Transitions . items ( ) :
if trans . CurrentInsertionAction is not None :
actions . add ( tuple ( trans . CurrentInsertionAction ) )
if trans . MarkedInsertionAction is not None :
actions . add ( tuple ( trans . MarkedInsertionAction ) )
# Sort the compiled actions in decreasing order of
# length, so that the longer sequence come before the
# shorter ones.
for action in sorted ( actions , key = lambda x : ( - len ( x ) , x ) ) :
# We insert all sub-sequences of the action glyph sequence
# into actionIndex. For example, if one action triggers on
# glyph sequence [A, B, C, D, E] and another action triggers
# on [C, D], we return result=[A, B, C, D, E] (as list of
# encoded glyph IDs), and actionIndex={('A','B','C','D','E'): 0,
# ('C','D'): 2}.
if action in actionIndex :
continue
for start in range ( 0 , len ( action ) ) :
startIndex = ( len ( result ) / / 2 ) + start
for limit in range ( start , len ( action ) ) :
glyphs = action [ start : limit + 1 ]
actionIndex . setdefault ( glyphs , startIndex )
for glyph in action :
glyphID = font . getGlyphID ( glyph )
result + = struct . pack ( " >H " , glyphID )
return result , actionIndex
2017-10-31 13:54:30 +01:00
2002-05-11 00:59:27 +00:00
class FeatureParams ( BaseTable ) :
2013-11-26 19:23:08 -05:00
def compile ( self , writer , font ) :
2013-12-17 05:59:05 -05:00
assert featureParamTypes . get ( writer [ ' FeatureTag ' ] ) == self . __class__ , " Wrong FeatureParams type for feature ' %s ' : %s " % ( writer [ ' FeatureTag ' ] , self . __class__ . __name__ )
2013-11-26 19:23:08 -05:00
BaseTable . compile ( self , writer , font )
2013-12-09 00:39:25 -05:00
def toXML ( self , xmlWriter , font , attrs = None , name = None ) :
BaseTable . toXML ( self , xmlWriter , font , attrs , name = self . __class__ . __name__ )
2013-11-26 19:23:08 -05:00
class FeatureParamsSize ( FeatureParams ) :
pass
class FeatureParamsStylisticSet ( FeatureParams ) :
pass
class FeatureParamsCharacterVariants ( FeatureParams ) :
pass
2002-09-12 16:47:02 +00:00
class Coverage ( FormatSwitchingBaseTable ) :
2015-04-26 02:01:01 -04:00
2002-09-12 16:47:02 +00:00
# manual implementation to get rid of glyphID dependencies
2015-04-26 02:01:01 -04:00
2017-02-03 14:33:57 -08:00
def populateDefaults ( self , propagator = None ) :
if not hasattr ( self , ' glyphs ' ) :
self . glyphs = [ ]
2002-09-12 17:09:13 +00:00
def postRead ( self , rawTable , font ) :
2002-09-12 16:47:02 +00:00
if self . Format == 1 :
2002-09-12 17:09:13 +00:00
self . glyphs = rawTable [ " GlyphArray " ]
2002-09-12 16:47:02 +00:00
elif self . Format == 2 :
glyphs = self . glyphs = [ ]
2002-09-12 17:09:13 +00:00
ranges = rawTable [ " RangeRecord " ]
2013-11-18 14:11:47 -05:00
# Some SIL fonts have coverage entries that don't have sorted
# StartCoverageIndex. If it is so, fixup and warn. We undo
# this when writing font out.
2013-11-27 18:58:45 -05:00
sorted_ranges = sorted ( ranges , key = lambda a : a . StartCoverageIndex )
2013-11-18 14:11:47 -05:00
if ranges != sorted_ranges :
2016-01-24 14:44:42 +00:00
log . warning ( " GSUB/GPOS Coverage is not sorted by glyph ids. " )
2013-11-18 14:11:47 -05:00
ranges = sorted_ranges
del sorted_ranges
2002-09-12 16:47:02 +00:00
for r in ranges :
start = r . Start
end = r . End
2021-08-19 12:08:05 -06:00
startID = font . getGlyphID ( start )
endID = font . getGlyphID ( end ) + 1
2021-08-20 16:03:03 -06:00
glyphs . extend ( font . getGlyphNameMany ( range ( startID , endID ) ) )
2002-09-12 16:47:02 +00:00
else :
2017-12-14 19:02:28 -08:00
self . glyphs = [ ]
2018-04-13 11:26:27 +02:00
log . warning ( " Unknown Coverage format: %s " , self . Format )
2013-12-17 03:06:10 -05:00
del self . Format # Don't need this anymore
2015-04-26 02:01:01 -04:00
2002-09-12 16:47:02 +00:00
def preWrite ( self , font ) :
2005-02-11 19:36:38 +00:00
glyphs = getattr ( self , " glyphs " , None )
2003-01-03 20:54:31 +00:00
if glyphs is None :
glyphs = self . glyphs = [ ]
2002-09-12 16:47:02 +00:00
format = 1
2002-09-24 20:50:57 +00:00
rawTable = { " GlyphArray " : glyphs }
if glyphs :
2002-09-12 17:09:13 +00:00
# find out whether Format 2 is more compact or not
2021-08-21 11:16:27 -06:00
glyphIDs = font . getGlyphIDMany ( glyphs )
2013-11-18 14:11:47 -05:00
brokenOrder = sorted ( glyphIDs ) != glyphIDs
2015-04-26 02:01:01 -04:00
2002-09-12 16:47:02 +00:00
last = glyphIDs [ 0 ]
ranges = [ [ last ] ]
for glyphID in glyphIDs [ 1 : ] :
2002-09-12 20:51:09 +00:00
if glyphID != last + 1 :
2002-09-12 16:47:02 +00:00
ranges [ - 1 ] . append ( last )
ranges . append ( [ glyphID ] )
last = glyphID
ranges [ - 1 ] . append ( last )
2015-04-26 02:01:01 -04:00
2013-11-18 14:11:47 -05:00
if brokenOrder or len ( ranges ) * 3 < len ( glyphs ) : # 3 words vs. 1 word
2002-09-12 16:47:02 +00:00
# Format 2 is more compact
index = 0
for i in range ( len ( ranges ) ) :
start , end = ranges [ i ]
r = RangeRecord ( )
2013-11-18 14:11:47 -05:00
r . StartID = start
2002-09-12 16:47:02 +00:00
r . Start = font . getGlyphName ( start )
r . End = font . getGlyphName ( end )
r . StartCoverageIndex = index
ranges [ i ] = r
index = index + end - start + 1
2013-11-18 14:11:47 -05:00
if brokenOrder :
2016-01-24 14:44:42 +00:00
log . warning ( " GSUB/GPOS Coverage is not sorted by glyph ids. " )
2013-11-27 18:58:45 -05:00
ranges . sort ( key = lambda a : a . StartID )
2013-11-18 14:11:47 -05:00
for r in ranges :
del r . StartID
2002-09-12 16:47:02 +00:00
format = 2
2002-09-12 17:09:13 +00:00
rawTable = { " RangeRecord " : ranges }
2002-09-12 16:47:02 +00:00
#else:
# fallthrough; Format 1 is more compact
self . Format = format
2002-09-12 17:09:13 +00:00
return rawTable
2015-04-26 02:01:01 -04:00
2002-09-12 16:47:02 +00:00
def toXML2 ( self , xmlWriter , font ) :
2002-09-24 20:50:57 +00:00
for glyphName in getattr ( self , " glyphs " , [ ] ) :
2002-09-12 16:47:02 +00:00
xmlWriter . simpletag ( " Glyph " , value = glyphName )
xmlWriter . newline ( )
2015-04-26 02:01:01 -04:00
2013-11-27 03:19:32 -05:00
def fromXML ( self , name , attrs , content , font ) :
2002-09-12 16:47:02 +00:00
glyphs = getattr ( self , " glyphs " , None )
if glyphs is None :
glyphs = [ ]
self . glyphs = glyphs
glyphs . append ( attrs [ " value " ] )
2002-05-11 00:59:27 +00:00
2021-07-07 19:06:38 +01:00
class DeltaSetIndexMap ( getFormatSwitchingBaseTableClass ( " uint8 " ) ) :
2016-08-10 01:17:45 -07:00
2017-02-03 14:33:57 -08:00
def populateDefaults ( self , propagator = None ) :
if not hasattr ( self , ' mapping ' ) :
2021-07-07 19:06:38 +01:00
self . mapping = [ ]
2017-02-03 14:33:57 -08:00
2016-08-10 01:17:45 -07:00
def postRead ( self , rawTable , font ) :
assert ( rawTable [ ' EntryFormat ' ] & 0xFFC0 ) == 0
2021-07-07 19:06:38 +01:00
self . mapping = rawTable [ ' mapping ' ]
2016-08-10 01:17:45 -07:00
2021-07-07 19:06:38 +01:00
@staticmethod
def getEntryFormat ( mapping ) :
2016-08-10 01:17:45 -07:00
ored = 0
for idx in mapping :
ored | = idx
inner = ored & 0xFFFF
innerBits = 0
while inner :
innerBits + = 1
inner >> = 1
innerBits = max ( innerBits , 1 )
assert innerBits < = 16
2016-08-11 01:35:56 -07:00
ored = ( ored >> ( 16 - innerBits ) ) | ( ored & ( ( 1 << innerBits ) - 1 ) )
2016-08-10 01:17:45 -07:00
if ored < = 0x000000FF :
entrySize = 1
elif ored < = 0x0000FFFF :
entrySize = 2
elif ored < = 0x00FFFFFF :
entrySize = 3
else :
entrySize = 4
2021-07-07 19:06:38 +01:00
return ( ( entrySize - 1 ) << 4 ) | ( innerBits - 1 )
def preWrite ( self , font ) :
mapping = getattr ( self , " mapping " , None )
if mapping is None :
mapping = self . mapping = [ ]
self . Format = 1 if len ( mapping ) > 0xFFFF else 0
rawTable = self . __dict__ . copy ( )
rawTable [ ' MappingCount ' ] = len ( mapping )
rawTable [ ' EntryFormat ' ] = self . getEntryFormat ( mapping )
return rawTable
def toXML2 ( self , xmlWriter , font ) :
for i , value in enumerate ( getattr ( self , " mapping " , [ ] ) ) :
attrs = (
( ' index ' , i ) ,
( ' outer ' , value >> 16 ) ,
( ' inner ' , value & 0xFFFF ) ,
)
xmlWriter . simpletag ( " Map " , attrs )
xmlWriter . newline ( )
2016-08-10 01:17:45 -07:00
2021-07-07 19:06:38 +01:00
def fromXML ( self , name , attrs , content , font ) :
mapping = getattr ( self , " mapping " , None )
if mapping is None :
self . mapping = mapping = [ ]
index = safeEval ( attrs [ ' index ' ] )
outer = safeEval ( attrs [ ' outer ' ] )
inner = safeEval ( attrs [ ' inner ' ] )
assert inner < = 0xFFFF
mapping . insert ( index , ( outer << 16 ) | inner )
class VarIdxMap ( BaseTable ) :
def populateDefaults ( self , propagator = None ) :
if not hasattr ( self , ' mapping ' ) :
self . mapping = { }
def postRead ( self , rawTable , font ) :
assert ( rawTable [ ' EntryFormat ' ] & 0xFFC0 ) == 0
glyphOrder = font . getGlyphOrder ( )
mapList = rawTable [ ' mapping ' ]
mapList . extend ( [ mapList [ - 1 ] ] * ( len ( glyphOrder ) - len ( mapList ) ) )
self . mapping = dict ( zip ( glyphOrder , mapList ) )
def preWrite ( self , font ) :
mapping = getattr ( self , " mapping " , None )
if mapping is None :
mapping = self . mapping = { }
glyphOrder = font . getGlyphOrder ( )
mapping = [ mapping [ g ] for g in glyphOrder ]
while len ( mapping ) > 1 and mapping [ - 2 ] == mapping [ - 1 ] :
del mapping [ - 1 ]
rawTable = { ' mapping ' : mapping }
rawTable [ ' MappingCount ' ] = len ( mapping )
rawTable [ ' EntryFormat ' ] = DeltaSetIndexMap . getEntryFormat ( mapping )
2016-08-10 01:17:45 -07:00
return rawTable
def toXML2 ( self , xmlWriter , font ) :
2018-02-18 22:29:24 -08:00
for glyph , value in sorted ( getattr ( self , " mapping " , { } ) . items ( ) ) :
2016-08-11 01:35:56 -07:00
attrs = (
2018-02-18 22:29:24 -08:00
( ' glyph ' , glyph ) ,
2016-08-11 01:35:56 -07:00
( ' outer ' , value >> 16 ) ,
( ' inner ' , value & 0xFFFF ) ,
)
xmlWriter . simpletag ( " Map " , attrs )
2016-08-10 01:17:45 -07:00
xmlWriter . newline ( )
def fromXML ( self , name , attrs , content , font ) :
mapping = getattr ( self , " mapping " , None )
if mapping is None :
2018-02-18 22:29:24 -08:00
mapping = { }
2016-08-10 01:17:45 -07:00
self . mapping = mapping
2018-02-20 17:42:49 -08:00
try :
glyph = attrs [ ' glyph ' ]
except : # https://github.com/fonttools/fonttools/commit/21cbab8ce9ded3356fef3745122da64dcaf314e9#commitcomment-27649836
glyph = font . getGlyphOrder ( ) [ attrs [ ' index ' ] ]
2016-08-11 01:35:56 -07:00
outer = safeEval ( attrs [ ' outer ' ] )
inner = safeEval ( attrs [ ' inner ' ] )
assert inner < = 0xFFFF
2018-02-18 22:29:24 -08:00
mapping [ glyph ] = ( outer << 16 ) | inner
2016-08-10 01:17:45 -07:00
2019-10-24 12:51:48 +01:00
class VarRegionList ( BaseTable ) :
def preWrite ( self , font ) :
# The OT spec says VarStore.VarRegionList.RegionAxisCount should always
# be equal to the fvar.axisCount, and OTS < v8.0.0 enforces this rule
# even when the VarRegionList is empty. We can't treat RegionAxisCount
# like a normal propagated count (== len(Region[i].VarRegionAxis)),
# otherwise it would default to 0 if VarRegionList is empty.
# Thus, we force it to always be equal to fvar.axisCount.
# https://github.com/khaledhosny/ots/pull/192
fvarTable = font . get ( " fvar " )
if fvarTable :
2019-10-29 12:52:42 +00:00
self . RegionAxisCount = len ( fvarTable . axes )
return {
* * self . __dict__ ,
" RegionAxisCount " : CountReference ( self . __dict__ , " RegionAxisCount " )
}
2019-10-24 12:51:48 +01:00
2002-09-12 19:54:02 +00:00
class SingleSubst ( FormatSwitchingBaseTable ) :
2017-02-03 14:33:57 -08:00
def populateDefaults ( self , propagator = None ) :
if not hasattr ( self , ' mapping ' ) :
self . mapping = { }
2002-09-12 19:54:02 +00:00
def postRead ( self , rawTable , font ) :
mapping = { }
2004-09-25 10:31:29 +00:00
input = _getGlyphsFromCoverageTable ( rawTable [ " Coverage " ] )
2002-09-12 19:54:02 +00:00
if self . Format == 1 :
delta = rawTable [ " DeltaGlyphID " ]
2021-08-21 11:16:27 -06:00
inputGIDS = font . getGlyphIDMany ( input )
2015-06-12 10:27:04 -07:00
outGIDS = [ ( glyphID + delta ) % 65536 for glyphID in inputGIDS ]
2021-08-20 16:03:03 -06:00
outNames = font . getGlyphNameMany ( outGIDS )
2019-03-03 13:02:27 +01:00
for inp , out in zip ( input , outNames ) :
mapping [ inp ] = out
2002-09-12 19:54:02 +00:00
elif self . Format == 2 :
2016-01-14 12:55:42 +00:00
assert len ( input ) == rawTable [ " GlyphCount " ] , \
2002-09-12 19:54:02 +00:00
" invalid SingleSubstFormat2 table "
subst = rawTable [ " Substitute " ]
2019-03-03 13:02:27 +01:00
for inp , sub in zip ( input , subst ) :
mapping [ inp ] = sub
2002-09-12 19:54:02 +00:00
else :
assert 0 , " unknown format: %s " % self . Format
self . mapping = mapping
2013-12-17 03:06:10 -05:00
del self . Format # Don't need this anymore
2015-04-26 02:01:01 -04:00
2002-09-12 19:54:02 +00:00
def preWrite ( self , font ) :
2005-02-11 19:36:38 +00:00
mapping = getattr ( self , " mapping " , None )
if mapping is None :
mapping = self . mapping = { }
2013-11-27 06:26:55 -05:00
items = list ( mapping . items ( ) )
2006-10-21 14:12:38 +00:00
getGlyphID = font . getGlyphID
2013-12-04 22:46:11 -05:00
gidItems = [ ( getGlyphID ( a ) , getGlyphID ( b ) ) for a , b in items ]
2013-11-27 04:15:34 -05:00
sortableItems = sorted ( zip ( gidItems , items ) )
2006-10-21 14:12:38 +00:00
# figure out format
2002-09-12 19:54:02 +00:00
format = 2
2015-06-12 15:04:37 -07:00
delta = None
2006-10-21 14:12:38 +00:00
for inID , outID in gidItems :
2002-09-12 19:54:02 +00:00
if delta is None :
2015-06-12 10:27:04 -07:00
delta = ( outID - inID ) % 65536
if ( inID + delta ) % 65536 != outID :
2002-09-12 19:54:02 +00:00
break
else :
2016-05-28 22:20:22 +09:00
if delta is None :
2016-06-06 22:06:33 -07:00
# the mapping is empty, better use format 2
2016-05-28 22:20:22 +09:00
format = 2
else :
format = 1
2006-10-21 14:12:38 +00:00
2002-09-12 19:54:02 +00:00
rawTable = { }
self . Format = format
cov = Coverage ( )
2006-10-21 14:12:38 +00:00
input = [ item [ 1 ] [ 0 ] for item in sortableItems ]
subst = [ item [ 1 ] [ 1 ] for item in sortableItems ]
cov . glyphs = input
2002-09-12 19:54:02 +00:00
rawTable [ " Coverage " ] = cov
if format == 1 :
assert delta is not None
rawTable [ " DeltaGlyphID " ] = delta
else :
rawTable [ " Substitute " ] = subst
return rawTable
2015-04-26 02:01:01 -04:00
2002-09-12 19:54:02 +00:00
def toXML2 ( self , xmlWriter , font ) :
2013-11-27 04:15:34 -05:00
items = sorted ( self . mapping . items ( ) )
2002-09-12 19:54:02 +00:00
for inGlyph , outGlyph in items :
xmlWriter . simpletag ( " Substitution " ,
[ ( " in " , inGlyph ) , ( " out " , outGlyph ) ] )
xmlWriter . newline ( )
2015-04-26 02:01:01 -04:00
2013-11-27 03:19:32 -05:00
def fromXML ( self , name , attrs , content , font ) :
2002-09-12 19:54:02 +00:00
mapping = getattr ( self , " mapping " , None )
if mapping is None :
mapping = { }
self . mapping = mapping
mapping [ attrs [ " in " ] ] = attrs [ " out " ]
2015-09-10 14:44:07 +02:00
class MultipleSubst ( FormatSwitchingBaseTable ) :
2017-02-03 14:33:57 -08:00
def populateDefaults ( self , propagator = None ) :
if not hasattr ( self , ' mapping ' ) :
self . mapping = { }
2015-09-10 14:44:07 +02:00
def postRead ( self , rawTable , font ) :
mapping = { }
if self . Format == 1 :
glyphs = _getGlyphsFromCoverageTable ( rawTable [ " Coverage " ] )
subst = [ s . Substitute for s in rawTable [ " Sequence " ] ]
mapping = dict ( zip ( glyphs , subst ) )
else :
assert 0 , " unknown format: %s " % self . Format
self . mapping = mapping
2021-03-23 18:01:06 -07:00
del self . Format # Don't need this anymore
2015-09-10 14:44:07 +02:00
def preWrite ( self , font ) :
mapping = getattr ( self , " mapping " , None )
if mapping is None :
mapping = self . mapping = { }
cov = Coverage ( )
cov . glyphs = sorted ( list ( mapping . keys ( ) ) , key = font . getGlyphID )
self . Format = 1
rawTable = {
" Coverage " : cov ,
" Sequence " : [ self . makeSequence_ ( mapping [ glyph ] )
for glyph in cov . glyphs ] ,
}
return rawTable
def toXML2 ( self , xmlWriter , font ) :
items = sorted ( self . mapping . items ( ) )
for inGlyph , outGlyphs in items :
out = " , " . join ( outGlyphs )
xmlWriter . simpletag ( " Substitution " ,
[ ( " in " , inGlyph ) , ( " out " , out ) ] )
xmlWriter . newline ( )
def fromXML ( self , name , attrs , content , font ) :
mapping = getattr ( self , " mapping " , None )
if mapping is None :
mapping = { }
self . mapping = mapping
2015-09-11 08:19:20 +02:00
# TTX v3.0 and earlier.
if name == " Coverage " :
2015-09-11 11:41:14 +01:00
self . old_coverage_ = [ ]
for element in content :
if not isinstance ( element , tuple ) :
continue
element_name , element_attrs , _ = element
if element_name == " Glyph " :
self . old_coverage_ . append ( element_attrs [ " value " ] )
2015-09-11 08:19:20 +02:00
return
if name == " Sequence " :
2015-10-13 17:44:53 +02:00
index = int ( attrs . get ( " index " , len ( mapping ) ) )
glyph = self . old_coverage_ [ index ]
2015-09-11 11:41:14 +01:00
glyph_mapping = mapping [ glyph ] = [ ]
for element in content :
if not isinstance ( element , tuple ) :
continue
element_name , element_attrs , _ = element
if element_name == " Substitute " :
glyph_mapping . append ( element_attrs [ " value " ] )
2015-09-11 08:19:20 +02:00
return
# TTX v3.1 and later.
2017-02-02 15:48:34 -08:00
outGlyphs = attrs [ " out " ] . split ( " , " ) if attrs [ " out " ] else [ ]
2015-09-10 14:44:07 +02:00
mapping [ attrs [ " in " ] ] = [ g . strip ( ) for g in outGlyphs ]
@staticmethod
def makeSequence_ ( g ) :
seq = Sequence ( )
seq . Substitute = g
return seq
2002-09-12 20:51:09 +00:00
class ClassDef ( FormatSwitchingBaseTable ) :
2015-04-26 02:01:01 -04:00
2017-02-03 14:33:57 -08:00
def populateDefaults ( self , propagator = None ) :
if not hasattr ( self , ' classDefs ' ) :
self . classDefs = { }
2002-09-12 20:51:09 +00:00
def postRead ( self , rawTable , font ) :
classDefs = { }
2006-10-21 14:12:38 +00:00
2002-09-12 20:51:09 +00:00
if self . Format == 1 :
start = rawTable [ " StartGlyph " ]
2006-10-21 14:12:38 +00:00
classList = rawTable [ " ClassValueArray " ]
2021-08-19 12:08:05 -06:00
startID = font . getGlyphID ( start )
2013-12-04 21:49:00 -05:00
endID = startID + len ( classList )
2021-08-20 16:03:03 -06:00
glyphNames = font . getGlyphNameMany ( range ( startID , endID ) )
for glyphName , cls in zip ( glyphNames , classList ) :
2016-12-26 17:26:14 -05:00
if cls :
2021-08-20 16:03:03 -06:00
classDefs [ glyphName ] = cls
2006-10-21 14:12:38 +00:00
2002-09-12 20:51:09 +00:00
elif self . Format == 2 :
records = rawTable [ " ClassRangeRecord " ]
for rec in records :
2021-08-22 04:09:29 -06:00
cls = rec . Class
if not cls :
continue
2002-09-12 20:51:09 +00:00
start = rec . Start
end = rec . End
2021-08-19 12:08:05 -06:00
startID = font . getGlyphID ( start )
endID = font . getGlyphID ( end ) + 1
2021-08-20 16:03:03 -06:00
glyphNames = font . getGlyphNameMany ( range ( startID , endID ) )
2021-08-22 04:09:29 -06:00
for glyphName in glyphNames :
classDefs [ glyphName ] = cls
2002-09-12 20:51:09 +00:00
else :
2018-04-13 11:26:27 +02:00
log . warning ( " Unknown ClassDef format: %s " , self . Format )
2002-09-12 20:51:09 +00:00
self . classDefs = classDefs
2013-12-17 03:06:10 -05:00
del self . Format # Don't need this anymore
2015-04-26 02:01:01 -04:00
2017-03-14 16:14:08 +00:00
def _getClassRanges ( self , font ) :
2005-02-11 19:36:38 +00:00
classDefs = getattr ( self , " classDefs " , None )
if classDefs is None :
2017-03-14 16:14:08 +00:00
self . classDefs = { }
return
2006-10-21 14:12:38 +00:00
getGlyphID = font . getGlyphID
2016-12-26 17:26:14 -05:00
items = [ ]
for glyphName , cls in classDefs . items ( ) :
2017-03-14 16:14:08 +00:00
if not cls :
continue
2016-12-26 17:26:14 -05:00
items . append ( ( getGlyphID ( glyphName ) , glyphName , cls ) )
2005-02-11 19:36:38 +00:00
if items :
2017-03-14 16:14:08 +00:00
items . sort ( )
2005-02-11 19:36:38 +00:00
last , lastName , lastCls = items [ 0 ]
2013-12-02 03:06:44 -05:00
ranges = [ [ lastCls , last , lastName ] ]
2005-02-11 19:36:38 +00:00
for glyphID , glyphName , cls in items [ 1 : ] :
if glyphID != last + 1 or cls != lastCls :
2013-12-02 03:06:44 -05:00
ranges [ - 1 ] . extend ( [ last , lastName ] )
ranges . append ( [ cls , glyphID , glyphName ] )
2005-02-11 19:36:38 +00:00
last = glyphID
lastName = glyphName
lastCls = cls
2013-12-02 03:06:44 -05:00
ranges [ - 1 ] . extend ( [ last , lastName ] )
2017-03-14 16:14:08 +00:00
return ranges
2013-12-02 03:06:44 -05:00
2017-03-14 16:14:08 +00:00
def preWrite ( self , font ) :
format = 2
rawTable = { " ClassRangeRecord " : [ ] }
ranges = self . _getClassRanges ( font )
if ranges :
2013-12-02 03:06:44 -05:00
startGlyph = ranges [ 0 ] [ 1 ]
endGlyph = ranges [ - 1 ] [ 3 ]
glyphCount = endGlyph - startGlyph + 1
if len ( ranges ) * 3 < glyphCount + 1 :
# Format 2 is more compact
for i in range ( len ( ranges ) ) :
cls , start , startName , end , endName = ranges [ i ]
rec = ClassRangeRecord ( )
rec . Start = startName
rec . End = endName
rec . Class = cls
ranges [ i ] = rec
format = 2
rawTable = { " ClassRangeRecord " : ranges }
else :
# Format 1 is more compact
startGlyphName = ranges [ 0 ] [ 2 ]
classes = [ 0 ] * glyphCount
for cls , start , startName , end , endName in ranges :
for g in range ( start - startGlyph , end - startGlyph + 1 ) :
classes [ g ] = cls
format = 1
rawTable = { " StartGlyph " : startGlyphName , " ClassValueArray " : classes }
self . Format = format
return rawTable
2015-04-26 02:01:01 -04:00
2002-09-12 20:51:09 +00:00
def toXML2 ( self , xmlWriter , font ) :
2013-11-27 04:15:34 -05:00
items = sorted ( self . classDefs . items ( ) )
2002-09-12 20:51:09 +00:00
for glyphName , cls in items :
xmlWriter . simpletag ( " ClassDef " , [ ( " glyph " , glyphName ) , ( " class " , cls ) ] )
xmlWriter . newline ( )
2015-04-26 02:01:01 -04:00
2013-11-27 03:19:32 -05:00
def fromXML ( self , name , attrs , content , font ) :
2002-09-12 20:51:09 +00:00
classDefs = getattr ( self , " classDefs " , None )
if classDefs is None :
classDefs = { }
self . classDefs = classDefs
classDefs [ attrs [ " glyph " ] ] = int ( attrs [ " class " ] )
2002-09-12 21:21:58 +00:00
class AlternateSubst ( FormatSwitchingBaseTable ) :
2015-04-26 02:01:01 -04:00
2017-02-03 14:33:57 -08:00
def populateDefaults ( self , propagator = None ) :
if not hasattr ( self , ' alternates ' ) :
self . alternates = { }
2002-09-12 21:21:58 +00:00
def postRead ( self , rawTable , font ) :
alternates = { }
if self . Format == 1 :
2004-09-25 10:31:29 +00:00
input = _getGlyphsFromCoverageTable ( rawTable [ " Coverage " ] )
2002-09-12 21:21:58 +00:00
alts = rawTable [ " AlternateSet " ]
2015-10-27 14:11:04 -07:00
assert len ( input ) == len ( alts )
for inp , alt in zip ( input , alts ) :
alternates [ inp ] = alt . Alternate
2002-09-12 21:21:58 +00:00
else :
assert 0 , " unknown format: %s " % self . Format
self . alternates = alternates
2013-12-17 03:06:10 -05:00
del self . Format # Don't need this anymore
2015-04-26 02:01:01 -04:00
2002-09-12 21:21:58 +00:00
def preWrite ( self , font ) :
self . Format = 1
2005-02-11 19:36:38 +00:00
alternates = getattr ( self , " alternates " , None )
if alternates is None :
alternates = self . alternates = { }
2013-11-27 06:26:55 -05:00
items = list ( alternates . items ( ) )
2002-09-12 21:21:58 +00:00
for i in range ( len ( items ) ) :
glyphName , set = items [ i ]
items [ i ] = font . getGlyphID ( glyphName ) , glyphName , set
items . sort ( )
cov = Coverage ( )
2006-10-21 14:12:38 +00:00
cov . glyphs = [ item [ 1 ] for item in items ]
2002-09-12 21:21:58 +00:00
alternates = [ ]
2006-10-21 14:12:38 +00:00
setList = [ item [ - 1 ] for item in items ]
2017-02-11 16:48:27 +01:00
for set in setList :
2002-09-12 21:21:58 +00:00
alts = AlternateSet ( )
alts . Alternate = set
alternates . append ( alts )
2006-10-21 14:12:38 +00:00
# a special case to deal with the fact that several hundred Adobe Japan1-5
# CJK fonts will overflow an offset if the coverage table isn't pushed to the end.
# Also useful in that when splitting a sub-table because of an offset overflow
# I don't need to calculate the change in the subtable offset due to the change in the coverage table size.
# Allows packing more rules in subtable.
2015-04-26 02:01:01 -04:00
self . sortCoverageLast = 1
2002-09-12 21:21:58 +00:00
return { " Coverage " : cov , " AlternateSet " : alternates }
2015-04-26 02:01:01 -04:00
2002-09-12 21:21:58 +00:00
def toXML2 ( self , xmlWriter , font ) :
2013-11-27 04:15:34 -05:00
items = sorted ( self . alternates . items ( ) )
2002-09-12 21:21:58 +00:00
for glyphName , alternates in items :
xmlWriter . begintag ( " AlternateSet " , glyph = glyphName )
xmlWriter . newline ( )
2017-02-11 16:48:27 +01:00
for alt in alternates :
2002-09-12 21:21:58 +00:00
xmlWriter . simpletag ( " Alternate " , glyph = alt )
xmlWriter . newline ( )
xmlWriter . endtag ( " AlternateSet " )
xmlWriter . newline ( )
2015-04-26 02:01:01 -04:00
2013-11-27 03:19:32 -05:00
def fromXML ( self , name , attrs , content , font ) :
2002-09-12 21:21:58 +00:00
alternates = getattr ( self , " alternates " , None )
if alternates is None :
alternates = { }
self . alternates = alternates
glyphName = attrs [ " glyph " ]
set = [ ]
alternates [ glyphName ] = set
for element in content :
2013-11-27 05:17:37 -05:00
if not isinstance ( element , tuple ) :
2002-09-12 21:21:58 +00:00
continue
name , attrs , content = element
set . append ( attrs [ " glyph " ] )
2002-09-12 21:48:03 +00:00
class LigatureSubst ( FormatSwitchingBaseTable ) :
2015-04-26 02:01:01 -04:00
2017-02-03 14:33:57 -08:00
def populateDefaults ( self , propagator = None ) :
if not hasattr ( self , ' ligatures ' ) :
self . ligatures = { }
2002-09-12 21:48:03 +00:00
def postRead ( self , rawTable , font ) :
ligatures = { }
if self . Format == 1 :
2013-12-17 01:58:26 -05:00
input = _getGlyphsFromCoverageTable ( rawTable [ " Coverage " ] )
2002-09-12 21:48:03 +00:00
ligSets = rawTable [ " LigatureSet " ]
assert len ( input ) == len ( ligSets )
for i in range ( len ( input ) ) :
ligatures [ input [ i ] ] = ligSets [ i ] . Ligature
else :
assert 0 , " unknown format: %s " % self . Format
self . ligatures = ligatures
2013-12-17 03:06:10 -05:00
del self . Format # Don't need this anymore
2015-04-26 02:01:01 -04:00
2002-09-12 21:48:03 +00:00
def preWrite ( self , font ) :
2013-12-17 03:06:10 -05:00
self . Format = 1
2005-02-11 19:36:38 +00:00
ligatures = getattr ( self , " ligatures " , None )
if ligatures is None :
ligatures = self . ligatures = { }
2015-10-27 16:18:09 -07:00
if ligatures and isinstance ( next ( iter ( ligatures ) ) , tuple ) :
# New high-level API in v3.1 and later. Note that we just support compiling this
# for now. We don't load to this API, and don't do XML with it.
# ligatures is map from components-sequence to lig-glyph
newLigatures = dict ( )
2015-12-07 12:05:24 +01:00
for comps , lig in sorted ( ligatures . items ( ) , key = lambda item : ( - len ( item [ 0 ] ) , item [ 0 ] ) ) :
ligature = Ligature ( )
2015-10-27 16:18:09 -07:00
ligature . Component = comps [ 1 : ]
ligature . CompCount = len ( comps )
ligature . LigGlyph = lig
newLigatures . setdefault ( comps [ 0 ] , [ ] ) . append ( ligature )
ligatures = newLigatures
2013-11-27 06:26:55 -05:00
items = list ( ligatures . items ( ) )
2002-09-12 21:48:03 +00:00
for i in range ( len ( items ) ) :
glyphName , set = items [ i ]
items [ i ] = font . getGlyphID ( glyphName ) , glyphName , set
items . sort ( )
cov = Coverage ( )
2006-10-21 14:12:38 +00:00
cov . glyphs = [ item [ 1 ] for item in items ]
2002-09-12 21:48:03 +00:00
ligSets = [ ]
2006-10-21 14:12:38 +00:00
setList = [ item [ - 1 ] for item in items ]
for set in setList :
2002-09-12 21:48:03 +00:00
ligSet = LigatureSet ( )
ligs = ligSet . Ligature = [ ]
for lig in set :
ligs . append ( lig )
ligSets . append ( ligSet )
2006-10-21 14:12:38 +00:00
# Useful in that when splitting a sub-table because of an offset overflow
# I don't need to calculate the change in subtabl offset due to the coverage table size.
# Allows packing more rules in subtable.
2015-04-26 02:01:01 -04:00
self . sortCoverageLast = 1
2002-09-12 21:48:03 +00:00
return { " Coverage " : cov , " LigatureSet " : ligSets }
2015-04-26 02:01:01 -04:00
2002-09-12 21:48:03 +00:00
def toXML2 ( self , xmlWriter , font ) :
2013-11-27 04:15:34 -05:00
items = sorted ( self . ligatures . items ( ) )
2002-09-12 21:48:03 +00:00
for glyphName , ligSets in items :
xmlWriter . begintag ( " LigatureSet " , glyph = glyphName )
xmlWriter . newline ( )
for lig in ligSets :
xmlWriter . simpletag ( " Ligature " , glyph = lig . LigGlyph ,
components = " , " . join ( lig . Component ) )
xmlWriter . newline ( )
xmlWriter . endtag ( " LigatureSet " )
xmlWriter . newline ( )
2015-04-26 02:01:01 -04:00
2013-11-27 03:19:32 -05:00
def fromXML ( self , name , attrs , content , font ) :
2002-09-12 21:48:03 +00:00
ligatures = getattr ( self , " ligatures " , None )
if ligatures is None :
ligatures = { }
self . ligatures = ligatures
glyphName = attrs [ " glyph " ]
ligs = [ ]
ligatures [ glyphName ] = ligs
for element in content :
2013-11-27 05:17:37 -05:00
if not isinstance ( element , tuple ) :
2002-09-12 21:48:03 +00:00
continue
name , attrs , content = element
lig = Ligature ( )
lig . LigGlyph = attrs [ " glyph " ]
2014-06-05 17:03:31 -04:00
components = attrs [ " components " ]
lig . Component = components . split ( " , " ) if components else [ ]
2019-04-20 17:45:38 +01:00
lig . CompCount = len ( lig . Component )
2002-09-12 21:48:03 +00:00
ligs . append ( lig )
2020-02-04 17:03:40 +00:00
class COLR ( BaseTable ) :
def decompile ( self , reader , font ) :
# COLRv0 is exceptional in that LayerRecordCount appears *after* the
# LayerRecordArray it counts, but the parser logic expects Count fields
# to always precede the arrays. Here we work around this by parsing the
# LayerRecordCount before the rest of the table, and storing it in
# the reader's local state.
subReader = reader . getSubReader ( offset = 0 )
for conv in self . getConverters ( ) :
if conv . name != " LayerRecordCount " :
subReader . advance ( conv . staticSize )
continue
conv = self . getConverterByName ( " LayerRecordCount " )
reader [ conv . name ] = conv . read ( subReader , font , tableDict = { } )
break
else :
raise AssertionError ( " LayerRecordCount converter not found " )
return BaseTable . decompile ( self , reader , font )
2020-02-04 17:14:52 +00:00
def preWrite ( self , font ) :
# The writer similarly assumes Count values precede the things counted,
# thus here we pre-initialize a CountReference; the actual count value
# will be set to the lenght of the array by the time this is assembled.
self . LayerRecordCount = None
return {
* * self . __dict__ ,
" LayerRecordCount " : CountReference ( self . __dict__ , " LayerRecordCount " )
}
2020-02-04 17:03:40 +00:00
2020-08-25 14:07:19 +01:00
class LookupList ( BaseTable ) :
2020-09-09 19:56:44 +01:00
@property
2020-08-25 14:07:19 +01:00
def table ( self ) :
for l in self . Lookup :
for st in l . SubTable :
2020-09-11 09:35:45 +01:00
if type ( st ) . __name__ . endswith ( " Subst " ) :
return " GSUB "
if type ( st ) . __name__ . endswith ( " Pos " ) :
return " GPOS "
2020-08-25 14:07:19 +01:00
raise ValueError
def toXML2 ( self , xmlWriter , font ) :
2020-09-10 16:00:06 +01:00
if not font or " Debg " not in font or LOOKUP_DEBUG_INFO_KEY not in font [ " Debg " ] . data :
2020-08-25 14:07:19 +01:00
return super ( ) . toXML2 ( xmlWriter , font )
2020-09-10 16:00:06 +01:00
debugData = font [ " Debg " ] . data [ LOOKUP_DEBUG_INFO_KEY ] [ self . table ]
2020-08-25 14:07:19 +01:00
for conv in self . getConverters ( ) :
if conv . repeat :
value = getattr ( self , conv . name , [ ] )
2020-09-09 19:56:05 +01:00
for lookupIndex , item in enumerate ( value ) :
if str ( lookupIndex ) in debugData :
info = LookupDebugInfo ( * debugData [ str ( lookupIndex ) ] )
tag = info . location
if info . name :
tag = f ' { info . name } : { tag } '
if info . feature :
script , language , feature = info . feature
tag = f ' { tag } in { feature } ( { script } / { language } ) '
2020-08-25 14:07:19 +01:00
xmlWriter . comment ( tag )
xmlWriter . newline ( )
conv . xmlWrite ( xmlWriter , font , item , conv . name ,
2020-09-09 19:56:05 +01:00
[ ( " index " , lookupIndex ) ] )
2020-09-10 08:29:15 +01:00
else :
if conv . aux and not eval ( conv . aux , None , vars ( self ) ) :
continue
value = getattr ( self , conv . name , None ) # TODO Handle defaults instead of defaulting to None!
conv . xmlWrite ( xmlWriter , font , value , conv . name , [ ] )
2020-08-25 14:07:19 +01:00
2020-03-06 13:19:07 +00:00
class BaseGlyphRecordArray ( BaseTable ) :
def preWrite ( self , font ) :
self . BaseGlyphRecord = sorted (
self . BaseGlyphRecord ,
key = lambda rec : font . getGlyphID ( rec . BaseGlyph )
)
return self . __dict__ . copy ( )
2021-06-28 16:13:47 +01:00
class BaseGlyphList ( BaseTable ) :
2020-03-06 13:19:07 +00:00
def preWrite ( self , font ) :
2021-06-28 16:13:47 +01:00
self . BaseGlyphPaintRecord = sorted (
self . BaseGlyphPaintRecord ,
2020-03-06 13:19:07 +00:00
key = lambda rec : font . getGlyphID ( rec . BaseGlyph )
)
return self . __dict__ . copy ( )
2021-07-26 15:47:02 +01:00
class ClipBox ( getFormatSwitchingBaseTableClass ( " uint8 " ) ) :
def as_tuple ( self ) :
return tuple ( getattr ( self , conv . name ) for conv in self . getConverters ( ) )
def __repr__ ( self ) :
return f " { self . __class__ . __name__ } { self . as_tuple ( ) } "
2021-07-29 15:35:08 +02:00
class ClipList ( getFormatSwitchingBaseTableClass ( " uint8 " ) ) :
2021-07-26 15:47:02 +01:00
def populateDefaults ( self , propagator = None ) :
if not hasattr ( self , " clips " ) :
self . clips = { }
def postRead ( self , rawTable , font ) :
clips = { }
glyphOrder = font . getGlyphOrder ( )
for i , rec in enumerate ( rawTable [ " ClipRecord " ] ) :
2021-07-29 15:06:16 +02:00
if rec . StartGlyphID > rec . EndGlyphID :
log . warning (
" invalid ClipRecord[ %i ].StartGlyphID ( %i ) > "
" EndGlyphID ( %i ); skipped " ,
i ,
rec . StartGlyphID ,
rec . EndGlyphID ,
)
continue
redefinedGlyphs = [ ]
missingGlyphs = [ ]
2021-07-26 15:47:02 +01:00
for glyphID in range ( rec . StartGlyphID , rec . EndGlyphID + 1 ) :
try :
glyph = glyphOrder [ glyphID ]
except IndexError :
2021-07-29 15:06:16 +02:00
missingGlyphs . append ( glyphID )
2021-08-09 11:30:00 +02:00
continue
2021-07-26 15:47:02 +01:00
if glyph not in clips :
clips [ glyph ] = copy . copy ( rec . ClipBox )
else :
2021-07-29 15:06:16 +02:00
redefinedGlyphs . append ( glyphID )
if redefinedGlyphs :
log . warning (
" ClipRecord[ %i ] overlaps previous records; "
" ignoring redefined clip boxes for the "
" following glyph ID range: [ %i - %i ] " ,
i ,
min ( redefinedGlyphs ) ,
max ( redefinedGlyphs ) ,
)
if missingGlyphs :
2021-07-26 15:47:02 +01:00
log . warning (
2021-07-29 15:06:16 +02:00
" ClipRecord[ %i ] range references missing "
" glyph IDs: [ %i - %i ] " ,
2021-07-26 15:47:02 +01:00
i ,
2021-07-29 15:06:16 +02:00
min ( missingGlyphs ) ,
max ( missingGlyphs ) ,
2021-07-26 15:47:02 +01:00
)
self . clips = clips
def groups ( self ) :
glyphsByClip = defaultdict ( list )
uniqueClips = { }
for glyphName , clipBox in self . clips . items ( ) :
2021-07-29 15:06:16 +02:00
key = clipBox . as_tuple ( )
2021-07-26 15:47:02 +01:00
glyphsByClip [ key ] . append ( glyphName )
if key not in uniqueClips :
uniqueClips [ key ] = clipBox
return {
frozenset ( glyphs ) : uniqueClips [ key ]
for key , glyphs in glyphsByClip . items ( )
}
def preWrite ( self , font ) :
if not hasattr ( self , " clips " ) :
self . clips = { }
clipBoxRanges = { }
glyphMap = font . getReverseGlyphMap ( )
for glyphs , clipBox in self . groups ( ) . items ( ) :
glyphIDs = sorted (
glyphMap [ glyphName ] for glyphName in glyphs
if glyphName in glyphMap
)
if not glyphIDs :
continue
last = glyphIDs [ 0 ]
ranges = [ [ last ] ]
for glyphID in glyphIDs [ 1 : ] :
if glyphID != last + 1 :
ranges [ - 1 ] . append ( last )
ranges . append ( [ glyphID ] )
last = glyphID
ranges [ - 1 ] . append ( last )
for start , end in ranges :
assert ( start , end ) not in clipBoxRanges
clipBoxRanges [ ( start , end ) ] = clipBox
clipRecords = [ ]
for ( start , end ) , clipBox in sorted ( clipBoxRanges . items ( ) ) :
record = ClipRecord ( )
record . StartGlyphID = start
record . EndGlyphID = end
record . ClipBox = clipBox
clipRecords . append ( record )
rawTable = {
" ClipCount " : len ( clipRecords ) ,
" ClipRecord " : clipRecords ,
}
return rawTable
def toXML ( self , xmlWriter , font , attrs = None , name = None ) :
tableName = name if name else self . __class__ . __name__
if attrs is None :
attrs = [ ]
2021-07-29 15:35:08 +02:00
if hasattr ( self , " Format " ) :
attrs . append ( ( " Format " , self . Format ) )
2021-07-26 15:47:02 +01:00
xmlWriter . begintag ( tableName , attrs )
xmlWriter . newline ( )
# sort clips alphabetically to ensure deterministic XML dump
for glyphs , clipBox in sorted (
self . groups ( ) . items ( ) , key = lambda item : min ( item [ 0 ] )
) :
xmlWriter . begintag ( " Clip " )
xmlWriter . newline ( )
for glyphName in sorted ( glyphs ) :
xmlWriter . simpletag ( " Glyph " , value = glyphName )
xmlWriter . newline ( )
xmlWriter . begintag ( " ClipBox " , [ ( " Format " , clipBox . Format ) ] )
xmlWriter . newline ( )
clipBox . toXML2 ( xmlWriter , font )
xmlWriter . endtag ( " ClipBox " )
xmlWriter . newline ( )
xmlWriter . endtag ( " Clip " )
xmlWriter . newline ( )
xmlWriter . endtag ( tableName )
xmlWriter . newline ( )
def fromXML ( self , name , attrs , content , font ) :
clips = getattr ( self , " clips " , None )
if clips is None :
self . clips = clips = { }
assert name == " Clip "
glyphs = [ ]
clipBox = None
for elem in content :
if not isinstance ( elem , tuple ) :
continue
name , attrs , content = elem
if name == " Glyph " :
glyphs . append ( attrs [ " value " ] )
elif name == " ClipBox " :
clipBox = ClipBox ( )
clipBox . Format = safeEval ( attrs [ " Format " ] )
for elem in content :
if not isinstance ( elem , tuple ) :
continue
name , attrs , content = elem
clipBox . fromXML ( name , attrs , content , font )
if clipBox :
for glyphName in glyphs :
clips [ glyphName ] = clipBox
2020-03-06 13:19:07 +00:00
class ExtendMode ( IntEnum ) :
PAD = 0
REPEAT = 1
REFLECT = 2
2020-10-09 17:01:31 +01:00
# Porter-Duff modes for COLRv1 PaintComposite:
# https://github.com/googlefonts/colr-gradients-spec/tree/off_sub_1#compositemode-enumeration
class CompositeMode ( IntEnum ) :
CLEAR = 0
SRC = 1
DEST = 2
SRC_OVER = 3
DEST_OVER = 4
SRC_IN = 5
DEST_IN = 6
SRC_OUT = 7
DEST_OUT = 8
SRC_ATOP = 9
DEST_ATOP = 10
XOR = 11
2021-08-09 11:27:46 +02:00
PLUS = 12
SCREEN = 13
OVERLAY = 14
DARKEN = 15
LIGHTEN = 16
COLOR_DODGE = 17
COLOR_BURN = 18
HARD_LIGHT = 19
SOFT_LIGHT = 20
DIFFERENCE = 21
EXCLUSION = 22
MULTIPLY = 23
HSL_HUE = 24
HSL_SATURATION = 25
HSL_COLOR = 26
HSL_LUMINOSITY = 27
2020-10-09 17:01:31 +01:00
2021-02-05 12:11:43 +00:00
class PaintFormat ( IntEnum ) :
PaintColrLayers = 1
PaintSolid = 2
2021-02-09 16:25:09 -08:00
PaintVarSolid = 3 ,
PaintLinearGradient = 4
PaintVarLinearGradient = 5
PaintRadialGradient = 6
PaintVarRadialGradient = 7
PaintSweepGradient = 8
PaintVarSweepGradient = 9
PaintGlyph = 10
PaintColrGlyph = 11
PaintTransform = 12
PaintVarTransform = 13
PaintTranslate = 14
PaintVarTranslate = 15
2021-06-28 18:38:47 +01:00
PaintScale = 16
PaintVarScale = 17
PaintScaleAroundCenter = 18
PaintVarScaleAroundCenter = 19
PaintScaleUniform = 20
PaintVarScaleUniform = 21
PaintScaleUniformAroundCenter = 22
PaintVarScaleUniformAroundCenter = 23
PaintRotate = 24
PaintVarRotate = 25
PaintRotateAroundCenter = 26
PaintVarRotateAroundCenter = 27
PaintSkew = 28
PaintVarSkew = 29
PaintSkewAroundCenter = 30
PaintVarSkewAroundCenter = 31
PaintComposite = 32
2021-02-05 12:11:43 +00:00
2020-10-20 19:03:17 +01:00
2021-02-05 12:11:43 +00:00
class Paint ( getFormatSwitchingBaseTableClass ( " uint8 " ) ) :
2020-10-20 19:03:17 +01:00
def getFormatName ( self ) :
try :
2021-02-05 12:11:43 +00:00
return PaintFormat ( self . Format ) . name
2020-10-20 19:03:17 +01:00
except ValueError :
raise NotImplementedError ( f " Unknown Paint format: { self . Format } " )
def toXML ( self , xmlWriter , font , attrs = None , name = None ) :
tableName = name if name else self . __class__ . __name__
if attrs is None :
attrs = [ ]
attrs . append ( ( " Format " , self . Format ) )
xmlWriter . begintag ( tableName , attrs )
xmlWriter . comment ( self . getFormatName ( ) )
xmlWriter . newline ( )
self . toXML2 ( xmlWriter , font )
xmlWriter . endtag ( tableName )
xmlWriter . newline ( )
2021-02-05 17:17:34 +00:00
def getChildren ( self , colr ) :
if self . Format == PaintFormat . PaintColrLayers :
2021-11-06 14:26:27 -07:00
# https://github.com/fonttools/fonttools/issues/2438: don't die when no LayerList exists
layers = [ ]
if colr . LayerList is not None :
layers = colr . LayerList . Paint
return layers [
2021-02-05 17:17:34 +00:00
self . FirstLayerIndex : self . FirstLayerIndex + self . NumLayers
]
if self . Format == PaintFormat . PaintColrGlyph :
2021-06-28 16:13:47 +01:00
for record in colr . BaseGlyphList . BaseGlyphPaintRecord :
2021-02-05 17:17:34 +00:00
if record . BaseGlyph == self . Glyph :
return [ record . Paint ]
else :
2021-06-28 16:13:47 +01:00
raise KeyError ( f " { self . Glyph !r} not in colr.BaseGlyphList " )
2021-02-05 17:17:34 +00:00
children = [ ]
for conv in self . getConverters ( ) :
if conv . tableClass is not None and issubclass ( conv . tableClass , type ( self ) ) :
children . append ( getattr ( self , conv . name ) )
return children
def traverse ( self , colr : COLR , callback ) :
""" Depth-first traversal of graph rooted at self, callback on each node. """
if not callable ( callback ) :
raise TypeError ( " callback must be callable " )
stack = [ self ]
visited = set ( )
while stack :
current = stack . pop ( )
if id ( current ) in visited :
continue
callback ( current )
visited . add ( id ( current ) )
stack . extend ( reversed ( current . getChildren ( colr ) ) )
2020-10-20 19:03:17 +01:00
2002-05-11 10:21:36 +00:00
# For each subtable format there is a class. However, we don't really distinguish
# between "field name" and "format name": often these are the same. Yet there's
# a whole bunch of fields with different names. The following dict is a mapping
# from "format name" to "field name". _buildClasses() uses this to create a
# subclass for each alternate field name.
#
_equivalents = {
' MarkArray ' : ( " Mark1Array " , ) ,
' LangSys ' : ( ' DefaultLangSys ' , ) ,
' Coverage ' : ( ' MarkCoverage ' , ' BaseCoverage ' , ' LigatureCoverage ' , ' Mark1Coverage ' ,
2002-05-11 00:59:27 +00:00
' Mark2Coverage ' , ' BacktrackCoverage ' , ' InputCoverage ' ,
2013-12-09 00:40:48 -05:00
' LookAheadCoverage ' , ' VertGlyphCoverage ' , ' HorizGlyphCoverage ' ,
' TopAccentCoverage ' , ' ExtendedShapeCoverage ' , ' MathKernCoverage ' ) ,
2002-05-11 10:21:36 +00:00
' ClassDef ' : ( ' ClassDef1 ' , ' ClassDef2 ' , ' BacktrackClassDef ' , ' InputClassDef ' ,
2003-09-11 07:11:35 +00:00
' LookAheadClassDef ' , ' GlyphClassDef ' , ' MarkAttachClassDef ' ) ,
2002-05-11 10:21:36 +00:00
' Anchor ' : ( ' EntryAnchor ' , ' ExitAnchor ' , ' BaseAnchor ' , ' LigatureAnchor ' ,
' Mark2Anchor ' , ' MarkAnchor ' ) ,
' Device ' : ( ' XPlaDevice ' , ' YPlaDevice ' , ' XAdvDevice ' , ' YAdvDevice ' ,
' XDeviceTable ' , ' YDeviceTable ' , ' DeviceTable ' ) ,
' Axis ' : ( ' HorizAxis ' , ' VertAxis ' , ) ,
' MinMax ' : ( ' DefaultMinMax ' , ) ,
' BaseCoord ' : ( ' MinCoord ' , ' MaxCoord ' , ) ,
' JstfLangSys ' : ( ' DefJstfLangSys ' , ) ,
' JstfGSUBModList ' : ( ' ShrinkageEnableGSUB ' , ' ShrinkageDisableGSUB ' , ' ExtensionEnableGSUB ' ,
' ExtensionDisableGSUB ' , ) ,
' JstfGPOSModList ' : ( ' ShrinkageEnableGPOS ' , ' ShrinkageDisableGPOS ' , ' ExtensionEnableGPOS ' ,
' ExtensionDisableGPOS ' , ) ,
' JstfMax ' : ( ' ShrinkageJstfMax ' , ' ExtensionJstfMax ' , ) ,
2013-12-09 00:40:48 -05:00
' MathKern ' : ( ' TopRightMathKern ' , ' TopLeftMathKern ' , ' BottomRightMathKern ' ,
' BottomLeftMathKern ' ) ,
' MathGlyphConstruction ' : ( ' VertGlyphConstruction ' , ' HorizGlyphConstruction ' ) ,
2002-05-11 10:21:36 +00:00
}
2002-05-11 00:59:27 +00:00
2008-03-09 21:43:19 +00:00
#
# OverFlow logic, to automatically create ExtensionLookups
# XXX This should probably move to otBase.py
#
2002-05-11 00:59:27 +00:00
2006-10-21 14:12:38 +00:00
def fixLookupOverFlows ( ttf , overflowRecord ) :
""" Either the offset from the LookupList to a lookup overflowed, or
2015-04-26 02:01:01 -04:00
an offset from a lookup to a subtable overflowed .
2006-10-21 14:12:38 +00:00
The table layout is :
GPSO / GUSB
Script List
Feature List
LookUpList
Lookup [ 0 ] and contents
SubTable offset list
SubTable [ 0 ] and contents
. . .
SubTable [ n ] and contents
. . .
Lookup [ n ] and contents
SubTable offset list
SubTable [ 0 ] and contents
. . .
SubTable [ n ] and contents
2013-12-04 16:31:44 -05:00
If the offset to a lookup overflowed ( SubTableIndex is None )
2006-10-21 14:12:38 +00:00
we must promote the * previous * lookup to an Extension type .
2015-04-26 02:01:01 -04:00
If the offset from a lookup to subtable overflowed , then we must promote it
2006-10-21 14:12:38 +00:00
to an Extension Lookup type .
"""
ok = 0
lookupIndex = overflowRecord . LookupListIndex
2013-12-04 16:31:44 -05:00
if ( overflowRecord . SubTableIndex is None ) :
2006-10-21 14:12:38 +00:00
lookupIndex = lookupIndex - 1
if lookupIndex < 0 :
return ok
if overflowRecord . tableType == ' GSUB ' :
extType = 7
elif overflowRecord . tableType == ' GPOS ' :
extType = 9
lookups = ttf [ overflowRecord . tableType ] . table . LookupList . Lookup
lookup = lookups [ lookupIndex ]
# If the previous lookup is an extType, look further back. Very unlikely, but possible.
2014-02-20 16:31:15 -05:00
while lookup . SubTable [ 0 ] . __class__ . LookupType == extType :
2006-10-21 14:12:38 +00:00
lookupIndex = lookupIndex - 1
if lookupIndex < 0 :
return ok
lookup = lookups [ lookupIndex ]
2015-04-26 02:01:01 -04:00
2017-01-09 19:52:32 -08:00
lookup . LookupType = extType
2006-10-21 14:12:38 +00:00
for si in range ( len ( lookup . SubTable ) ) :
subTable = lookup . SubTable [ si ]
extSubTableClass = lookupTypes [ overflowRecord . tableType ] [ extType ]
extSubTable = extSubTableClass ( )
extSubTable . Format = 1
extSubTable . ExtSubTable = subTable
lookup . SubTable [ si ] = extSubTable
ok = 1
return ok
2019-08-24 13:29:53 -04:00
def splitMultipleSubst ( oldSubTable , newSubTable , overflowRecord ) :
ok = 1
oldMapping = sorted ( oldSubTable . mapping . items ( ) )
oldLen = len ( oldMapping )
if overflowRecord . itemName in [ ' Coverage ' , ' RangeRecord ' ] :
# Coverage table is written last. Overflow is to or within the
# the coverage table. We will just cut the subtable in half.
newLen = oldLen / / 2
elif overflowRecord . itemName == ' Sequence ' :
# We just need to back up by two items from the overflowed
# Sequence index to make sure the offset to the Coverage table
# doesn't overflow.
newLen = overflowRecord . itemIndex - 1
newSubTable . mapping = { }
for i in range ( newLen , oldLen ) :
item = oldMapping [ i ]
key = item [ 0 ]
newSubTable . mapping [ key ] = item [ 1 ]
del oldSubTable . mapping [ key ]
return ok
2006-10-21 14:12:38 +00:00
def splitAlternateSubst ( oldSubTable , newSubTable , overflowRecord ) :
ok = 1
if hasattr ( oldSubTable , ' sortCoverageLast ' ) :
newSubTable . sortCoverageLast = oldSubTable . sortCoverageLast
2015-04-26 02:01:01 -04:00
2013-11-27 04:15:34 -05:00
oldAlts = sorted ( oldSubTable . alternates . items ( ) )
2006-10-21 14:12:38 +00:00
oldLen = len ( oldAlts )
if overflowRecord . itemName in [ ' Coverage ' , ' RangeRecord ' ] :
# Coverage table is written last. overflow is to or within the
# the coverage table. We will just cut the subtable in half.
2013-11-27 17:46:17 -05:00
newLen = oldLen / / 2
2006-10-21 14:12:38 +00:00
elif overflowRecord . itemName == ' AlternateSet ' :
2015-04-26 02:01:01 -04:00
# We just need to back up by two items
2006-10-21 14:12:38 +00:00
# from the overflowed AlternateSet index to make sure the offset
# to the Coverage table doesn't overflow.
2015-04-26 00:54:30 -04:00
newLen = overflowRecord . itemIndex - 1
2006-10-21 14:12:38 +00:00
newSubTable . alternates = { }
for i in range ( newLen , oldLen ) :
item = oldAlts [ i ]
key = item [ 0 ]
newSubTable . alternates [ key ] = item [ 1 ]
del oldSubTable . alternates [ key ]
return ok
def splitLigatureSubst ( oldSubTable , newSubTable , overflowRecord ) :
ok = 1
2013-11-27 04:15:34 -05:00
oldLigs = sorted ( oldSubTable . ligatures . items ( ) )
2006-10-21 14:12:38 +00:00
oldLen = len ( oldLigs )
if overflowRecord . itemName in [ ' Coverage ' , ' RangeRecord ' ] :
# Coverage table is written last. overflow is to or within the
# the coverage table. We will just cut the subtable in half.
2013-11-27 17:46:17 -05:00
newLen = oldLen / / 2
2006-10-21 14:12:38 +00:00
elif overflowRecord . itemName == ' LigatureSet ' :
2015-04-26 02:01:01 -04:00
# We just need to back up by two items
2006-10-21 14:12:38 +00:00
# from the overflowed AlternateSet index to make sure the offset
# to the Coverage table doesn't overflow.
2015-04-26 00:54:30 -04:00
newLen = overflowRecord . itemIndex - 1
2006-10-21 14:12:38 +00:00
newSubTable . ligatures = { }
for i in range ( newLen , oldLen ) :
item = oldLigs [ i ]
key = item [ 0 ]
newSubTable . ligatures [ key ] = item [ 1 ]
del oldSubTable . ligatures [ key ]
return ok
2016-03-12 18:17:37 -08:00
def splitPairPos ( oldSubTable , newSubTable , overflowRecord ) :
st = oldSubTable
ok = False
newSubTable . Format = oldSubTable . Format
2016-11-01 18:14:55 -07:00
if oldSubTable . Format == 1 and len ( oldSubTable . PairSet ) > 1 :
for name in ' ValueFormat1 ' , ' ValueFormat2 ' :
setattr ( newSubTable , name , getattr ( oldSubTable , name ) )
# Move top half of coverage to new subtable
newSubTable . Coverage = oldSubTable . Coverage . __class__ ( )
coverage = oldSubTable . Coverage . glyphs
records = oldSubTable . PairSet
oldCount = len ( oldSubTable . PairSet ) / / 2
oldSubTable . Coverage . glyphs = coverage [ : oldCount ]
oldSubTable . PairSet = records [ : oldCount ]
newSubTable . Coverage . glyphs = coverage [ oldCount : ]
newSubTable . PairSet = records [ oldCount : ]
oldSubTable . PairSetCount = len ( oldSubTable . PairSet )
newSubTable . PairSetCount = len ( newSubTable . PairSet )
ok = True
elif oldSubTable . Format == 2 and len ( oldSubTable . Class1Record ) > 1 :
2016-04-13 16:16:22 -07:00
if not hasattr ( oldSubTable , ' Class2Count ' ) :
oldSubTable . Class2Count = len ( oldSubTable . Class1Record [ 0 ] . Class2Record )
2016-03-12 18:17:37 -08:00
for name in ' Class2Count ' , ' ClassDef2 ' , ' ValueFormat1 ' , ' ValueFormat2 ' :
setattr ( newSubTable , name , getattr ( oldSubTable , name ) )
2016-04-09 13:06:16 -07:00
# The two subtables will still have the same ClassDef2 and the table
# sharing will still cause the sharing to overflow. As such, disable
# sharing on the one that is serialized second (that's oldSubTable).
oldSubTable . DontShare = True
2016-03-12 18:17:37 -08:00
# Move top half of class numbers to new subtable
newSubTable . Coverage = oldSubTable . Coverage . __class__ ( )
newSubTable . ClassDef1 = oldSubTable . ClassDef1 . __class__ ( )
coverage = oldSubTable . Coverage . glyphs
classDefs = oldSubTable . ClassDef1 . classDefs
records = oldSubTable . Class1Record
2016-04-13 16:16:22 -07:00
oldCount = len ( oldSubTable . Class1Record ) / / 2
2016-03-12 18:17:37 -08:00
newGlyphs = set ( k for k , v in classDefs . items ( ) if v > = oldCount )
oldSubTable . Coverage . glyphs = [ g for g in coverage if g not in newGlyphs ]
oldSubTable . ClassDef1 . classDefs = { k : v for k , v in classDefs . items ( ) if v < oldCount }
oldSubTable . Class1Record = records [ : oldCount ]
newSubTable . Coverage . glyphs = [ g for g in coverage if g in newGlyphs ]
2017-04-06 15:34:00 +02:00
newSubTable . ClassDef1 . classDefs = { k : ( v - oldCount ) for k , v in classDefs . items ( ) if v > oldCount }
2016-03-12 18:17:37 -08:00
newSubTable . Class1Record = records [ oldCount : ]
oldSubTable . Class1Count = len ( oldSubTable . Class1Record )
newSubTable . Class1Count = len ( newSubTable . Class1Record )
ok = True
return ok
2018-07-19 17:52:00 +01:00
def splitMarkBasePos ( oldSubTable , newSubTable , overflowRecord ) :
# split half of the mark classes to the new subtable
classCount = oldSubTable . ClassCount
if classCount < 2 :
# oh well, not much left to split...
return False
oldClassCount = classCount / / 2
newClassCount = classCount - oldClassCount
oldMarkCoverage , oldMarkRecords = [ ] , [ ]
newMarkCoverage , newMarkRecords = [ ] , [ ]
for glyphName , markRecord in zip (
oldSubTable . MarkCoverage . glyphs ,
oldSubTable . MarkArray . MarkRecord
) :
if markRecord . Class < oldClassCount :
oldMarkCoverage . append ( glyphName )
oldMarkRecords . append ( markRecord )
else :
2020-01-07 13:24:40 +00:00
markRecord . Class - = oldClassCount
2018-07-19 17:52:00 +01:00
newMarkCoverage . append ( glyphName )
newMarkRecords . append ( markRecord )
oldBaseRecords , newBaseRecords = [ ] , [ ]
for rec in oldSubTable . BaseArray . BaseRecord :
oldBaseRecord , newBaseRecord = rec . __class__ ( ) , rec . __class__ ( )
oldBaseRecord . BaseAnchor = rec . BaseAnchor [ : oldClassCount ]
newBaseRecord . BaseAnchor = rec . BaseAnchor [ oldClassCount : ]
oldBaseRecords . append ( oldBaseRecord )
newBaseRecords . append ( newBaseRecord )
newSubTable . Format = oldSubTable . Format
oldSubTable . MarkCoverage . glyphs = oldMarkCoverage
newSubTable . MarkCoverage = oldSubTable . MarkCoverage . __class__ ( )
newSubTable . MarkCoverage . glyphs = newMarkCoverage
# share the same BaseCoverage in both halves
newSubTable . BaseCoverage = oldSubTable . BaseCoverage
oldSubTable . ClassCount = oldClassCount
newSubTable . ClassCount = newClassCount
oldSubTable . MarkArray . MarkRecord = oldMarkRecords
newSubTable . MarkArray = oldSubTable . MarkArray . __class__ ( )
newSubTable . MarkArray . MarkRecord = newMarkRecords
oldSubTable . MarkArray . MarkCount = len ( oldMarkRecords )
newSubTable . MarkArray . MarkCount = len ( newMarkRecords )
oldSubTable . BaseArray . BaseRecord = oldBaseRecords
newSubTable . BaseArray = oldSubTable . BaseArray . __class__ ( )
newSubTable . BaseArray . BaseRecord = newBaseRecords
oldSubTable . BaseArray . BaseCount = len ( oldBaseRecords )
newSubTable . BaseArray . BaseCount = len ( newBaseRecords )
return True
2006-10-21 14:12:38 +00:00
splitTable = { ' GSUB ' : {
# 1: splitSingleSubst,
2019-08-24 13:29:53 -04:00
2 : splitMultipleSubst ,
2006-10-21 14:12:38 +00:00
3 : splitAlternateSubst ,
4 : splitLigatureSubst ,
# 5: splitContextSubst,
# 6: splitChainContextSubst,
# 7: splitExtensionSubst,
# 8: splitReverseChainSingleSubst,
} ,
' GPOS ' : {
# 1: splitSinglePos,
2016-03-12 18:17:37 -08:00
2 : splitPairPos ,
2006-10-21 14:12:38 +00:00
# 3: splitCursivePos,
2018-07-19 17:52:00 +01:00
4 : splitMarkBasePos ,
2006-10-21 14:12:38 +00:00
# 5: splitMarkLigPos,
# 6: splitMarkMarkPos,
# 7: splitContextPos,
# 8: splitChainContextPos,
# 9: splitExtensionPos,
}
}
def fixSubTableOverFlows ( ttf , overflowRecord ) :
2015-04-26 02:01:01 -04:00
"""
2006-10-21 14:12:38 +00:00
An offset has overflowed within a sub - table . We need to divide this subtable into smaller parts .
"""
table = ttf [ overflowRecord . tableType ] . table
lookup = table . LookupList . Lookup [ overflowRecord . LookupListIndex ]
subIndex = overflowRecord . SubTableIndex
subtable = lookup . SubTable [ subIndex ]
2016-04-13 16:34:14 -07:00
# First, try not sharing anything for this subtable...
if not hasattr ( subtable , " DontShare " ) :
subtable . DontShare = True
return True
2006-10-21 14:12:38 +00:00
if hasattr ( subtable , ' ExtSubTable ' ) :
# We split the subtable of the Extension table, and add a new Extension table
# to contain the new subtable.
2014-02-20 16:31:15 -05:00
subTableType = subtable . ExtSubTable . __class__ . LookupType
2006-10-21 14:12:38 +00:00
extSubTable = subtable
subtable = extSubTable . ExtSubTable
2016-07-12 23:55:47 -07:00
newExtSubTableClass = lookupTypes [ overflowRecord . tableType ] [ extSubTable . __class__ . LookupType ]
2006-10-21 14:12:38 +00:00
newExtSubTable = newExtSubTableClass ( )
newExtSubTable . Format = extSubTable . Format
2018-07-25 11:04:48 -07:00
toInsert = newExtSubTable
2006-10-21 14:12:38 +00:00
newSubTableClass = lookupTypes [ overflowRecord . tableType ] [ subTableType ]
newSubTable = newSubTableClass ( )
newExtSubTable . ExtSubTable = newSubTable
else :
2014-02-20 16:31:15 -05:00
subTableType = subtable . __class__ . LookupType
2006-10-21 14:12:38 +00:00
newSubTableClass = lookupTypes [ overflowRecord . tableType ] [ subTableType ]
newSubTable = newSubTableClass ( )
2018-07-25 11:04:48 -07:00
toInsert = newSubTable
2006-10-21 14:12:38 +00:00
if hasattr ( lookup , ' SubTableCount ' ) : # may not be defined yet.
lookup . SubTableCount = lookup . SubTableCount + 1
try :
splitFunc = splitTable [ overflowRecord . tableType ] [ subTableType ]
except KeyError :
2018-07-19 17:50:43 +01:00
log . error (
" Don ' t know how to split %s lookup type %s " ,
overflowRecord . tableType ,
subTableType ,
)
2018-07-25 11:04:48 -07:00
return False
2006-10-21 14:12:38 +00:00
ok = splitFunc ( subtable , newSubTable , overflowRecord )
2018-07-25 11:04:48 -07:00
if ok :
lookup . SubTable . insert ( subIndex + 1 , toInsert )
2006-10-21 14:12:38 +00:00
return ok
2008-03-09 21:43:19 +00:00
# End of OverFlow logic
2006-10-21 14:12:38 +00:00
2002-05-11 00:59:27 +00:00
def _buildClasses ( ) :
2013-11-27 05:57:39 -05:00
import re
2013-11-27 02:34:11 -05:00
from . otData import otData
2015-04-26 02:01:01 -04:00
2019-04-01 13:53:59 -07:00
formatPat = re . compile ( r " ([A-Za-z0-9]+)Format( \ d+)$ " )
2002-05-11 00:59:27 +00:00
namespace = globals ( )
2015-04-26 02:01:01 -04:00
2002-05-11 00:59:27 +00:00
# populate module with classes
for name , table in otData :
baseClass = BaseTable
m = formatPat . match ( name )
if m :
# XxxFormatN subtable, we only add the "base" table
name = m . group ( 1 )
2020-10-27 18:35:36 +00:00
# the first row of a format-switching otData table describes the Format;
# the first column defines the type of the Format field.
# Currently this can be either 'uint16' or 'uint8'.
formatType = table [ 0 ] [ 0 ]
2020-10-07 18:45:56 +01:00
baseClass = getFormatSwitchingBaseTableClass ( formatType )
2013-11-27 02:33:03 -05:00
if name not in namespace :
2002-05-11 10:21:36 +00:00
# the class doesn't exist yet, so the base implementation is used.
2013-11-27 05:57:39 -05:00
cls = type ( name , ( baseClass , ) , { } )
2016-02-10 18:00:48 +07:00
if name in ( ' GSUB ' , ' GPOS ' ) :
cls . DontShare = True
2002-05-11 00:59:27 +00:00
namespace [ name ] = cls
2015-04-26 02:01:01 -04:00
2002-05-11 10:21:36 +00:00
for base , alts in _equivalents . items ( ) :
2002-05-11 00:59:27 +00:00
base = namespace [ base ]
for alt in alts :
2016-01-13 15:58:18 +00:00
namespace [ alt ] = base
2015-04-26 02:01:01 -04:00
2002-05-11 00:59:27 +00:00
global lookupTypes
lookupTypes = {
' GSUB ' : {
1 : SingleSubst ,
2 : MultipleSubst ,
3 : AlternateSubst ,
4 : LigatureSubst ,
5 : ContextSubst ,
6 : ChainContextSubst ,
7 : ExtensionSubst ,
2003-09-22 13:12:55 +00:00
8 : ReverseChainSingleSubst ,
2002-05-11 00:59:27 +00:00
} ,
' GPOS ' : {
1 : SinglePos ,
2 : PairPos ,
3 : CursivePos ,
4 : MarkBasePos ,
5 : MarkLigPos ,
6 : MarkMarkPos ,
7 : ContextPos ,
8 : ChainContextPos ,
9 : ExtensionPos ,
} ,
2017-08-29 12:52:30 +02:00
' mort ' : {
4 : NoncontextualMorph ,
} ,
2017-06-07 11:36:15 +02:00
' morx ' : {
2017-09-04 12:29:55 +02:00
0 : RearrangementMorph ,
2017-09-05 18:32:05 +02:00
1 : ContextualMorph ,
2017-09-12 19:28:17 +02:00
2 : LigatureMorph ,
2017-06-07 11:36:15 +02:00
# 3: Reserved,
4 : NoncontextualMorph ,
2018-09-24 17:28:24 +02:00
5 : InsertionMorph ,
2017-06-07 11:36:15 +02:00
} ,
2002-05-11 00:59:27 +00:00
}
lookupTypes [ ' JSTF ' ] = lookupTypes [ ' GPOS ' ] # JSTF contains GPOS
2002-05-11 10:21:36 +00:00
for lookupEnum in lookupTypes . values ( ) :
for enum , cls in lookupEnum . items ( ) :
cls . LookupType = enum
2013-11-26 19:23:08 -05:00
global featureParamTypes
featureParamTypes = {
' size ' : FeatureParamsSize ,
}
for i in range ( 1 , 20 + 1 ) :
featureParamTypes [ ' ss %02d ' % i ] = FeatureParamsStylisticSet
for i in range ( 1 , 99 + 1 ) :
featureParamTypes [ ' cv %02d ' % i ] = FeatureParamsCharacterVariants
2015-04-26 02:01:01 -04:00
2002-05-11 00:59:27 +00:00
# add converters to classes
2013-11-27 02:34:11 -05:00
from . otConverters import buildConverters
2002-05-11 00:59:27 +00:00
for name , table in otData :
m = formatPat . match ( name )
if m :
# XxxFormatN subtable, add converter to "base" table
name , format = m . groups ( )
format = int ( format )
cls = namespace [ name ]
if not hasattr ( cls , " converters " ) :
cls . converters = { }
cls . convertersByName = { }
2002-05-11 10:21:36 +00:00
converters , convertersByName = buildConverters ( table [ 1 : ] , namespace )
2002-05-11 00:59:27 +00:00
cls . converters [ format ] = converters
cls . convertersByName [ format ] = convertersByName
2015-07-02 18:00:41 -07:00
# XXX Add staticSize?
2002-05-11 00:59:27 +00:00
else :
cls = namespace [ name ]
2002-05-11 10:21:36 +00:00
cls . converters , cls . convertersByName = buildConverters ( table , namespace )
2015-07-02 18:00:41 -07:00
# XXX Add staticSize?
2002-05-11 00:59:27 +00:00
_buildClasses ( )
2004-09-25 10:31:29 +00:00
def _getGlyphsFromCoverageTable ( coverage ) :
if coverage is None :
# empty coverage table
return [ ]
else :
return coverage . glyphs