git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@344 4cde692c-a291-49d1-8350-778aa11640f8
201 lines
5.8 KiB
Python
201 lines
5.8 KiB
Python
"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various
|
|
OpenType subtables.
|
|
|
|
Most are constructed upon import from data in otData.py, all are populated with
|
|
converter objects from otConverters.py.
|
|
"""
|
|
|
|
from otBase import BaseTable, FormatSwitchingBaseTable
|
|
|
|
|
|
class LookupOrder(BaseTable):
|
|
"""Dummy class; this table isn't defined, but is used, and is always NULL."""
|
|
|
|
|
|
class FeatureParams(BaseTable):
|
|
"""Dummy class; this table isn't defined, but is used, and is always NULL."""
|
|
# XXX The above is no longer true; the 'size' feature uses FeatureParams now.
|
|
|
|
|
|
class Coverage(FormatSwitchingBaseTable):
|
|
|
|
# manual implementation to get rid of glyphID dependencies
|
|
|
|
def postRead(self, rawTable, font):
|
|
if self.Format == 1:
|
|
self.glyphs = rawTable["GlyphArray"]
|
|
elif self.Format == 2:
|
|
glyphs = self.glyphs = []
|
|
ranges = rawTable["RangeRecord"]
|
|
for r in ranges:
|
|
assert r.StartCoverageIndex == len(glyphs), \
|
|
(r.StartCoverageIndex, len(glyphs))
|
|
start = r.Start
|
|
end = r.End
|
|
startID = font.getGlyphID(start)
|
|
endID = font.getGlyphID(end)
|
|
glyphs.append(start)
|
|
for glyphID in range(startID + 1, endID):
|
|
glyphs.append(font.getGlyphName(glyphID))
|
|
if start != end:
|
|
glyphs.append(end)
|
|
else:
|
|
assert 0, "unknown format: %s" % self.Format
|
|
|
|
def preWrite(self, font):
|
|
format = 1
|
|
rawTable = {"GlyphArray": self.glyphs}
|
|
if self.glyphs:
|
|
# find out whether Format 2 is more compact or not
|
|
glyphIDs = []
|
|
for glyphName in self.glyphs:
|
|
glyphIDs.append(font.getGlyphID(glyphName))
|
|
|
|
last = glyphIDs[0]
|
|
ranges = [[last]]
|
|
for glyphID in glyphIDs[1:]:
|
|
if glyphID == last + 1:
|
|
pass
|
|
else:
|
|
ranges[-1].append(last)
|
|
ranges.append([glyphID])
|
|
last = glyphID
|
|
ranges[-1].append(last)
|
|
|
|
if len(ranges) * 3 < len(self.glyphs): # 3 words vs. 1 word
|
|
# Format 2 is more compact
|
|
index = 0
|
|
for i in range(len(ranges)):
|
|
start, end = ranges[i]
|
|
r = RangeRecord()
|
|
r.Start = font.getGlyphName(start)
|
|
r.End = font.getGlyphName(end)
|
|
r.StartCoverageIndex = index
|
|
ranges[i] = r
|
|
index = index + end - start + 1
|
|
format = 2
|
|
rawTable = {"RangeRecord": ranges}
|
|
#else:
|
|
# fallthrough; Format 1 is more compact
|
|
self.Format = format
|
|
return rawTable
|
|
|
|
def toXML2(self, xmlWriter, font):
|
|
for glyphName in self.glyphs:
|
|
xmlWriter.simpletag("Glyph", value=glyphName)
|
|
xmlWriter.newline()
|
|
|
|
def fromXML(self, (name, attrs, content), font):
|
|
glyphs = getattr(self, "glyphs", None)
|
|
if glyphs is None:
|
|
glyphs = []
|
|
self.glyphs = glyphs
|
|
glyphs.append(attrs["value"])
|
|
|
|
|
|
#
|
|
# 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',
|
|
'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage',
|
|
'LookaheadCoverage'),
|
|
'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef',
|
|
'LookaheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'),
|
|
'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',),
|
|
}
|
|
|
|
|
|
def _buildClasses():
|
|
import new, re
|
|
from otData import otData
|
|
|
|
formatPat = re.compile("([A-Za-z0-9]+)Format(\d+)$")
|
|
namespace = globals()
|
|
|
|
# populate module with classes
|
|
for name, table in otData:
|
|
baseClass = BaseTable
|
|
m = formatPat.match(name)
|
|
if m:
|
|
# XxxFormatN subtable, we only add the "base" table
|
|
name = m.group(1)
|
|
baseClass = FormatSwitchingBaseTable
|
|
if not namespace.has_key(name):
|
|
# the class doesn't exist yet, so the base implementation is used.
|
|
cls = new.classobj(name, (baseClass,), {})
|
|
namespace[name] = cls
|
|
|
|
for base, alts in _equivalents.items():
|
|
base = namespace[base]
|
|
for alt in alts:
|
|
namespace[alt] = new.classobj(alt, (base,), {})
|
|
|
|
global lookupTypes
|
|
lookupTypes = {
|
|
'GSUB': {
|
|
1: SingleSubst,
|
|
2: MultipleSubst,
|
|
3: AlternateSubst,
|
|
4: LigatureSubst,
|
|
5: ContextSubst,
|
|
6: ChainContextSubst,
|
|
7: ExtensionSubst,
|
|
},
|
|
'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
|
|
for lookupEnum in lookupTypes.values():
|
|
for enum, cls in lookupEnum.items():
|
|
cls.LookupType = enum
|
|
|
|
# add converters to classes
|
|
from otConverters import buildConverters
|
|
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 = {}
|
|
converters, convertersByName = buildConverters(table[1:], namespace)
|
|
cls.converters[format] = converters
|
|
cls.convertersByName[format] = convertersByName
|
|
else:
|
|
cls = namespace[name]
|
|
cls.converters, cls.convertersByName = buildConverters(table, namespace)
|
|
|
|
|
|
_buildClasses()
|