2015-10-17 00:47:12 -03:00
|
|
|
#!/usr/bin/python
|
|
|
|
|
2015-11-24 15:01:11 -06:00
|
|
|
# FontDame-to-FontTools for OpenType Layout tables
|
|
|
|
#
|
|
|
|
# Source language spec is available at:
|
|
|
|
# https://rawgit.com/Monotype/OpenType_Table_Source/master/otl_source.html
|
|
|
|
# https://github.com/Monotype/OpenType_Table_Source/
|
2015-10-17 00:47:12 -03:00
|
|
|
|
2015-10-17 02:26:22 -03:00
|
|
|
from __future__ import print_function, division, absolute_import
|
|
|
|
from fontTools import ttLib
|
|
|
|
from fontTools.ttLib.tables import otTables as ot
|
2015-12-08 22:22:33 +01:00
|
|
|
from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
|
2015-10-17 02:26:22 -03:00
|
|
|
import re
|
|
|
|
|
|
|
|
debug = print
|
|
|
|
|
2015-12-08 16:21:59 +01:00
|
|
|
def parseGlyph(s):
|
2015-12-08 16:55:51 +01:00
|
|
|
if s[:2] == 'U ':
|
|
|
|
return ttLib.TTFont._makeGlyphName(int(s[2:], 16))
|
|
|
|
elif s[:2] == '# ':
|
|
|
|
return "glyph%.5d" % int(s[2:])
|
2015-12-08 16:21:59 +01:00
|
|
|
return s
|
|
|
|
|
|
|
|
def parseGlyphs(l):
|
|
|
|
return [parseGlyph(g) for g in l]
|
|
|
|
|
2015-10-17 02:26:22 -03:00
|
|
|
def parseScriptList(lines):
|
2015-10-27 12:44:32 -07:00
|
|
|
lines.skipUntil('script table begin')
|
2015-10-17 02:26:22 -03:00
|
|
|
self = ot.ScriptList()
|
|
|
|
self.ScriptRecord = []
|
2015-10-27 12:44:32 -07:00
|
|
|
for line in lines.readUntil('script table end'):
|
2015-10-17 02:26:22 -03:00
|
|
|
scriptTag, langSysTag, defaultFeature, features = line
|
|
|
|
debug("Adding script", scriptTag, "language-system", langSysTag)
|
|
|
|
|
|
|
|
langSys = ot.LangSys()
|
|
|
|
langSys.LookupOrder = None
|
|
|
|
# TODO The following two lines should use lazy feature name-to-index mapping
|
|
|
|
langSys.ReqFeatureIndex = int(defaultFeature) if defaultFeature else 0xFFFF
|
2015-12-07 21:54:53 +01:00
|
|
|
langSys.FeatureIndex = intSplitComma(features)
|
2015-10-17 02:26:22 -03:00
|
|
|
langSys.FeatureCount = len(langSys.FeatureIndex)
|
|
|
|
|
|
|
|
script = [s for s in self.ScriptRecord if s.ScriptTag == scriptTag]
|
|
|
|
if script:
|
|
|
|
script = script[0].Script
|
|
|
|
else:
|
|
|
|
scriptRec = ot.ScriptRecord()
|
|
|
|
scriptRec.ScriptTag = scriptTag
|
|
|
|
scriptRec.Script = ot.Script()
|
|
|
|
self.ScriptRecord.append(scriptRec)
|
|
|
|
script = scriptRec.Script
|
|
|
|
script.DefaultLangSys = None
|
|
|
|
script.LangSysRecord = []
|
|
|
|
script.LangSysCount = 0
|
|
|
|
|
|
|
|
if langSysTag == 'default':
|
|
|
|
script.DefaultLangSys = langSys
|
|
|
|
else:
|
|
|
|
langSysRec = ot.LangSysRecord()
|
|
|
|
langSysRec.LangSysTag = langSysTag + ' '*(4 - len(langSysTag))
|
|
|
|
langSysRec.LangSys = langSys
|
|
|
|
script.LangSysRecord.append(langSysRec)
|
|
|
|
script.LangSysCount = len(script.LangSysRecord)
|
|
|
|
|
|
|
|
self.ScriptCount = len(self.ScriptRecord)
|
|
|
|
# TODO sort scripts and langSys's?
|
|
|
|
return self
|
|
|
|
|
|
|
|
def parseFeatureList(lines):
|
2015-10-27 12:44:32 -07:00
|
|
|
lines.skipUntil('feature table begin')
|
2015-10-17 02:26:22 -03:00
|
|
|
self = ot.FeatureList()
|
|
|
|
self.FeatureRecord = []
|
2015-10-27 12:44:32 -07:00
|
|
|
for line in lines.readUntil('feature table end'):
|
2015-10-17 02:26:22 -03:00
|
|
|
idx, featureTag, lookups = line
|
2015-10-17 02:55:00 -03:00
|
|
|
assert int(idx) == len(self.FeatureRecord), "%d %d" % (idx, len(self.FeatureRecord))
|
2015-10-17 02:26:22 -03:00
|
|
|
featureRec = ot.FeatureRecord()
|
|
|
|
featureRec.FeatureTag = featureTag
|
|
|
|
featureRec.Feature = ot.Feature()
|
|
|
|
self.FeatureRecord.append(featureRec)
|
|
|
|
feature = featureRec.Feature
|
|
|
|
feature.FeatureParams = None
|
|
|
|
# TODO The following line should use lazy lookup name-to-index mapping
|
2015-12-07 21:54:53 +01:00
|
|
|
feature.LookupListIndex = intSplitComma(lookups)
|
2015-10-17 02:26:22 -03:00
|
|
|
feature.LookupCount = len(feature.LookupListIndex)
|
|
|
|
|
|
|
|
self.FeatureCount = len(self.FeatureRecord)
|
|
|
|
return self
|
|
|
|
|
2015-10-27 14:16:00 -07:00
|
|
|
def parseLookupFlags(lines):
|
|
|
|
flags = 0
|
2015-12-08 19:08:41 +01:00
|
|
|
filterset = None
|
2015-10-27 14:16:00 -07:00
|
|
|
for line in lines:
|
|
|
|
flag = {
|
2015-12-08 19:08:41 +01:00
|
|
|
'righttoleft': 0x0001,
|
|
|
|
'ignorebaseglyphs': 0x0002,
|
|
|
|
'ignoreligatures': 0x0004,
|
|
|
|
'ignoremarks': 0x0008,
|
|
|
|
}.get(line[0].lower())
|
2015-10-27 14:16:00 -07:00
|
|
|
if flag:
|
2015-12-08 19:08:41 +01:00
|
|
|
assert line[1].lower() in ['yes', 'no'], line[1]
|
|
|
|
if line[1].lower() == 'yes':
|
2015-10-27 14:16:00 -07:00
|
|
|
flags |= flag
|
|
|
|
continue
|
2015-12-08 19:08:41 +01:00
|
|
|
if line[0].lower() == 'markattachmenttype':
|
2015-10-27 14:16:00 -07:00
|
|
|
flags |= int(line[1]) << 8
|
|
|
|
continue
|
2015-12-08 19:08:41 +01:00
|
|
|
if line[0].lower() == 'markfiltertype':
|
|
|
|
flags |= 0x10
|
|
|
|
filterset = int(line[1])
|
2015-10-27 14:16:00 -07:00
|
|
|
lines.pack(line)
|
|
|
|
break
|
2015-12-08 19:08:41 +01:00
|
|
|
return flags, filterset
|
2015-12-07 21:54:53 +01:00
|
|
|
|
|
|
|
def parseSingleSubst(self, lines, font):
|
2015-10-27 12:44:32 -07:00
|
|
|
self.mapping = {}
|
|
|
|
for line in lines:
|
|
|
|
assert len(line) == 2, line
|
2015-12-08 16:21:59 +01:00
|
|
|
line = parseGlyphs(line)
|
2015-10-27 12:44:32 -07:00
|
|
|
self.mapping[line[0]] = line[1]
|
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def parseMultiple(self, lines, font):
|
2015-10-27 14:16:00 -07:00
|
|
|
self.mapping = {}
|
|
|
|
for line in lines:
|
2015-12-08 16:21:59 +01:00
|
|
|
line = parseGlyphs(line)
|
2015-10-27 14:16:00 -07:00
|
|
|
self.mapping[line[0]] = line[1:]
|
2015-10-17 03:30:31 -03:00
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def parseAlternate(self, lines, font):
|
2015-10-27 14:16:00 -07:00
|
|
|
self.alternates = {}
|
|
|
|
for line in lines:
|
2015-12-08 16:21:59 +01:00
|
|
|
line = parseGlyphs(line)
|
2015-10-27 14:16:00 -07:00
|
|
|
self.alternates[line[0]] = line[1:]
|
2015-10-17 03:30:31 -03:00
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def parseLigature(self, lines, font):
|
2015-10-27 12:44:32 -07:00
|
|
|
self.ligatures = {}
|
|
|
|
for line in lines:
|
|
|
|
assert len(line) >= 2, line
|
2015-12-08 16:21:59 +01:00
|
|
|
line = parseGlyphs(line)
|
2015-12-07 21:54:53 +01:00
|
|
|
# The following single line can replace the rest of this function with fontTools >= 3.1
|
|
|
|
#self.ligatures[tuple(line[1:])] = line[0]
|
|
|
|
ligGlyph, firstGlyph = line[:2]
|
|
|
|
otherComponents = line[2:]
|
|
|
|
ligature = ot.Ligature()
|
|
|
|
ligature.Component = otherComponents
|
|
|
|
ligature.CompCount = len(ligature.Component) + 1
|
|
|
|
ligature.LigGlyph = ligGlyph
|
|
|
|
self.ligatures.setdefault(firstGlyph, []).append(ligature)
|
|
|
|
|
|
|
|
def parseSinglePos(self, lines, font):
|
2015-12-08 20:47:53 +01:00
|
|
|
values = {}
|
|
|
|
for line in lines:
|
|
|
|
assert len(line) == 3, line
|
|
|
|
w = line[0].title().replace(' ', '')
|
2015-12-08 22:22:33 +01:00
|
|
|
assert w in valueRecordFormatDict
|
2015-12-08 20:47:53 +01:00
|
|
|
g = parseGlyph(line[1])
|
|
|
|
v = int(line[2])
|
|
|
|
if g not in values:
|
|
|
|
values[g] = ValueRecord()
|
|
|
|
assert not hasattr(values[g], w), (g, w)
|
|
|
|
setattr(values[g], w, v)
|
|
|
|
self.Coverage = makeCoverage(values.keys(), font)
|
|
|
|
values = [values[k] for k in self.Coverage.glyphs]
|
|
|
|
self.ValueFormat = reduce(int.__or__, [v.getFormat() for v in values])
|
|
|
|
if all(v == values[0] for v in values):
|
|
|
|
self.Format = 1
|
|
|
|
self.Value = values[0]
|
|
|
|
else:
|
|
|
|
self.Format = 2
|
|
|
|
self.Value = values
|
|
|
|
self.ValueCount = len(self.Value)
|
2015-10-17 03:30:31 -03:00
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def parsePair(self, lines, font):
|
2015-12-08 22:47:37 +01:00
|
|
|
self.ValueFormat1 = self.ValueFormat2 = 0
|
|
|
|
typ = lines.peek()[0].split()[0].lower()
|
|
|
|
if typ in ('left', 'right'):
|
|
|
|
self.Format = 1
|
2015-12-08 23:09:24 +01:00
|
|
|
values = {}
|
2015-12-08 22:47:37 +01:00
|
|
|
for line in lines:
|
2015-12-08 23:09:24 +01:00
|
|
|
assert len(line) == 4, line
|
|
|
|
side = line[0].split()[0].lower()
|
|
|
|
assert side in ('left', 'right'), side
|
|
|
|
what = line[0][len(side):].title().replace(' ', '')
|
|
|
|
mask = valueRecordFormatDict[what][0]
|
|
|
|
glyph1, glyph2 = parseGlyphs(line[1:3])
|
|
|
|
value = int(line[3])
|
|
|
|
if not glyph1 in values: values[glyph1] = {}
|
|
|
|
if not glyph2 in values[glyph1]: values[glyph1][glyph2] = (ValueRecord(),ValueRecord())
|
|
|
|
rec2 = values[glyph1][glyph2]
|
|
|
|
if side == 'left':
|
|
|
|
self.ValueFormat1 |= mask
|
|
|
|
vr = rec2[0]
|
|
|
|
else:
|
|
|
|
self.ValueFormat2 |= mask
|
|
|
|
vr = rec2[1]
|
|
|
|
assert not hasattr(vr, what), (vr, what)
|
|
|
|
setattr(vr, what, value)
|
|
|
|
self.Coverage = makeCoverage(values.keys(), font)
|
|
|
|
self.PairSet = []
|
|
|
|
for glyph1 in self.Coverage.glyphs:
|
|
|
|
values1 = values[glyph1]
|
|
|
|
pairset = ot.PairSet()
|
|
|
|
records = pairset.PairValueRecord = []
|
|
|
|
for glyph2 in sorted(values1.keys(), key=font.getGlyphID):
|
|
|
|
values2 = values1[glyph2]
|
|
|
|
pair = ot.PairValueRecord()
|
|
|
|
pair.SecondGlyph = glyph2
|
|
|
|
pair.Value1,pair.Value2 = values2
|
|
|
|
records.append(pair)
|
|
|
|
pairset.PairValueCount = len(pairset.PairValueRecord)
|
|
|
|
self.PairSet.append(pairset)
|
|
|
|
self.PairSetCount = len(self.PairSet)
|
2015-12-08 22:47:37 +01:00
|
|
|
elif typ.endswith('class'):
|
|
|
|
self.Format = 2
|
|
|
|
classDefs = [None, None]
|
|
|
|
while lines.peek()[0].endswith("class definition begin"):
|
|
|
|
typ = lines.peek()[0][:-len("class definition begin")].lower()
|
|
|
|
idx,klass = {
|
|
|
|
'first': (0,ot.ClassDef1),
|
|
|
|
'second': (1,ot.ClassDef2),
|
|
|
|
}[typ]
|
|
|
|
assert classDefs[idx] is None
|
|
|
|
classDefs[idx] = parseClassDef(lines, klass=klass)
|
|
|
|
self.ClassDef1, self.ClassDef2 = classDefs
|
|
|
|
self.Class1Count, self.Class2Count = (1+max(c.classDefs.values()) for c in classDefs)
|
|
|
|
self.Class1Record = [ot.Class1Record() for i in range(self.Class1Count)]
|
|
|
|
for rec1 in self.Class1Record:
|
|
|
|
rec1.Class2Record = [ot.Class2Record() for j in range(self.Class2Count)]
|
|
|
|
for rec2 in rec1.Class2Record:
|
|
|
|
rec2.Value1 = ValueRecord()
|
|
|
|
rec2.Value2 = ValueRecord()
|
|
|
|
for line in lines:
|
|
|
|
assert len(line) == 4, line
|
2015-12-08 23:09:24 +01:00
|
|
|
side = line[0].split()[0].lower()
|
2015-12-08 22:47:37 +01:00
|
|
|
assert side in ('left', 'right'), side
|
2015-12-08 23:09:24 +01:00
|
|
|
what = line[0][len(side):].title().replace(' ', '')
|
2015-12-08 22:47:37 +01:00
|
|
|
mask = valueRecordFormatDict[what][0]
|
|
|
|
class1, class2, value = (int(x) for x in line[1:4])
|
|
|
|
rec2 = self.Class1Record[class1].Class2Record[class2]
|
|
|
|
if side == 'left':
|
|
|
|
self.ValueFormat1 |= mask
|
|
|
|
vr = rec2.Value1
|
|
|
|
else:
|
|
|
|
self.ValueFormat2 |= mask
|
|
|
|
vr = rec2.Value2
|
|
|
|
assert not hasattr(vr, what), (vr, what)
|
|
|
|
setattr(vr, what, value)
|
|
|
|
self.Coverage = makeCoverage(self.ClassDef1.classDefs.keys(), font)
|
|
|
|
else:
|
|
|
|
assert 0
|
2015-10-17 03:30:31 -03:00
|
|
|
|
2015-12-08 23:26:05 +01:00
|
|
|
def parseKernset(self, lines, font):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2015-12-08 23:40:41 +01:00
|
|
|
def makeAnchor(data, klass=ot.Anchor):
|
|
|
|
assert len(data) <= 2
|
|
|
|
anchor = klass()
|
|
|
|
anchor.Format = 1
|
|
|
|
anchor.XCoordinate,anchor.YCoordinate = intSplitComma(data[0])
|
|
|
|
if len(data) > 1 and data[1] != '':
|
|
|
|
anchor.Format = 2
|
|
|
|
anchor.AnchorPoint = int(data[1])
|
|
|
|
return anchor
|
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def parseCursive(self, lines, font):
|
2015-12-08 20:47:53 +01:00
|
|
|
self.Format = 1
|
|
|
|
self.EntryExitRecord = []
|
2015-12-08 23:25:12 +01:00
|
|
|
records = {}
|
|
|
|
for line in lines:
|
2015-12-08 23:37:15 +01:00
|
|
|
assert len(line) in [3,4], line
|
2015-12-08 23:25:12 +01:00
|
|
|
idx,klass = {
|
|
|
|
'entry': (0,ot.EntryAnchor),
|
|
|
|
'exit': (1,ot.ExitAnchor),
|
|
|
|
}[line[0]]
|
|
|
|
glyph = parseGlyph(line[1])
|
|
|
|
if glyph not in records:
|
|
|
|
records[glyph] = [None,None]
|
|
|
|
assert records[glyph][idx] is None, (glyph, idx)
|
2015-12-08 23:40:41 +01:00
|
|
|
records[glyph][idx] = makeAnchor(line[2:], klass)
|
2015-12-08 23:25:12 +01:00
|
|
|
self.Coverage = makeCoverage(records.keys(), font)
|
|
|
|
recs = self.EntryExitRecord = []
|
|
|
|
for glyph in self.Coverage.glyphs:
|
|
|
|
rec = ot.EntryExitRecord()
|
|
|
|
rec.EntryAnchor,rec.ExitAnchor = records[glyph]
|
|
|
|
recs.append(rec)
|
|
|
|
self.EntryExitCount = len(self.EntryExitRecord)
|
2015-10-17 03:30:31 -03:00
|
|
|
|
2015-12-08 20:47:53 +01:00
|
|
|
def parseMarkToBase(self, lines, font):
|
|
|
|
self.Format = 1
|
|
|
|
self.MarkArray = None
|
|
|
|
self.BaseArray = None
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
def parseMarkToMark(self, lines, font):
|
|
|
|
self.Format = 1
|
|
|
|
self.Mark1Array = None
|
|
|
|
self.Mark2Array = None
|
2015-10-17 03:30:31 -03:00
|
|
|
raise NotImplementedError
|
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def parseMarkToLigature(self, lines, font):
|
2015-12-08 20:47:53 +01:00
|
|
|
self.Format = 1
|
|
|
|
self.MarkArray = []
|
|
|
|
self.LigatureArray = []
|
2015-10-17 03:30:31 -03:00
|
|
|
raise NotImplementedError
|
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def stripSplitComma(line):
|
2015-12-08 18:15:27 +01:00
|
|
|
return [s.strip() for s in line.split(',')] if line else []
|
2015-12-07 21:54:53 +01:00
|
|
|
|
|
|
|
def intSplitComma(line):
|
2015-12-08 18:15:27 +01:00
|
|
|
return [int(i) for i in line.split(',')] if line else []
|
2015-12-07 21:54:53 +01:00
|
|
|
|
|
|
|
# Copied from fontTools.subset
|
|
|
|
class ContextHelper(object):
|
|
|
|
def __init__(self, klassName, Format):
|
|
|
|
if klassName.endswith('Subst'):
|
|
|
|
Typ = 'Sub'
|
|
|
|
Type = 'Subst'
|
|
|
|
else:
|
|
|
|
Typ = 'Pos'
|
|
|
|
Type = 'Pos'
|
|
|
|
if klassName.startswith('Chain'):
|
|
|
|
Chain = 'Chain'
|
2015-12-08 18:15:27 +01:00
|
|
|
InputIdx = 1
|
|
|
|
DataLen = 3
|
2015-12-07 21:54:53 +01:00
|
|
|
else:
|
|
|
|
Chain = ''
|
2015-12-08 18:15:27 +01:00
|
|
|
InputIdx = 0
|
|
|
|
DataLen = 1
|
2015-12-07 21:54:53 +01:00
|
|
|
ChainTyp = Chain+Typ
|
|
|
|
|
|
|
|
self.Typ = Typ
|
|
|
|
self.Type = Type
|
|
|
|
self.Chain = Chain
|
|
|
|
self.ChainTyp = ChainTyp
|
2015-12-08 18:15:27 +01:00
|
|
|
self.InputIdx = InputIdx
|
|
|
|
self.DataLen = DataLen
|
2015-12-07 21:54:53 +01:00
|
|
|
|
|
|
|
self.LookupRecord = Type+'LookupRecord'
|
|
|
|
|
|
|
|
if Format == 1:
|
|
|
|
Coverage = lambda r: r.Coverage
|
|
|
|
ChainCoverage = lambda r: r.Coverage
|
|
|
|
ContextData = lambda r:(None,)
|
|
|
|
ChainContextData = lambda r:(None, None, None)
|
2015-12-08 18:30:07 +01:00
|
|
|
SetContextData = None
|
|
|
|
SetChainContextData = None
|
2015-12-07 21:54:53 +01:00
|
|
|
RuleData = lambda r:(r.Input,)
|
|
|
|
ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
|
2015-12-08 18:15:27 +01:00
|
|
|
def SetRuleData(r, d):
|
|
|
|
(r.Input,) = d
|
2015-12-08 18:36:32 +01:00
|
|
|
(r.GlyphCount,) = (len(x)+1 for x in d)
|
2015-12-08 18:15:27 +01:00
|
|
|
def ChainSetRuleData(r, d):
|
|
|
|
(r.Backtrack, r.Input, r.LookAhead) = d
|
2015-12-08 18:36:32 +01:00
|
|
|
(r.BacktrackGlyphCount,r.InputGlyphCount,r.LookAheadGlyphCount,) = (len(d[0]),len(d[1])+1,len(d[2]))
|
2015-12-07 21:54:53 +01:00
|
|
|
elif Format == 2:
|
|
|
|
Coverage = lambda r: r.Coverage
|
|
|
|
ChainCoverage = lambda r: r.Coverage
|
|
|
|
ContextData = lambda r:(r.ClassDef,)
|
|
|
|
ChainContextData = lambda r:(r.BacktrackClassDef,
|
|
|
|
r.InputClassDef,
|
|
|
|
r.LookAheadClassDef)
|
2015-12-08 18:30:07 +01:00
|
|
|
def SetContextData(r, d):
|
|
|
|
(r.ClassDef,) = d
|
|
|
|
def SetChainContextData(r, d):
|
|
|
|
(r.BacktrackClassDef,
|
|
|
|
r.InputClassDef,
|
|
|
|
r.LookAheadClassDef) = d
|
2015-12-07 21:54:53 +01:00
|
|
|
RuleData = lambda r:(r.Class,)
|
|
|
|
ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
|
2015-12-08 18:15:27 +01:00
|
|
|
def SetRuleData(r, d):
|
|
|
|
(r.Class,) = d
|
2015-12-08 18:36:32 +01:00
|
|
|
(r.GlyphCount,) = (len(x)+1 for x in d)
|
2015-12-08 18:15:27 +01:00
|
|
|
def ChainSetRuleData(r, d):
|
|
|
|
(r.Backtrack, r.Input, r.LookAhead) = d
|
2015-12-08 18:36:32 +01:00
|
|
|
(r.BacktrackGlyphCount,r.InputGlyphCount,r.LookAheadGlyphCount,) = (len(d[0]),len(d[1])+1,len(d[2]))
|
2015-12-07 21:54:53 +01:00
|
|
|
elif Format == 3:
|
|
|
|
Coverage = lambda r: r.Coverage[0]
|
|
|
|
ChainCoverage = lambda r: r.InputCoverage[0]
|
|
|
|
ContextData = None
|
|
|
|
ChainContextData = None
|
2015-12-08 18:30:07 +01:00
|
|
|
SetContextData = None
|
|
|
|
SetChainContextData = None
|
2015-12-07 21:54:53 +01:00
|
|
|
RuleData = lambda r: r.Coverage
|
|
|
|
ChainRuleData = lambda r:(r.BacktrackCoverage +
|
|
|
|
r.InputCoverage +
|
|
|
|
r.LookAheadCoverage)
|
2015-12-08 18:15:27 +01:00
|
|
|
def SetRuleData(r, d):
|
|
|
|
(r.Coverage,) = d
|
|
|
|
(r.GlyphCount,) = (len(x) for x in d)
|
|
|
|
def ChainSetRuleData(r, d):
|
|
|
|
(r.BacktrackCoverage, r.InputCoverage, r.LookAheadCoverage) = d
|
|
|
|
(r.BacktrackGlyphCount,r.InputGlyphCount,r.LookAheadGlyphCount,) = (len(x) for x in d)
|
2015-12-07 21:54:53 +01:00
|
|
|
else:
|
|
|
|
assert 0, "unknown format: %s" % Format
|
|
|
|
|
|
|
|
if Chain:
|
|
|
|
self.Coverage = ChainCoverage
|
|
|
|
self.ContextData = ChainContextData
|
2015-12-08 18:30:07 +01:00
|
|
|
self.SetContextData = SetChainContextData
|
2015-12-07 21:54:53 +01:00
|
|
|
self.RuleData = ChainRuleData
|
|
|
|
self.SetRuleData = ChainSetRuleData
|
|
|
|
else:
|
|
|
|
self.Coverage = Coverage
|
|
|
|
self.ContextData = ContextData
|
2015-12-08 18:30:07 +01:00
|
|
|
self.SetContextData = SetContextData
|
2015-12-07 21:54:53 +01:00
|
|
|
self.RuleData = RuleData
|
|
|
|
self.SetRuleData = SetRuleData
|
|
|
|
|
|
|
|
if Format == 1:
|
|
|
|
self.Rule = ChainTyp+'Rule'
|
|
|
|
self.RuleCount = ChainTyp+'RuleCount'
|
|
|
|
self.RuleSet = ChainTyp+'RuleSet'
|
|
|
|
self.RuleSetCount = ChainTyp+'RuleSetCount'
|
|
|
|
self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
|
|
|
|
elif Format == 2:
|
|
|
|
self.Rule = ChainTyp+'ClassRule'
|
|
|
|
self.RuleCount = ChainTyp+'ClassRuleCount'
|
|
|
|
self.RuleSet = ChainTyp+'ClassSet'
|
|
|
|
self.RuleSetCount = ChainTyp+'ClassSetCount'
|
|
|
|
self.Intersect = lambda glyphs, c, r: (c.intersect_class(glyphs, r) if c
|
|
|
|
else (set(glyphs) if r == 0 else set()))
|
|
|
|
|
|
|
|
self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
|
|
|
|
self.ClassDefIndex = 1 if Chain else 0
|
|
|
|
self.Input = 'Input' if Chain else 'Class'
|
|
|
|
|
|
|
|
def parseLookupRecords(items, klassName):
|
|
|
|
klass = getattr(ot, klassName)
|
|
|
|
lst = []
|
|
|
|
for item in items:
|
|
|
|
rec = klass()
|
|
|
|
item = intSplitComma(item)
|
|
|
|
assert len(item) == 2, item
|
|
|
|
assert item[0] > 0, item[0]
|
|
|
|
rec.SequenceIndex = item[0] - 1
|
2015-12-08 11:30:32 +01:00
|
|
|
# TODO The following line should use lazy lookup name-to-index mapping
|
2015-12-07 21:54:53 +01:00
|
|
|
rec.LookupListIndex = item[1]
|
|
|
|
lst.append(rec)
|
|
|
|
return lst
|
|
|
|
|
2015-12-08 19:08:41 +01:00
|
|
|
def parseClassDef(lines, klass=ot.ClassDef):
|
|
|
|
line = next(lines)
|
|
|
|
assert line[0].lower().endswith('class definition begin'), line
|
|
|
|
self = klass()
|
|
|
|
classDefs = self.classDefs = {}
|
|
|
|
for line in lines.readUntil('class definition end'):
|
|
|
|
classDefs[parseGlyph(line[0])] = int(line[1])
|
|
|
|
return self
|
|
|
|
|
2015-12-08 16:09:29 +01:00
|
|
|
def makeCoverage(glyphs, fonts, klass=ot.Coverage):
|
|
|
|
coverage = klass()
|
2015-12-07 21:54:53 +01:00
|
|
|
coverage.glyphs = sorted(set(glyphs), key=font.getGlyphID)
|
|
|
|
return coverage
|
|
|
|
|
2015-12-08 16:09:29 +01:00
|
|
|
def parseCoverage(lines, font, klass=ot.Coverage):
|
|
|
|
line = next(lines)
|
2015-12-08 19:08:41 +01:00
|
|
|
assert line[0].lower().endswith('coverage definition begin'), line
|
2015-12-08 16:09:29 +01:00
|
|
|
glyphs = []
|
|
|
|
for line in lines.readUntil('coverage definition end'):
|
2015-12-08 16:21:59 +01:00
|
|
|
glyphs.append(parseGlyph(line[0]))
|
2015-12-08 16:09:29 +01:00
|
|
|
return makeCoverage(glyphs, font, klass)
|
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def bucketizeRules(self, c, rules, bucketKeys):
|
|
|
|
buckets = {}
|
|
|
|
for seq,recs in rules:
|
2015-12-08 18:15:27 +01:00
|
|
|
buckets.setdefault(seq[c.InputIdx][0], []).append((tuple(s[1 if i==c.InputIdx else 0:] for i,s in enumerate(seq)), recs))
|
2015-12-07 21:54:53 +01:00
|
|
|
|
|
|
|
rulesets = []
|
|
|
|
for firstGlyph in bucketKeys:
|
|
|
|
if firstGlyph not in buckets:
|
|
|
|
rulesets.append(None)
|
|
|
|
continue
|
|
|
|
thisRules = []
|
|
|
|
for seq,recs in buckets[firstGlyph]:
|
|
|
|
rule = getattr(ot, c.Rule)()
|
2015-12-08 18:15:27 +01:00
|
|
|
c.SetRuleData(rule, seq)
|
2015-12-07 21:54:53 +01:00
|
|
|
setattr(rule, c.Type+'Count', len(recs))
|
|
|
|
setattr(rule, c.LookupRecord, recs)
|
|
|
|
thisRules.append(rule)
|
|
|
|
|
|
|
|
ruleset = getattr(ot, c.RuleSet)()
|
|
|
|
setattr(ruleset, c.Rule, thisRules)
|
|
|
|
setattr(ruleset, c.RuleCount, len(thisRules))
|
|
|
|
rulesets.append(ruleset)
|
|
|
|
|
|
|
|
setattr(self, c.RuleSet, rulesets)
|
|
|
|
setattr(self, c.RuleSetCount, len(rulesets))
|
|
|
|
|
|
|
|
def parseContext(self, lines, font, Type):
|
2015-12-08 19:08:41 +01:00
|
|
|
typ = lines.peek()[0].split()[0].lower()
|
2015-10-27 12:44:32 -07:00
|
|
|
if typ == 'glyph':
|
2015-10-27 16:07:11 -07:00
|
|
|
self.Format = 1
|
2015-12-08 18:30:07 +01:00
|
|
|
debug("Parsing %s format %s" % (Type, self.Format))
|
2015-12-08 18:15:27 +01:00
|
|
|
c = ContextHelper(Type, self.Format)
|
2015-12-07 21:54:53 +01:00
|
|
|
rules = []
|
|
|
|
for line in lines:
|
2015-12-08 19:08:41 +01:00
|
|
|
assert line[0].lower() == 'glyph', line[0]
|
2015-12-08 18:15:27 +01:00
|
|
|
seq = tuple(parseGlyphs(stripSplitComma(i)) for i in line[1:1+c.DataLen])
|
|
|
|
recs = parseLookupRecords(line[1+c.DataLen:], c.LookupRecord)
|
2015-12-07 21:54:53 +01:00
|
|
|
rules.append((seq, recs))
|
|
|
|
|
2015-12-08 18:15:27 +01:00
|
|
|
firstGlyphs = set(seq[c.InputIdx][0] for seq,recs in rules)
|
|
|
|
self.Coverage = makeCoverage(firstGlyphs, font)
|
2015-12-07 21:54:53 +01:00
|
|
|
bucketizeRules(self, c, rules, self.Coverage.glyphs)
|
2015-12-08 18:15:27 +01:00
|
|
|
elif typ.endswith('class'):
|
2015-10-27 16:07:11 -07:00
|
|
|
self.Format = 2
|
2015-12-08 18:30:07 +01:00
|
|
|
debug("Parsing %s format %s" % (Type, self.Format))
|
2015-12-08 18:15:27 +01:00
|
|
|
c = ContextHelper(Type, self.Format)
|
2015-12-08 18:30:07 +01:00
|
|
|
classDefs = [None] * c.DataLen
|
2015-12-08 18:15:27 +01:00
|
|
|
while lines.peek()[0].endswith("class definition begin"):
|
2015-12-08 19:08:41 +01:00
|
|
|
typ = lines.peek()[0][:-len("class definition begin")].lower()
|
2015-12-08 18:30:07 +01:00
|
|
|
idx,klass = {
|
|
|
|
1: {
|
|
|
|
'': (0,ot.ClassDef),
|
|
|
|
},
|
|
|
|
3: {
|
|
|
|
'backtrack': (0,ot.BacktrackClassDef),
|
|
|
|
'': (1,ot.InputClassDef),
|
|
|
|
'lookahead': (2,ot.LookAheadClassDef),
|
|
|
|
},
|
|
|
|
}[c.DataLen][typ]
|
|
|
|
assert classDefs[idx] is None, idx
|
|
|
|
classDefs[idx] = parseClassDef(lines, klass=klass)
|
|
|
|
c.SetContextData(self, classDefs)
|
2015-12-07 21:54:53 +01:00
|
|
|
rules = []
|
|
|
|
for line in lines:
|
2015-12-08 19:08:41 +01:00
|
|
|
assert line[0].lower().startswith('class'), line[0]
|
2015-12-08 18:15:27 +01:00
|
|
|
seq = tuple(intSplitComma(i) for i in line[1:1+c.DataLen])
|
|
|
|
recs = parseLookupRecords(line[1+c.DataLen:], c.LookupRecord)
|
2015-12-07 21:54:53 +01:00
|
|
|
rules.append((seq, recs))
|
2015-12-08 18:15:27 +01:00
|
|
|
firstClasses = set(seq[c.InputIdx][0] for seq,recs in rules)
|
2015-12-08 18:30:07 +01:00
|
|
|
firstGlyphs = set(g for g,c in classDefs[c.InputIdx].classDefs.items() if c in firstClasses)
|
2015-12-08 18:15:27 +01:00
|
|
|
self.Coverage = makeCoverage(firstGlyphs, font)
|
|
|
|
bucketizeRules(self, c, rules, range(max(firstClasses) + 1))
|
|
|
|
elif typ.endswith('coverage'):
|
2015-12-08 16:09:29 +01:00
|
|
|
self.Format = 3
|
2015-12-08 18:30:07 +01:00
|
|
|
debug("Parsing %s format %s" % (Type, self.Format))
|
2015-12-08 18:15:27 +01:00
|
|
|
c = ContextHelper(Type, self.Format)
|
2015-12-08 19:08:41 +01:00
|
|
|
coverages = tuple([] for i in range(c.DataLen))
|
2015-12-08 16:09:29 +01:00
|
|
|
while lines.peek()[0].endswith("coverage definition begin"):
|
2015-12-08 19:08:41 +01:00
|
|
|
typ = lines.peek()[0][:-len("coverage definition begin")].lower()
|
|
|
|
idx,klass = {
|
|
|
|
1: {
|
|
|
|
'': (0,ot.Coverage),
|
|
|
|
},
|
|
|
|
3: {
|
|
|
|
'backtrack': (0,ot.BacktrackCoverage),
|
|
|
|
'input': (1,ot.InputCoverage),
|
|
|
|
'lookahead': (2,ot.LookAheadCoverage),
|
|
|
|
},
|
|
|
|
}[c.DataLen][typ]
|
|
|
|
coverages[idx].append(parseCoverage(lines, font, klass=klass))
|
|
|
|
c.SetRuleData(self, coverages)
|
2015-12-08 16:09:29 +01:00
|
|
|
lines = list(lines)
|
|
|
|
assert len(lines) == 1
|
|
|
|
line = lines[0]
|
2015-12-08 19:08:41 +01:00
|
|
|
assert line[0].lower() == 'coverage', line[0]
|
2015-12-08 16:09:29 +01:00
|
|
|
recs = parseLookupRecords(line[1:], c.LookupRecord)
|
|
|
|
setattr(self, c.Type+'Count', len(recs))
|
|
|
|
setattr(self, c.LookupRecord, recs)
|
|
|
|
else:
|
2015-12-08 18:15:27 +01:00
|
|
|
assert 0, typ
|
2015-10-17 03:30:31 -03:00
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def parseContextSubst(self, lines, font):
|
2015-12-08 18:15:27 +01:00
|
|
|
return parseContext(self, lines, font, "ContextSubst")
|
2015-12-07 21:54:53 +01:00
|
|
|
def parseContextPos(self, lines, font):
|
2015-12-08 18:15:27 +01:00
|
|
|
return parseContext(self, lines, font, "ContextPos")
|
2015-12-07 21:54:53 +01:00
|
|
|
def parseChainedSubst(self, lines, font):
|
2015-12-08 18:15:27 +01:00
|
|
|
return parseContext(self, lines, font, "ChainContextSubst")
|
2015-12-08 20:00:24 +01:00
|
|
|
def parseChainedPos(self, lines, font):
|
2015-12-08 18:15:27 +01:00
|
|
|
return parseContext(self, lines, font, "ChainContextPos")
|
2015-12-07 21:54:53 +01:00
|
|
|
|
2015-12-08 19:28:05 +01:00
|
|
|
def parseReverseChainedSubst(self, lines, font):
|
|
|
|
self.Format = 1
|
|
|
|
coverages = ([], [])
|
|
|
|
while lines.peek()[0].endswith("coverage definition begin"):
|
|
|
|
typ = lines.peek()[0][:-len("coverage definition begin")].lower()
|
|
|
|
idx,klass = {
|
|
|
|
'backtrack': (0,ot.BacktrackCoverage),
|
|
|
|
'lookahead': (1,ot.LookAheadCoverage),
|
|
|
|
}[typ]
|
|
|
|
coverages[idx].append(parseCoverage(lines, font, klass=klass))
|
|
|
|
self.BacktrackCoverage = coverages[0]
|
|
|
|
self.BacktrackGlyphCount = len(self.BacktrackCoverage)
|
|
|
|
self.LookAheadCoverage = coverages[1]
|
|
|
|
self.LookAheadGlyphCount = len(self.LookAheadCoverage)
|
|
|
|
mapping = {}
|
|
|
|
for line in lines:
|
|
|
|
assert len(line) == 2, line
|
|
|
|
line = parseGlyphs(line)
|
|
|
|
mapping[line[0]] = line[1]
|
|
|
|
self.Coverage = makeCoverage(mapping.keys(), font)
|
|
|
|
self.Substitute = [mapping[k] for k in self.Coverage.glyphs]
|
|
|
|
self.GlyphCount = len(self.Substitute)
|
|
|
|
|
2015-12-08 14:37:33 +01:00
|
|
|
def parseLookup(lines, tableTag, font):
|
|
|
|
line = lines.skipUntil('lookup')
|
|
|
|
if line is None: return None, None
|
|
|
|
lookupLines = lines.readUntil('lookup end')
|
|
|
|
_, name, typ = line
|
2015-12-08 18:17:37 +01:00
|
|
|
debug("Parsing lookup type %s %s" % (typ, name))
|
2015-12-08 14:37:33 +01:00
|
|
|
|
|
|
|
lookup = ot.Lookup()
|
2015-12-08 19:08:41 +01:00
|
|
|
lookup.LookupFlag,filterset = parseLookupFlags(lookupLines)
|
|
|
|
if filterset is not None:
|
|
|
|
lookup.MarkFilteringSet = filterset
|
2015-12-08 14:37:33 +01:00
|
|
|
lookup.LookupType, parseLookupSubTable = {
|
|
|
|
'GSUB': {
|
|
|
|
'single': (1, parseSingleSubst),
|
|
|
|
'multiple': (2, parseMultiple),
|
|
|
|
'alternate': (3, parseAlternate),
|
|
|
|
'ligature': (4, parseLigature),
|
|
|
|
'context': (5, parseContextSubst),
|
|
|
|
'chained': (6, parseChainedSubst),
|
2015-12-08 19:28:05 +01:00
|
|
|
'reversechained':(8, parseReverseChainedSubst),
|
2015-12-08 14:37:33 +01:00
|
|
|
},
|
|
|
|
'GPOS': {
|
|
|
|
'single': (1, parseSinglePos),
|
|
|
|
'pair': (2, parsePair),
|
2015-12-08 23:26:05 +01:00
|
|
|
'kernset': (2, parseKernset),
|
2015-12-08 14:37:33 +01:00
|
|
|
'cursive': (3, parseCursive),
|
2015-12-08 20:47:53 +01:00
|
|
|
'mark to base': (4, parseMarkToBase),
|
2015-12-08 14:37:33 +01:00
|
|
|
'mark to ligature':(5, parseMarkToLigature),
|
2015-12-08 20:47:53 +01:00
|
|
|
'mark to mark': (6, parseMarkToMark),
|
2015-12-08 14:37:33 +01:00
|
|
|
'context': (7, parseContextPos),
|
|
|
|
'chained': (8, parseChainedPos),
|
|
|
|
},
|
|
|
|
}[tableTag][typ]
|
|
|
|
|
2015-12-08 19:38:21 +01:00
|
|
|
subtables = []
|
2015-12-08 14:37:33 +01:00
|
|
|
|
2015-12-08 19:38:21 +01:00
|
|
|
while True:
|
|
|
|
subLookupLines = lookupLines.readUntil(('% subtable', 'subtable end'))
|
|
|
|
if subLookupLines.peek() is None:
|
|
|
|
break
|
|
|
|
subtable = ot.lookupTypes[tableTag][lookup.LookupType]()
|
|
|
|
subtable.LookupType = lookup.LookupType
|
2015-12-08 20:47:53 +01:00
|
|
|
try:
|
|
|
|
parseLookupSubTable(subtable, subLookupLines, font)
|
|
|
|
except NotImplementedError:
|
|
|
|
list(subLookupLines) # Exhaust subLookupLines
|
|
|
|
continue
|
|
|
|
assert subLookupLines.peek() is None
|
2015-12-08 19:38:21 +01:00
|
|
|
subtables.append(subtable)
|
|
|
|
|
|
|
|
lookup.SubTable = subtables
|
2015-12-08 14:37:33 +01:00
|
|
|
lookup.SubTableCount = len(lookup.SubTable)
|
2015-12-08 20:47:53 +01:00
|
|
|
if lookup.SubTableCount is 0:
|
|
|
|
return None, name
|
2015-12-08 14:37:33 +01:00
|
|
|
return lookup, name
|
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def parseLookupList(lines, tableTag, font):
|
2015-12-08 18:17:37 +01:00
|
|
|
debug("Parsing lookup list")
|
2015-10-17 02:55:00 -03:00
|
|
|
self = ot.LookupList()
|
|
|
|
self.Lookup = []
|
|
|
|
while True:
|
2015-12-08 14:37:33 +01:00
|
|
|
lookup, name = parseLookup(lines, tableTag, font)
|
2015-12-08 20:47:53 +01:00
|
|
|
if name is None: break
|
2015-12-08 14:37:33 +01:00
|
|
|
assert int(name) == len(self.Lookup), "%d %d" % (name, len(self.Lookup))
|
2015-10-17 02:55:00 -03:00
|
|
|
self.Lookup.append(lookup)
|
|
|
|
self.LookupCount = len(self.Lookup)
|
|
|
|
return self
|
2015-10-17 02:26:22 -03:00
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def parseGSUB(lines, font):
|
2015-10-17 02:26:22 -03:00
|
|
|
debug("Parsing GSUB")
|
|
|
|
self = ot.GSUB()
|
2015-10-27 16:07:11 -07:00
|
|
|
self.Version = 1.0
|
2015-10-17 02:26:22 -03:00
|
|
|
self.ScriptList = parseScriptList(lines)
|
|
|
|
self.FeatureList = parseFeatureList(lines)
|
2015-12-07 21:54:53 +01:00
|
|
|
self.LookupList = parseLookupList(lines, 'GSUB', font)
|
2015-10-17 02:26:22 -03:00
|
|
|
return self
|
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def parseGPOS(lines, font):
|
2015-10-17 02:26:22 -03:00
|
|
|
debug("Parsing GPOS")
|
|
|
|
self = ot.GPOS()
|
2015-10-27 16:07:11 -07:00
|
|
|
self.Version = 1.0
|
2015-10-17 03:30:31 -03:00
|
|
|
# TODO parse EM?
|
2015-10-17 02:26:22 -03:00
|
|
|
self.ScriptList = parseScriptList(lines)
|
|
|
|
self.FeatureList = parseFeatureList(lines)
|
2015-12-07 21:54:53 +01:00
|
|
|
self.LookupList = parseLookupList(lines, 'GPOS', font)
|
2015-10-17 02:26:22 -03:00
|
|
|
return self
|
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
def parseGDEF(lines, font):
|
2015-10-17 02:26:22 -03:00
|
|
|
debug("Parsing GDEF TODO")
|
|
|
|
return None
|
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
|
|
|
|
class ReadUntilMixin(object):
|
|
|
|
|
|
|
|
def _readUntil(self, what):
|
2015-12-08 19:38:21 +01:00
|
|
|
if type(what) is not tuple:
|
|
|
|
what = (what,)
|
2015-12-07 21:54:53 +01:00
|
|
|
for line in self:
|
2015-12-08 19:38:21 +01:00
|
|
|
if line[0].lower() in what:
|
2015-12-07 21:54:53 +01:00
|
|
|
raise StopIteration
|
|
|
|
yield line
|
|
|
|
def readUntil(self, what):
|
|
|
|
return BufferedIter(self._readUntil(what))
|
|
|
|
|
|
|
|
class BufferedIter(ReadUntilMixin):
|
2015-10-27 14:16:00 -07:00
|
|
|
|
|
|
|
def __init__(self, it):
|
|
|
|
self.iter = it
|
|
|
|
self.buffer = []
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
return self
|
|
|
|
|
|
|
|
def next(self):
|
|
|
|
if self.buffer:
|
|
|
|
return self.buffer.pop(0)
|
|
|
|
else:
|
|
|
|
return self.iter.next()
|
|
|
|
|
|
|
|
def peek(self, n=0):
|
|
|
|
"""Return an item n entries ahead in the iteration."""
|
|
|
|
while n >= len(self.buffer):
|
|
|
|
try:
|
|
|
|
self.buffer.append(self.iter.next())
|
|
|
|
except StopIteration:
|
|
|
|
return None
|
|
|
|
return self.buffer[n]
|
|
|
|
|
|
|
|
def pack(self, item):
|
|
|
|
"""Push back item into the iterator."""
|
|
|
|
self.buffer.insert(0, item)
|
|
|
|
|
2015-12-07 21:54:53 +01:00
|
|
|
class Tokenizer(ReadUntilMixin):
|
2015-10-27 12:44:32 -07:00
|
|
|
|
|
|
|
def __init__(self, f):
|
|
|
|
# TODO BytesIO / StringIO as needed? also, figure out whether we work on bytes or unicode
|
|
|
|
|
|
|
|
lines = iter(f)
|
|
|
|
lines = ([s.strip() for s in line.split('\t')] for line in lines)
|
|
|
|
try:
|
|
|
|
self.filename = f.name
|
|
|
|
except:
|
|
|
|
self.filename = None
|
|
|
|
self._lines = lines
|
|
|
|
self._lineno = 0
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
return self
|
|
|
|
|
|
|
|
def _next(self):
|
|
|
|
self._lineno += 1
|
|
|
|
return next(self._lines)
|
|
|
|
|
|
|
|
def next(self):
|
|
|
|
while True:
|
|
|
|
line = self._next()
|
|
|
|
# Skip comments and empty lines
|
2015-12-08 19:38:21 +01:00
|
|
|
if line[0] and (line[0][0] != '%' or line[0] == '% subtable'):
|
2015-10-27 12:44:32 -07:00
|
|
|
return line
|
|
|
|
|
|
|
|
def skipUntil(self, what):
|
|
|
|
for line in self:
|
2015-12-08 19:08:41 +01:00
|
|
|
if line[0].lower() == what:
|
2015-10-27 12:44:32 -07:00
|
|
|
return line
|
|
|
|
|
2015-12-08 14:56:23 +01:00
|
|
|
def parseTable(lines, font):
|
|
|
|
debug("Parsing table")
|
2015-10-17 02:26:22 -03:00
|
|
|
line = next(lines)
|
2015-10-17 02:55:00 -03:00
|
|
|
assert line[0][:9] == 'FontDame ', line
|
|
|
|
assert line[0][13:] == ' table', line
|
2015-10-17 02:26:22 -03:00
|
|
|
tableTag = line[0][9:13]
|
|
|
|
container = ttLib.getTableClass(tableTag)()
|
|
|
|
table = {'GSUB': parseGSUB,
|
|
|
|
'GPOS': parseGPOS,
|
|
|
|
'GDEF': parseGDEF,
|
2015-12-07 21:54:53 +01:00
|
|
|
}[tableTag](lines, font)
|
2015-10-17 02:26:22 -03:00
|
|
|
container.table = table
|
|
|
|
return container
|
|
|
|
|
2015-12-08 14:56:23 +01:00
|
|
|
def build(f, font):
|
|
|
|
lines = Tokenizer(f)
|
|
|
|
return parseTable(lines, font)
|
|
|
|
|
2015-10-27 16:07:11 -07:00
|
|
|
|
|
|
|
class MockFont(object):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self._glyphOrder = ['.notdef']
|
|
|
|
self._reverseGlyphOrder = {'.notdef': 0}
|
2015-12-07 12:12:11 +01:00
|
|
|
self.lazy = False
|
2015-10-27 16:07:11 -07:00
|
|
|
|
2015-12-07 12:12:11 +01:00
|
|
|
def getGlyphID(self, glyph, requireReal=None):
|
2015-10-27 16:07:11 -07:00
|
|
|
gid = self._reverseGlyphOrder.get(glyph, None)
|
|
|
|
if gid is None:
|
|
|
|
gid = len(self._glyphOrder)
|
|
|
|
self._glyphOrder.append(glyph)
|
|
|
|
self._reverseGlyphOrder[glyph] = gid
|
|
|
|
return gid
|
|
|
|
|
|
|
|
def getGlyphName(self, gid):
|
|
|
|
return self._glyphOrder[gid]
|
|
|
|
|
2015-12-07 12:12:11 +01:00
|
|
|
def getGlyphOrder(self):
|
|
|
|
return self._glyphOrder
|
|
|
|
|
2015-10-17 02:26:22 -03:00
|
|
|
if __name__ == '__main__':
|
|
|
|
import sys
|
2015-10-27 16:07:11 -07:00
|
|
|
font = MockFont()
|
2015-10-17 02:26:22 -03:00
|
|
|
for f in sys.argv[1:]:
|
|
|
|
debug("Processing", f)
|
2015-12-07 21:54:53 +01:00
|
|
|
table = build(open(f, 'rt'), font)
|
2015-10-27 16:07:11 -07:00
|
|
|
blob = table.compile(font)
|
2015-12-07 12:12:11 +01:00
|
|
|
decompiled = table.__class__()
|
|
|
|
decompiled.decompile(blob, font)
|
|
|
|
|
2015-12-08 23:26:05 +01:00
|
|
|
continue
|
2015-12-07 12:12:11 +01:00
|
|
|
from fontTools.misc import xmlWriter
|
|
|
|
tag = table.tableTag
|
|
|
|
writer = xmlWriter.XMLWriter(sys.stdout)
|
|
|
|
writer.begintag(tag)
|
|
|
|
writer.newline()
|
2015-12-07 21:54:53 +01:00
|
|
|
#table.toXML(writer, font)
|
2015-12-07 12:12:11 +01:00
|
|
|
decompiled.toXML(writer, font)
|
|
|
|
writer.endtag(tag)
|
|
|
|
writer.newline()
|
2015-10-17 00:47:12 -03:00
|
|
|
|