fonttools/fd2ft.py

1082 lines
32 KiB
Python
Raw Normal View History

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
from contextlib import contextmanager
2015-10-17 02:26:22 -03:00
debug = print
2015-12-09 17:51:06 +01:00
def makeGlyph(s):
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
2015-12-09 17:51:06 +01:00
def makeGlyphs(l):
return [makeGlyph(g) for g in l]
2015-12-08 16:21:59 +01:00
2015-10-17 02:26:22 -03:00
def parseScriptList(lines):
self = ot.ScriptList()
self.ScriptRecord = []
with lines.between('script table'):
for line in lines:
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
langSys.FeatureIndex = intSplitComma(features)
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)
2015-10-17 02:26:22 -03:00
self.ScriptCount = len(self.ScriptRecord)
# TODO sort scripts and langSys's?
return self
def parseFeatureList(lines):
self = ot.FeatureList()
self.FeatureRecord = []
with lines.between('feature table'):
for line in lines:
idx, featureTag, lookups = line
assert int(idx) == len(self.FeatureRecord), "%d %d" % (idx, len(self.FeatureRecord))
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
feature.LookupListIndex = intSplitComma(lookups)
feature.LookupCount = len(feature.LookupListIndex)
2015-10-17 02:26:22 -03:00
self.FeatureCount = len(self.FeatureRecord)
return self
2015-10-27 14:16:00 -07:00
def parseLookupFlags(lines):
flags = 0
filterset = None
2015-12-09 18:11:54 +01:00
allFlags = [
'righttoleft',
'ignorebaseglyphs',
'ignoreligatures',
'ignoremarks',
'markattachmenttype',
'markfiltertype',
]
while lines.peek()[0].lower() in allFlags:
line = next(lines)
2015-10-27 14:16:00 -07:00
flag = {
'righttoleft': 0x0001,
'ignorebaseglyphs': 0x0002,
'ignoreligatures': 0x0004,
'ignoremarks': 0x0008,
}.get(line[0].lower())
2015-10-27 14:16:00 -07:00
if flag:
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
if line[0].lower() == 'markattachmenttype':
2015-10-27 14:16:00 -07:00
flags |= int(line[1]) << 8
continue
if line[0].lower() == 'markfiltertype':
flags |= 0x10
filterset = int(line[1])
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-09 17:51:06 +01:00
line = makeGlyphs(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-09 17:51:06 +01:00
line = makeGlyphs(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-09 17:51:06 +01:00
line = makeGlyphs(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-09 17:51:06 +01:00
line = makeGlyphs(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):
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-09 17:51:06 +01:00
g = makeGlyph(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 = 0
for v in values:
self.ValueFormat |= v.getFormat()
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]
2015-12-09 17:51:06 +01:00
glyph1, glyph2 = makeGlyphs(line[1:3])
2015-12-08 23:09:24 +01:00
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:
2015-12-09 18:11:54 +01:00
assert 0, typ
2015-10-17 03:30:31 -03:00
2015-12-08 23:26:05 +01:00
def parseKernset(self, lines, font):
2015-12-09 14:00:09 +01:00
typ = lines.peek()[0].split()[0].lower()
if typ in ('left', 'right'):
with lines.until(("firstclass definition begin", "secondclass definition begin")):
return parsePair(self, lines, font)
2015-12-09 14:00:09 +01:00
return parsePair(self, lines, font)
2015-12-08 23:26:05 +01:00
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):
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]]
2015-12-09 17:51:06 +01:00
glyph = makeGlyph(line[1])
2015-12-08 23:25:12 +01:00
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-09 13:44:20 +01:00
def makeMarkRecords(data, coverage, c):
2015-12-09 12:21:17 +01:00
records = []
for glyph in coverage.glyphs:
klass, anchor = data[glyph]
record = c.MarkRecordClass()
record.Class = klass
setattr(record, c.MarkAnchor, anchor)
records.append(record)
2015-12-09 13:44:20 +01:00
return records
2015-12-09 12:21:17 +01:00
2015-12-09 13:44:20 +01:00
def makeBaseRecords(data, coverage, c, classCount):
2015-12-09 12:21:17 +01:00
records = []
idx = {}
for glyph in coverage.glyphs:
idx[glyph] = len(records)
record = c.BaseRecordClass()
anchors = [None] * classCount
setattr(record, c.BaseAnchor, anchors)
records.append(record)
for (glyph,klass),anchor in data.items():
record = records[idx[glyph]]
anchors = getattr(record, c.BaseAnchor)
assert anchors[klass] is None, (glyph, klass)
anchors[klass] = anchor
2015-12-09 13:44:20 +01:00
return records
def makeLigatureRecords(data, coverage, c, classCount):
records = [None] * len(coverage.glyphs)
idx = {g:i for i,g in enumerate(coverage.glyphs)}
for (glyph,klass,compIdx,compCount),anchor in data.items():
record = records[idx[glyph]]
if record is None:
record = records[idx[glyph]] = ot.LigatureAttach()
record.ComponentCount = compCount
record.ComponentRecord = [ot.ComponentRecord() for i in range(compCount)]
for compRec in record.ComponentRecord:
compRec.LigatureAnchor = [None] * classCount
assert record.ComponentCount == compCount, (glyph, record.ComponentCount, compCount)
anchors = record.ComponentRecord[compIdx - 1].LigatureAnchor
assert anchors[klass] is None, (glyph, compIdx, klass)
anchors[klass] = anchor
return records
2015-12-09 12:21:17 +01:00
def parseMarkToSomething(self, lines, font, c):
self.Format = 1
2015-12-09 12:21:17 +01:00
markData = {}
baseData = {}
Data = {
2015-12-09 13:44:20 +01:00
'mark': (markData, c.MarkAnchorClass),
'base': (baseData, c.BaseAnchorClass),
'ligature': (baseData, c.BaseAnchorClass),
2015-12-09 12:21:17 +01:00
}
for line in lines:
typ = line[0]
2015-12-09 13:44:20 +01:00
assert typ in ('mark', 'base', 'ligature')
2015-12-09 17:51:06 +01:00
glyph = makeGlyph(line[1])
2015-12-09 12:21:17 +01:00
data, anchorClass = Data[typ]
2015-12-09 13:44:20 +01:00
extraItems = 2 if typ == 'ligature' else 0
extras = tuple(int(i) for i in line[2:2+extraItems])
klass = int(line[2+extraItems])
anchor = makeAnchor(line[3+extraItems:], anchorClass)
2015-12-09 12:21:17 +01:00
if typ == 'mark':
2015-12-09 13:44:20 +01:00
key,value = glyph,(klass,anchor)
2015-12-09 12:21:17 +01:00
else:
2015-12-09 13:44:20 +01:00
key,value = ((glyph,klass)+extras),anchor
assert key not in data, key
data[key] = value
2015-12-09 13:44:20 +01:00
# Mark
markCoverage = makeCoverage(markData.keys(), font, c.MarkCoverageClass)
markArray = c.MarkArrayClass()
markRecords = makeMarkRecords(markData, markCoverage, c)
setattr(markArray, c.MarkRecord, markRecords)
setattr(markArray, c.MarkCount, len(markRecords))
2015-12-09 12:21:17 +01:00
setattr(self, c.MarkCoverage, markCoverage)
setattr(self, c.MarkArray, markArray)
2015-12-09 13:44:20 +01:00
# Base
2015-12-09 12:21:17 +01:00
self.classCount = 0 if not baseData else 1+max(k[1] for k,v in baseData.items())
2015-12-09 13:44:20 +01:00
baseCoverage = makeCoverage([k[0] for k in baseData.keys()], font, c.BaseCoverageClass)
baseArray = c.BaseArrayClass()
if c.Base == 'Ligature':
baseRecords = makeLigatureRecords(baseData, baseCoverage, c, self.classCount)
else:
baseRecords = makeBaseRecords(baseData, baseCoverage, c, self.classCount)
setattr(baseArray, c.BaseRecord, baseRecords)
setattr(baseArray, c.BaseCount, len(baseRecords))
2015-12-09 12:21:17 +01:00
setattr(self, c.BaseCoverage, baseCoverage)
setattr(self, c.BaseArray, baseArray)
class MarkHelper(object):
def __init__(self):
for Which in ('Mark', 'Base'):
for What in ('Coverage', 'Array', 'Count', 'Record', 'Anchor'):
key = Which + What
if Which == 'Mark' and What in ('Count', 'Record', 'Anchor'):
value = key
else:
value = getattr(self, Which) + What
2015-12-09 13:44:20 +01:00
if value == 'LigatureRecord':
value = 'LigatureAttach'
2015-12-09 12:21:17 +01:00
setattr(self, key, value)
if What != 'Count':
klass = getattr(ot, value)
setattr(self, key+'Class', klass)
class MarkToBaseHelper(MarkHelper):
Mark = 'Mark'
Base = 'Base'
class MarkToMarkHelper(MarkHelper):
Mark = 'Mark1'
Base = 'Mark2'
2015-12-09 13:44:20 +01:00
class MarkToLigatureHelper(MarkHelper):
Mark = 'Mark'
Base = 'Ligature'
2015-12-09 12:21:17 +01:00
def parseMarkToBase(self, lines, font):
return parseMarkToSomething(self, lines, font, MarkToBaseHelper())
def parseMarkToMark(self, lines, font):
2015-12-09 12:21:17 +01:00
return parseMarkToSomething(self, lines, font, MarkToMarkHelper())
2015-12-07 21:54:53 +01:00
def parseMarkToLigature(self, lines, font):
2015-12-09 13:44:20 +01:00
return parseMarkToSomething(self, lines, font, MarkToLigatureHelper())
2015-10-17 03:30:31 -03:00
2015-12-07 21:54:53 +01:00
def stripSplitComma(line):
return [s.strip() for s in line.split(',')] if line else []
2015-12-07 21:54:53 +01:00
def intSplitComma(line):
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'
InputIdx = 1
DataLen = 3
2015-12-07 21:54:53 +01:00
else:
Chain = ''
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
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)
def SetRuleData(r, d):
(r.Input,) = d
2015-12-08 18:36:32 +01:00
(r.GlyphCount,) = (len(x)+1 for x in d)
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)
def SetRuleData(r, d):
(r.Class,) = d
2015-12-08 18:36:32 +01:00
(r.GlyphCount,) = (len(x)+1 for x in d)
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)
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-09 11:50:53 +01:00
def makeClassDef(classDefs, klass=ot.Coverage):
2015-12-09 17:03:12 +01:00
if not classDefs: return None
2015-12-09 11:50:53 +01:00
self = klass()
self.classDefs = dict(classDefs)
return self
def parseClassDef(lines, klass=ot.ClassDef):
2015-12-09 11:50:53 +01:00
classDefs = {}
with lines.between('class definition'):
for line in lines:
glyph = makeGlyph(line[0])
assert glyph not in classDefs, glyph
classDefs[glyph] = int(line[1])
2015-12-09 11:50:53 +01:00
return makeClassDef(classDefs, klass)
2015-12-09 11:48:56 +01:00
def makeCoverage(glyphs, font, klass=ot.Coverage):
2015-12-09 17:03:12 +01:00
if not glyphs: return None
2015-12-08 16:09:29 +01:00
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):
glyphs = []
with lines.between('coverage definition'):
for line in lines:
glyphs.append(makeGlyph(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:
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)()
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):
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))
c = ContextHelper(Type, self.Format)
2015-12-07 21:54:53 +01:00
rules = []
for line in lines:
assert line[0].lower() == 'glyph', line[0]
2015-12-09 17:51:06 +01:00
seq = tuple(makeGlyphs(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))
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)
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))
c = ContextHelper(Type, self.Format)
2015-12-08 18:30:07 +01:00
classDefs = [None] * c.DataLen
while lines.peek()[0].endswith("class definition begin"):
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:
assert line[0].lower().startswith('class'), line[0]
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))
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)
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))
c = ContextHelper(Type, self.Format)
coverages = tuple([] for i in range(c.DataLen))
2015-12-08 16:09:29 +01:00
while lines.peek()[0].endswith("coverage definition begin"):
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]
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:
assert 0, typ
2015-10-17 03:30:31 -03:00
2015-12-07 21:54:53 +01:00
def parseContextSubst(self, lines, font):
return parseContext(self, lines, font, "ContextSubst")
2015-12-07 21:54:53 +01:00
def parseContextPos(self, lines, font):
return parseContext(self, lines, font, "ContextPos")
2015-12-07 21:54:53 +01:00
def parseChainedSubst(self, lines, font):
return parseContext(self, lines, font, "ChainContextSubst")
2015-12-08 20:00:24 +01:00
def parseChainedPos(self, lines, font):
return parseContext(self, lines, font, "ChainContextPos")
2015-12-07 21:54:53 +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
2015-12-09 17:51:06 +01:00
line = makeGlyphs(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.expect('lookup')
2015-12-08 14:37:33 +01:00
_, 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()
with lines.until('lookup end'):
lookup.LookupFlag,filterset = parseLookupFlags(lines)
if filterset is not None:
lookup.MarkFilteringSet = filterset
lookup.LookupType, parseLookupSubTable = {
'GSUB': {
'single': (1, parseSingleSubst),
'multiple': (2, parseMultiple),
'alternate': (3, parseAlternate),
'ligature': (4, parseLigature),
'context': (5, parseContextSubst),
'chained': (6, parseChainedSubst),
'reversechained':(8, parseReverseChainedSubst),
},
'GPOS': {
'single': (1, parseSinglePos),
'pair': (2, parsePair),
'kernset': (2, parseKernset),
'cursive': (3, parseCursive),
'mark to base': (4, parseMarkToBase),
'mark to ligature':(5, parseMarkToLigature),
'mark to mark': (6, parseMarkToMark),
'context': (7, parseContextPos),
'chained': (8, parseChainedPos),
},
}[tableTag][typ]
subtables = []
while lines.peek():
with lines.until(('% subtable', 'subtable end')):
while lines.peek():
subtable = ot.lookupTypes[tableTag][lookup.LookupType]()
subtable.LookupType = lookup.LookupType
parseLookupSubTable(subtable, lines, font)
subtables.append(subtable)
if lines.peek() and lines.peek()[0] in ('% subtable', 'subtable end'):
next(lines)
lines.expect('lookup end')
2015-12-08 19:38:21 +01:00
lookup.SubTable = subtables
2015-12-08 14:37:33 +01:00
lookup.SubTableCount = len(lookup.SubTable)
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 lines.peek() is not None:
if lines.peek()[0].lower() != 'lookup':
debug ('Skipping', lines.peek())
next(lines)
continue
2015-12-08 14:37:33 +01:00
lookup, name = parseLookup(lines, tableTag, font)
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
def parseGSUBGPOS(lines, font, tableTag):
assert tableTag in ('GSUB', 'GPOS')
debug("Parsing", tableTag)
self = getattr(ot, tableTag)()
2015-10-27 16:07:11 -07:00
self.Version = 1.0
self.ScriptList = self.FeatureList = self.LookupList = None
fields = {
'script table begin': ('ScriptList', parseScriptList),
'feature table begin': ('FeaturetList', parseFeatureList),
'lookup': ('LookupList', lambda lines: parseLookupList(lines, tableTag, font)),
}
for attr,parser in fields.values():
setattr(self, attr, None)
while lines.peek() is not None:
typ = lines.peek()[0].lower()
if typ not in fields:
debug ('Skipping', lines.peek())
next(lines)
continue
attr,parser = fields[typ]
assert getattr(self, attr) is None, attr
setattr(self, attr, parser(lines))
2015-10-17 02:26:22 -03:00
return self
def parseGSUB(lines, font):
return parseGSUBGPOS(lines, font, 'GSUB')
2015-12-07 21:54:53 +01:00
def parseGPOS(lines, font):
return parseGSUBGPOS(lines, font, 'GPOS')
2015-10-17 02:26:22 -03:00
2015-12-09 17:03:12 +01:00
def makeAttachList(points, font):
self = ot.AttachList()
self.Coverage = makeCoverage(points.keys(), font)
records = []
for glyph in self.Coverage.glyphs:
record = ot.AttachPoint()
record.PointIndex = sorted(set(points[glyph]))
record.PointCount = len(record.PointIndex)
records.append(record)
self.AttachPoint = records
self.GlyphCount = len(records)
return self
def parseAttachList(lines, font):
points = {}
with lines.between('attachment list'):
for line in lines:
glyph = makeGlyph(line[0])
assert glyph not in points, glyph
points[glyph] = [int(i) for i in line[1:]]
2015-12-09 17:03:12 +01:00
return makeAttachList(points, font)
def makeCaretList(carets, font):
self = ot.LigCaretList()
self.Coverage = makeCoverage(carets.keys(), font)
records = []
for glyph in self.Coverage.glyphs:
record = ot.LigGlyph()
cvs = record.CaretValue = []
for v in carets[glyph]:
cv = ot.CaretValue()
cv.Format = 1
cv.Coordinate = v
cvs.append(cv)
record.CaretCount = len(record.CaretValue)
records.append(record)
self.LigGlyph = records
self.LigGlyphCount = len(records)
return self
def parseCaretList(lines, font):
carets = {}
with lines.between('carets'):
for line in lines:
glyph = makeGlyph(line[0])
assert glyph not in carets, glyph
num = int(line[1])
thisCarets = [int(i) for i in line[2:]]
assert num == len(thisCarets), line
carets[glyph] = thisCarets
2015-12-09 17:03:12 +01:00
return makeCaretList(carets, font)
def makeMarkFilteringSets(sets, font):
self = ot.MarkGlyphSetsDef()
self.MarkSetTableFormat = 1
self.MarkSetCount = 1 + max(sets.keys())
self.Coverage = [None] * self.MarkSetCount
for k,v in sets.items():
self.Coverage[k] = makeCoverage(v, font)
return self
def parseMarkFilteringSets(lines, font):
sets = {}
with lines.between('set definition'):
for line in lines:
assert len(line) == 2, line
glyph = makeGlyph(line[0])
# TODO accept set names
st = int(line[1])
if st not in sets:
sets[st] = []
sets[st].append(glyph)
2015-12-09 17:03:12 +01:00
return makeMarkFilteringSets(sets, font)
2015-12-07 21:54:53 +01:00
def parseGDEF(lines, font):
2015-12-09 17:03:12 +01:00
debug("Parsing GDEF")
self = ot.GDEF()
fields = {
'class definition begin':
('GlyphClassDef',
lambda lines, font: parseClassDef(lines, klass=ot.GlyphClassDef)),
'attachment list begin':
('AttachList', parseAttachList),
'carets begin':
('LigCaretList', parseCaretList),
'mark attachment class definition begin':
('MarkAttachClassDef',
lambda lines, font: parseClassDef(lines, klass=ot.MarkAttachClassDef)),
'markfilter set definition begin':
('MarkGlyphSetsDef', parseMarkFilteringSets),
}
for attr,parser in fields.values():
setattr(self, attr, None)
while lines.peek() is not None:
typ = lines.peek()[0].lower()
if typ not in fields:
debug ('Skipping', line)
next(lines)
continue
attr,parser = fields[typ]
assert getattr(self, attr) is None, attr
setattr(self, attr, parser(lines, font))
self.Version = 1.0 if self.MarkGlyphSetsDef is None else 0x00010002
return self
2015-10-17 02:26:22 -03:00
class Tokenizer(object):
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
self.stoppers = []
self.buffer = None
2015-10-27 12:44:32 -07:00
def __iter__(self):
return self
def _next_line(self):
2015-10-27 12:44:32 -07:00
self._lineno += 1
return next(self._lines)
def _next_nonempty(self):
2015-10-27 12:44:32 -07:00
while True:
line = self._next_line()
2015-10-27 12:44:32 -07:00
# 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 _next_buffered(self):
if self.buffer:
ret = self.buffer
self.buffer = None
return ret
else:
return self._next_nonempty()
def __next__(self):
line = self._next_buffered()
if line[0].lower() in self.stoppers:
self.buffer = line
raise StopIteration
return line
def next(self):
return self.__next__()
def peek(self):
if not self.buffer:
try:
self.buffer = self._next_nonempty()
except StopIteration:
return None
if self.buffer[0].lower() in self.stoppers:
return None
return self.buffer
@contextmanager
def between(self, tag):
start = tag + ' begin'
end = tag + ' end'
self.expectendswith(start)
self.stoppers.append(end)
yield
del self.stoppers[-1]
self.expect(tag + ' end')
@contextmanager
def until(self, tags):
if type(tags) is not tuple:
tags = (tags,)
self.stoppers.extend(tags)
yield
del self.stoppers[-len(tags):]
def expect(self, s):
line = next(self)
tag = line[0].lower()
assert tag == s, "Expected '%s', got '%s'" % (s, tag)
return line
def expectendswith(self, s):
line = next(self)
tag = line[0].lower()
assert tag.endswith(s), "Expected '*%s', got '%s'" % (s, tag)
return line
2015-12-09 17:03:12 +01:00
def parseTable(lines, font, tableTag=None):
2015-12-08 14:56:23 +01:00
debug("Parsing table")
2015-12-09 17:03:12 +01:00
line = lines.peek()
if line[0].split()[0] == 'FontDame':
next(lines)
tag = line[0].split()[1].ljust(4)
if tableTag is None:
tableTag = tag
else:
assert tableTag == tag, (tableTag, tag)
assert tableTag is not None, "Don't know what table to parse and data doesn't specify"
2015-10-17 02:26:22 -03:00
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-09 17:03:12 +01:00
def build(f, font, tableTag=None):
lines = Tokenizer(f)
2015-12-09 17:03:12 +01:00
return parseTable(lines, font, tableTag=tableTag)
2015-12-08 14:56:23 +01:00
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-12-09 11:48:56 +01:00
def main(args):
2015-10-27 16:07:11 -07:00
font = MockFont()
2015-12-09 17:03:12 +01:00
tableTag = None
if args[0].startswith('-t'):
tableTag = args[0][2:]
del args[0]
2015-12-09 11:48:56 +01:00
for f in args:
2015-10-17 02:26:22 -03:00
debug("Processing", f)
2015-12-09 17:03:12 +01:00
table = build(open(f, 'rt'), font, tableTag=tableTag)
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-09 17:03:12 +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
2015-12-09 11:48:56 +01:00
if __name__ == '__main__':
import sys
main (sys.argv[1:])