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
"""
2013-11-27 17:46:17 -05:00
from __future__ import print_function , division
2013-11-27 17:27:45 -05:00
from fontTools . misc . py23 import *
2013-11-27 02:34:11 -05:00
from . otBase import BaseTable , FormatSwitchingBaseTable
2013-11-27 17:27:45 -05:00
import operator
2013-11-15 15:56:49 -05:00
import warnings
2002-05-11 00:59:27 +00:00
class LookupOrder ( BaseTable ) :
""" Dummy class; this table isn ' t defined, but is used, and is always NULL. """
class FeatureParams ( BaseTable ) :
2013-11-26 19:23:08 -05:00
def compile ( self , writer , font ) :
assert featureParamTypes . get ( writer [ ' FeatureTag ' ] , None ) == self . __class__ , " Wrong FeatureParams type for feature ' %s ' : %s " % ( writer [ ' FeatureTag ' ] , self . __class__ . __name__ )
BaseTable . compile ( self , writer , font )
class FeatureParamsSize ( FeatureParams ) :
pass
class FeatureParamsStylisticSet ( FeatureParams ) :
pass
class FeatureParamsCharacterVariants ( FeatureParams ) :
pass
2002-09-12 16:47:02 +00:00
class Coverage ( FormatSwitchingBaseTable ) :
# manual implementation to get rid of glyphID dependencies
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 :
2013-12-04 21:53:47 -05:00
# TODO only allow glyphs that are valid?
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-08-09 15:07:04 -04:00
glyphOrder = font . getGlyphOrder ( )
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 :
warnings . warn ( " GSUB/GPOS Coverage is not sorted by glyph ids. " )
ranges = sorted_ranges
del sorted_ranges
2002-09-12 16:47:02 +00:00
for r in ranges :
assert r . StartCoverageIndex == len ( glyphs ) , \
( r . StartCoverageIndex , len ( glyphs ) )
start = r . Start
end = r . End
2013-11-15 15:56:49 -05:00
try :
2013-12-04 21:28:50 -05:00
startID = font . getGlyphID ( start , requireReal = True )
2013-11-15 15:56:49 -05:00
except KeyError :
warnings . warn ( " Coverage table has start glyph ID out of range: %s . " % start )
continue
try :
2013-12-04 21:53:47 -05:00
endID = font . getGlyphID ( end , requireReal = True ) + 1
2013-11-15 15:56:49 -05:00
except KeyError :
2013-12-04 22:51:27 -05:00
# Apparently some tools use 65535 to "match all" the range
if end != ' glyph65535 ' :
warnings . warn ( " Coverage table has end glyph ID out of range: %s . " % end )
# NOTE: We clobber out-of-range things here. There are legit uses for those,
# but none that we have seen in the wild.
2013-11-15 15:56:49 -05:00
endID = len ( glyphOrder )
2013-12-04 21:53:47 -05:00
glyphs . extend ( glyphOrder [ glyphID ] for glyphID in range ( startID , endID ) )
2002-09-12 16:47:02 +00:00
else :
2002-09-12 17:09:13 +00:00
assert 0 , " unknown format: %s " % self . Format
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 }
2006-10-21 14:12:38 +00:00
getGlyphID = font . getGlyphID
2002-09-24 20:50:57 +00:00
if glyphs :
2002-09-12 17:09:13 +00:00
# find out whether Format 2 is more compact or not
2006-10-21 14:12:38 +00:00
glyphIDs = [ getGlyphID ( glyphName ) for glyphName in glyphs ]
2013-11-18 14:11:47 -05:00
brokenOrder = sorted ( glyphIDs ) != glyphIDs
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 )
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 :
warnings . warn ( " 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
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 ( )
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
2006-10-21 14:12:38 +00:00
def doModulo ( value ) :
if value < 0 :
return value + 65536
return value
2002-09-12 19:54:02 +00:00
class SingleSubst ( FormatSwitchingBaseTable ) :
def postRead ( self , rawTable , font ) :
mapping = { }
2004-09-25 10:31:29 +00:00
input = _getGlyphsFromCoverageTable ( rawTable [ " Coverage " ] )
2006-10-21 14:12:38 +00:00
lenMapping = len ( input )
2002-09-12 19:54:02 +00:00
if self . Format == 1 :
delta = rawTable [ " DeltaGlyphID " ]
2006-10-21 14:12:38 +00:00
inputGIDS = [ font . getGlyphID ( name ) for name in input ]
outGIDS = [ glyphID + delta for glyphID in inputGIDS ]
2013-11-27 04:38:16 -05:00
outGIDS = map ( doModulo , outGIDS )
2006-10-21 14:12:38 +00:00
outNames = [ font . getGlyphName ( glyphID ) for glyphID in outGIDS ]
2013-11-27 04:38:16 -05:00
list ( map ( operator . setitem , [ mapping ] * lenMapping , input , outNames ) )
2002-09-12 19:54:02 +00:00
elif self . Format == 2 :
assert len ( input ) == rawTable [ " GlyphCount " ] , \
" invalid SingleSubstFormat2 table "
subst = rawTable [ " Substitute " ]
2013-11-27 04:38:16 -05:00
list ( map ( operator . setitem , [ mapping ] * lenMapping , input , subst ) )
2002-09-12 19:54:02 +00:00
else :
assert 0 , " unknown format: %s " % self . Format
self . mapping = mapping
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
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 :
delta = outID - inID
2013-12-04 22:46:11 -05:00
if delta < - 32768 :
delta + = 65536
elif delta > 32767 :
delta - = 65536
2002-09-12 19:54:02 +00:00
else :
if delta != outID - inID :
break
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
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 ( )
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 " ]
2002-09-12 20:51:09 +00:00
class ClassDef ( FormatSwitchingBaseTable ) :
def postRead ( self , rawTable , font ) :
classDefs = { }
2013-12-04 21:49:00 -05:00
glyphOrder = font . getGlyphOrder ( )
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 " ]
2013-12-04 21:49:00 -05:00
try :
startID = font . getGlyphID ( start , requireReal = True )
except KeyError :
warnings . warn ( " ClassDef table has start glyph ID out of range: %s . " % start )
startID = len ( glyphOrder )
endID = startID + len ( classList )
if endID > len ( glyphOrder ) :
warnings . warn ( " ClassDef table has entries for out of range glyph IDs: %s , %s . " % ( start , len ( classList ) ) )
2013-12-04 22:51:27 -05:00
# NOTE: We clobber out-of-range things here. There are legit uses for those,
# but none that we have seen in the wild.
2013-12-04 21:49:00 -05:00
endID = len ( glyphOrder )
2006-10-21 14:12:38 +00:00
2013-12-04 21:49:00 -05:00
for glyphID , cls in zip ( range ( startID , endID ) , classList ) :
classDefs [ glyphOrder [ glyphID ] ] = 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 :
start = rec . Start
end = rec . End
cls = rec . Class
2013-12-04 21:49:00 -05:00
try :
startID = font . getGlyphID ( start , requireReal = True )
except KeyError :
warnings . warn ( " ClassDef table has start glyph ID out of range: %s . " % start )
continue
try :
endID = font . getGlyphID ( end , requireReal = True )
except KeyError :
2013-12-04 22:51:27 -05:00
# Apparently some tools use 65535 to "match all" the range
if end != ' glyph65535 ' :
warnings . warn ( " ClassDef table has end glyph ID out of range: %s . " % end )
# NOTE: We clobber out-of-range things here. There are legit uses for those,
# but none that we have seen in the wild.
2013-12-04 21:49:00 -05:00
endID = len ( glyphOrder )
for glyphID in range ( startID , endID ) :
classDefs [ glyphOrder [ glyphID ] ] = cls
2002-09-12 20:51:09 +00:00
else :
assert 0 , " unknown format: %s " % self . Format
self . classDefs = classDefs
def preWrite ( self , font ) :
2005-02-11 19:36:38 +00:00
classDefs = getattr ( self , " classDefs " , None )
if classDefs is None :
classDefs = self . classDefs = { }
2013-11-27 06:26:55 -05:00
items = list ( classDefs . items ( ) )
2013-12-02 03:06:44 -05:00
format = 2
rawTable = { " ClassRangeRecord " : [ ] }
2006-10-21 14:12:38 +00:00
getGlyphID = font . getGlyphID
2002-09-12 20:51:09 +00:00
for i in range ( len ( items ) ) :
glyphName , cls = items [ i ]
2006-10-21 14:12:38 +00:00
items [ i ] = getGlyphID ( glyphName ) , glyphName , cls
2002-09-12 20:51:09 +00:00
items . sort ( )
2005-02-11 19:36:38 +00:00
if items :
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 ] )
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
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 ( )
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 ) :
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 " ]
2006-10-21 14:12:38 +00:00
if len ( input ) != len ( alts ) :
assert len ( input ) == len ( alts )
2002-09-12 21:21:58 +00:00
for i in range ( len ( input ) ) :
alternates [ input [ i ] ] = alts [ i ] . Alternate
else :
assert 0 , " unknown format: %s " % self . Format
self . alternates = alternates
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 ]
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.
self . sortCoverageLast = 1
2002-09-12 21:21:58 +00:00
return { " Coverage " : cov , " AlternateSet " : alternates }
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 ( )
for alt in alternates :
xmlWriter . simpletag ( " Alternate " , glyph = alt )
xmlWriter . newline ( )
xmlWriter . endtag ( " AlternateSet " )
xmlWriter . newline ( )
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 ) :
def postRead ( self , rawTable , font ) :
ligatures = { }
if self . Format == 1 :
2006-10-21 14:12:38 +00:00
input = rawTable [ " Coverage " ] . glyphs
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
def preWrite ( self , font ) :
2005-02-11 19:36:38 +00:00
ligatures = getattr ( self , " ligatures " , None )
if ligatures is None :
ligatures = self . ligatures = { }
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.
self . sortCoverageLast = 1
2002-09-12 21:48:03 +00:00
return { " Coverage " : cov , " LigatureSet " : ligSets }
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 ( )
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 " ]
lig . Component = attrs [ " components " ] . split ( " , " )
ligs . append ( lig )
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 ' ,
2003-09-11 07:11:35 +00:00
' LookAheadCoverage ' ) ,
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 ' , ) ,
}
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
an offset from a lookup to a subtable overflowed .
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 .
If the offset from a lookup to subtable overflowed , then we must promote it
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.
while lookup . LookupType == extType :
lookupIndex = lookupIndex - 1
if lookupIndex < 0 :
return ok
lookup = lookups [ lookupIndex ]
for si in range ( len ( lookup . SubTable ) ) :
subTable = lookup . SubTable [ si ]
extSubTableClass = lookupTypes [ overflowRecord . tableType ] [ extType ]
extSubTable = extSubTableClass ( )
extSubTable . Format = 1
extSubTable . ExtensionLookupType = lookup . LookupType
extSubTable . ExtSubTable = subTable
lookup . SubTable [ si ] = extSubTable
lookup . LookupType = extType
ok = 1
return ok
def splitAlternateSubst ( oldSubTable , newSubTable , overflowRecord ) :
ok = 1
newSubTable . Format = oldSubTable . Format
if hasattr ( oldSubTable , ' sortCoverageLast ' ) :
newSubTable . sortCoverageLast = oldSubTable . sortCoverageLast
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 ' :
# We just need to back up by two items
# from the overflowed AlternateSet index to make sure the offset
# to the Coverage table doesn't overflow.
newLen = overflowRecord . itemIndex - 1
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
newSubTable . Format = oldSubTable . Format
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 ' :
# We just need to back up by two items
# from the overflowed AlternateSet index to make sure the offset
# to the Coverage table doesn't overflow.
newLen = overflowRecord . itemIndex - 1
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
splitTable = { ' GSUB ' : {
# 1: splitSingleSubst,
# 2: splitMultipleSubst,
3 : splitAlternateSubst ,
4 : splitLigatureSubst ,
# 5: splitContextSubst,
# 6: splitChainContextSubst,
# 7: splitExtensionSubst,
# 8: splitReverseChainSingleSubst,
} ,
' GPOS ' : {
# 1: splitSinglePos,
# 2: splitPairPos,
# 3: splitCursivePos,
# 4: splitMarkBasePos,
# 5: splitMarkLigPos,
# 6: splitMarkMarkPos,
# 7: splitContextPos,
# 8: splitChainContextPos,
# 9: splitExtensionPos,
}
}
def fixSubTableOverFlows ( ttf , overflowRecord ) :
"""
An offset has overflowed within a sub - table . We need to divide this subtable into smaller parts .
"""
ok = 0
table = ttf [ overflowRecord . tableType ] . table
lookup = table . LookupList . Lookup [ overflowRecord . LookupListIndex ]
subIndex = overflowRecord . SubTableIndex
subtable = lookup . SubTable [ subIndex ]
if hasattr ( subtable , ' ExtSubTable ' ) :
# We split the subtable of the Extension table, and add a new Extension table
# to contain the new subtable.
subTableType = subtable . ExtensionLookupType
extSubTable = subtable
subtable = extSubTable . ExtSubTable
newExtSubTableClass = lookupTypes [ overflowRecord . tableType ] [ lookup . LookupType ]
newExtSubTable = newExtSubTableClass ( )
newExtSubTable . Format = extSubTable . Format
newExtSubTable . ExtensionLookupType = extSubTable . ExtensionLookupType
lookup . SubTable . insert ( subIndex + 1 , newExtSubTable )
newSubTableClass = lookupTypes [ overflowRecord . tableType ] [ subTableType ]
newSubTable = newSubTableClass ( )
newExtSubTable . ExtSubTable = newSubTable
else :
subTableType = lookup . LookupType
newSubTableClass = lookupTypes [ overflowRecord . tableType ] [ subTableType ]
newSubTable = newSubTableClass ( )
lookup . SubTable . insert ( subIndex + 1 , newSubTable )
if hasattr ( lookup , ' SubTableCount ' ) : # may not be defined yet.
lookup . SubTableCount = lookup . SubTableCount + 1
try :
splitFunc = splitTable [ overflowRecord . tableType ] [ subTableType ]
except KeyError :
return ok
ok = splitFunc ( subtable , newSubTable , overflowRecord )
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
2002-05-11 00:59:27 +00:00
formatPat = re . compile ( " ([A-Za-z0-9]+)Format( \ d+)$ " )
namespace = globals ( )
# 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 )
baseClass = FormatSwitchingBaseTable
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 , ) , { } )
2002-05-11 00:59:27 +00:00
namespace [ name ] = cls
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 :
2013-11-27 05:57:39 -05:00
namespace [ alt ] = type ( alt , ( base , ) , { } )
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 ,
} ,
}
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
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
else :
cls = namespace [ name ]
2002-05-11 10:21:36 +00:00
cls . converters , cls . convertersByName = buildConverters ( table , namespace )
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