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
"""
2017-09-04 12:29:55 +02:00
from __future__ import print_function , division , absolute_import , unicode_literals
2013-11-27 17:27:45 -05:00
from fontTools . misc . py23 import *
2016-08-10 01:17:45 -07:00
from fontTools . misc . textTools import safeEval
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
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
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
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
_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
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 = [ ]
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
_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 )
return [ font . getGlyphName ( glyphID )
for glyphID in reader . readUShortArray ( count ) ]
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
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 :
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 :
2017-12-14 19:02:28 -08:00
self . glyphs = [ ]
log . warning ( " Unknown Coverage 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
2016-08-10 01:17:45 -07:00
class VarIdxMap ( BaseTable ) :
2017-02-03 14:33:57 -08:00
def populateDefaults ( self , propagator = None ) :
if not hasattr ( self , ' mapping ' ) :
self . mapping = [ ]
2016-08-10 01:17:45 -07:00
def postRead ( self , rawTable , font ) :
assert ( rawTable [ ' EntryFormat ' ] & 0xFFC0 ) == 0
self . mapping = rawTable [ ' mapping ' ]
def preWrite ( self , font ) :
mapping = getattr ( self , " mapping " , None )
if mapping is None :
mapping = self . mapping = [ ]
rawTable = { ' mapping ' : mapping }
rawTable [ ' MappingCount ' ] = len ( mapping )
2016-09-27 19:01:14 +02:00
# TODO Remove this abstraction/optimization and move it varLib.builder?
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
entryFormat = ( ( entrySize - 1 ) << 4 ) | ( innerBits - 1 )
rawTable [ ' EntryFormat ' ] = entryFormat
return rawTable
def toXML2 ( self , xmlWriter , font ) :
for i , value in enumerate ( getattr ( self , " mapping " , [ ] ) ) :
2016-08-11 01:35:56 -07:00
attrs = (
( ' index ' , i ) ,
( ' 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 :
mapping = [ ]
self . mapping = mapping
2016-08-11 01:35:56 -07:00
outer = safeEval ( attrs [ ' outer ' ] )
inner = safeEval ( attrs [ ' inner ' ] )
assert inner < = 0xFFFF
mapping . append ( ( outer << 16 ) | inner )
2016-08-10 01:17:45 -07: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 " ] )
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 :
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
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 = { }
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 ) :
2016-12-26 17:26:14 -05:00
if cls :
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 ) :
2016-12-26 17:26:14 -05:00
if cls :
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
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
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
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
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
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
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
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 ]
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
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 ,
} ,
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 ,
2017-08-29 12:52:30 +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