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
"""
2014-01-14 15:07:50 +08:00
from __future__ import print_function , division , absolute_import
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
2016-01-24 14:44:42 +00:00
import logging
log = logging . getLogger ( __name__ )
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
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 :
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 :
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 :
2016-01-24 14:44:42 +00:00
log . warning ( " Coverage table has start glyph ID out of range: %s . " , start )
2013-11-15 15:56:49 -05:00
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 ' :
2016-01-24 14:44:42 +00:00
log . warning ( " Coverage table has end glyph ID out of range: %s . " , end )
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-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
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 }
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
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
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 ]
2015-06-12 10:27:04 -07:00
outGIDS = [ ( glyphID + delta ) % 65536 for glyphID in inputGIDS ]
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 :
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 " ]
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
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 :
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 ) :
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
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.
2015-09-10 14:44:07 +02:00
outGlyphs = attrs [ " out " ] . split ( " , " )
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
2002-09-12 20:51:09 +00:00
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 :
2016-01-24 14:44:42 +00:00
log . warning ( " ClassDef table has start glyph ID out of range: %s . " , start )
2013-12-04 21:49:00 -05:00
startID = len ( glyphOrder )
endID = startID + len ( classList )
if endID > len ( glyphOrder ) :
2016-01-24 14:44:42 +00:00
log . warning ( " 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 :
2016-01-24 14:44:42 +00:00
log . warning ( " ClassDef table has start glyph ID out of range: %s . " , start )
2013-12-04 21:49:00 -05:00
continue
try :
2013-12-07 12:44:49 -05:00
endID = font . getGlyphID ( end , requireReal = True ) + 1
2013-12-04 21:49:00 -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 ' :
2016-01-24 14:44:42 +00:00
log . warning ( " ClassDef table has end glyph ID out of range: %s . " , end )
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 )
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
2015-04-26 02:01:01 -04:00
2002-09-12 20:51:09 +00:00
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
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
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
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 ]
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 ( )
2015-09-07 11:21:17 +02:00
for alt in sorted ( 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
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
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 [ ]
2002-09-12 21:48:03 +00:00
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 ' ,
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
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
def splitAlternateSubst ( oldSubTable , newSubTable , overflowRecord ) :
ok = 1
newSubTable . Format = oldSubTable . Format
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
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 ' :
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
if oldSubTable . Format == 2 and oldSubTable . Class1Count > 1 :
for name in ' Class2Count ' , ' ClassDef2 ' , ' ValueFormat1 ' , ' ValueFormat2 ' :
setattr ( newSubTable , name , getattr ( oldSubTable , name ) )
# 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
oldCount = oldSubTable . Class1Count / / 2
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 ]
newSubTable . ClassDef1 . classDefs = { k : ( v - oldCount ) for k , v in classDefs . items ( ) if v > = oldCount }
newSubTable . Class1Record = records [ oldCount : ]
oldSubTable . Class1Count = len ( oldSubTable . Class1Record )
newSubTable . Class1Count = len ( newSubTable . Class1Record )
ok = True
return ok
2006-10-21 14:12:38 +00:00
splitTable = { ' GSUB ' : {
# 1: splitSingleSubst,
# 2: splitMultipleSubst,
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,
# 4: splitMarkBasePos,
# 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 .
"""
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.
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
2014-02-20 16:31:15 -05:00
newExtSubTableClass = lookupTypes [ overflowRecord . tableType ] [ subtable . __class__ . LookupType ]
2006-10-21 14:12:38 +00:00
newExtSubTable = newExtSubTableClass ( )
newExtSubTable . Format = extSubTable . Format
lookup . SubTable . insert ( subIndex + 1 , newExtSubTable )
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 ( )
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
2015-04-26 02:01:01 -04:00
2002-05-11 00:59:27 +00:00
formatPat = re . compile ( " ([A-Za-z0-9]+)Format( \ d+)$ " )
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 )
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 , ) , { } )
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 ,
} ,
}
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