Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1403 lines
46 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:
2016-04-21 16:11:12 -07:00
# http://monotype.github.io/OpenType_Table_Source/otl_source.html
2015-11-24 15:01:11 -06:00
# https://github.com/Monotype/OpenType_Table_Source/
2015-10-17 00:47:12 -03:00
2015-10-17 02:26:22 -03:00
from fontTools import ttLib
2016-06-30 12:45:44 -07:00
from fontTools.ttLib.tables._c_m_a_p import cmap_classes
2015-10-17 02:26:22 -03:00
from fontTools.ttLib.tables import otTables as ot
2015-12-08 22:22:33 +01:00
from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
2016-01-22 19:24:13 +01:00
from fontTools.otlLib import builder as otl
from contextlib import contextmanager
2023-03-02 20:42:37 +00:00
from fontTools.ttLib import newTable
from fontTools.feaLib.lookupDebugInfo import LOOKUP_DEBUG_ENV_VAR, LOOKUP_DEBUG_INFO_KEY
from operator import setitem
import os
import logging
2015-10-17 02:26:22 -03:00
2022-12-13 11:26:36 +00:00
class MtiLibError(Exception):
pass
2022-12-13 11:26:36 +00:00
class ReferenceNotFoundError(MtiLibError):
pass
2022-12-13 11:26:36 +00:00
class FeatureNotFoundError(ReferenceNotFoundError):
pass
2022-12-13 11:26:36 +00:00
class LookupNotFoundError(ReferenceNotFoundError):
pass
log = logging.getLogger("fontTools.mtiLib")
2015-10-17 02:26:22 -03:00
2015-12-09 17:51:06 +01:00
def makeGlyph(s):
if s[:2] in ["U ", "u "]:
return ttLib.TTFont._makeGlyphName(int(s[2:], 16))
elif s[:2] == "# ":
return "glyph%.5d" % int(s[2:])
2015-12-10 12:54:21 +01:00
assert s.find(" ") < 0, "Space found in glyph name: %s" % s
assert s, "Glyph name is empty"
2015-12-08 16:21:59 +01:00
return s
2022-12-13 11:26:36 +00:00
2015-12-08 16:21:59 +01:00
2015-12-09 17:51:06 +01:00
def makeGlyphs(l):
return [makeGlyph(g) for g in l]
2022-12-13 11:26:36 +00:00
2015-12-08 16:21:59 +01:00
def mapLookup(sym, mapping):
# Lookups are addressed by name. So resolved them using a map if available.
# Fallback to parsing as lookup index if a map isn't provided.
if mapping is not None:
try:
idx = mapping[sym]
except KeyError:
raise LookupNotFoundError(sym)
else:
idx = int(sym)
return idx
def mapFeature(sym, mapping):
# Features are referenced by index according the spec. So, if symbol is an
# integer, use it directly. Otherwise look up in the map if provided.
try:
idx = int(sym)
except ValueError:
try:
idx = mapping[sym]
except KeyError:
raise FeatureNotFoundError(sym)
return idx
def setReference(mapper, mapping, sym, setter, collection, key):
2022-12-13 11:26:36 +00:00
try:
mapped = mapper(sym, mapping)
except ReferenceNotFoundError as e:
2022-12-13 11:26:36 +00:00
try:
if mapping is not None:
mapping.addDeferredMapping(
lambda ref: setter(collection, key, ref), sym, e
2022-12-13 11:26:36 +00:00
)
return
except AttributeError:
2022-12-13 11:26:36 +00:00
pass
raise
setter(collection, key, mapped)
class DeferredMapping(dict):
def __init__(self):
self._deferredMappings = []
2022-12-13 11:26:36 +00:00
def addDeferredMapping(self, setter, sym, e):
log.debug("Adding deferred mapping for symbol '%s' %s", sym, type(e).__name__)
self._deferredMappings.append((setter, sym, e))
2022-12-13 11:26:36 +00:00
def applyDeferredMappings(self):
for setter, sym, e in self._deferredMappings:
log.debug(
"Applying deferred mapping for symbol '%s' %s", sym, type(e).__name__
2022-12-13 11:26:36 +00:00
)
try:
mapped = self[sym]
except KeyError:
raise e
setter(mapped)
log.debug("Set to %s", mapped)
self._deferredMappings = []
def parseScriptList(lines, featureMap=None):
2015-10-17 02:26:22 -03:00
self = ot.ScriptList()
records = []
with lines.between("script table"):
for line in lines:
while len(line) < 4:
line.append("")
scriptTag, langSysTag, defaultFeature, features = line
log.debug("Adding script %s language-system %s", scriptTag, langSysTag)
2022-12-13 11:26:36 +00:00
langSys = ot.LangSys()
langSys.LookupOrder = None
if defaultFeature:
setReference(
mapFeature,
featureMap,
defaultFeature,
setattr,
langSys,
"ReqFeatureIndex",
)
else:
langSys.ReqFeatureIndex = 0xFFFF
syms = stripSplitComma(features)
langSys.FeatureIndex = theList = [3] * len(syms)
for i, sym in enumerate(syms):
setReference(mapFeature, featureMap, sym, setitem, theList, i)
langSys.FeatureCount = len(langSys.FeatureIndex)
2022-12-13 11:26:36 +00:00
script = [s for s in records if s.ScriptTag == scriptTag]
if script:
script = script[0].Script
else:
scriptRec = ot.ScriptRecord()
scriptRec.ScriptTag = scriptTag + " " * (4 - len(scriptTag))
scriptRec.Script = ot.Script()
records.append(scriptRec)
script = scriptRec.Script
script.DefaultLangSys = None
script.LangSysRecord = []
script.LangSysCount = 0
2022-12-13 11:26:36 +00:00
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)
2022-12-13 11:26:36 +00:00
for script in records:
script.Script.LangSysRecord = sorted(
script.Script.LangSysRecord, key=lambda rec: rec.LangSysTag
)
self.ScriptRecord = sorted(records, key=lambda rec: rec.ScriptTag)
2015-10-17 02:26:22 -03:00
self.ScriptCount = len(self.ScriptRecord)
return self
2022-12-13 11:26:36 +00:00
2015-10-17 02:26:22 -03:00
def parseFeatureList(lines, lookupMap=None, featureMap=None):
2015-10-17 02:26:22 -03:00
self = ot.FeatureList()
self.FeatureRecord = []
with lines.between("feature table"):
for line in lines:
name, featureTag, lookups = line
if featureMap is not None:
assert name not in featureMap, "Duplicate feature name: %s" % name
featureMap[name] = len(self.FeatureRecord)
# If feature name is integer, make sure it matches its index.
try:
assert int(name) == len(self.FeatureRecord), "%d %d" % (
name,
len(self.FeatureRecord),
)
except ValueError:
pass
featureRec = ot.FeatureRecord()
featureRec.FeatureTag = featureTag
featureRec.Feature = ot.Feature()
self.FeatureRecord.append(featureRec)
feature = featureRec.Feature
feature.FeatureParams = None
syms = stripSplitComma(lookups)
feature.LookupListIndex = theList = [None] * len(syms)
for i, sym in enumerate(syms):
setReference(mapLookup, lookupMap, sym, setitem, theList, i)
feature.LookupCount = len(feature.LookupListIndex)
2022-12-13 11:26:36 +00:00
2015-10-17 02:26:22 -03:00
self.FeatureCount = len(self.FeatureRecord)
return self
2022-12-13 11:26:36 +00:00
2015-10-17 02:26:22 -03:00
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",
]
2016-12-27 13:37:11 -05:00
while lines.peeks()[0].lower() in allFlags:
2015-12-09 18:11:54 +01:00
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
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
def parseSingleSubst(lines, font, _lookupMap=None):
mapping = {}
2015-10-27 12:44:32 -07:00
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]
return otl.buildSingleSubstSubtable(mapping)
2022-12-13 11:26:36 +00:00
2015-10-27 12:44:32 -07:00
def parseMultiple(lines, font, _lookupMap=None):
mapping = {}
2015-10-27 14:16:00 -07:00
for line in lines:
2015-12-09 17:51:06 +01:00
line = makeGlyphs(line)
mapping[line[0]] = line[1:]
return otl.buildMultipleSubstSubtable(mapping)
2022-12-13 11:26:36 +00:00
2015-10-17 03:30:31 -03:00
def parseAlternate(lines, font, _lookupMap=None):
mapping = {}
2015-10-27 14:16:00 -07:00
for line in lines:
2015-12-09 17:51:06 +01:00
line = makeGlyphs(line)
mapping[line[0]] = line[1:]
return otl.buildAlternateSubstSubtable(mapping)
2022-12-13 11:26:36 +00:00
2015-10-17 03:30:31 -03:00
def parseLigature(lines, font, _lookupMap=None):
mapping = {}
2015-10-27 12:44:32 -07:00
for line in lines:
assert len(line) >= 2, line
2015-12-09 17:51:06 +01:00
line = makeGlyphs(line)
mapping[tuple(line[1:])] = line[0]
return otl.buildLigatureSubstSubtable(mapping)
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
2016-01-22 13:44:27 +01:00
def parseSinglePos(lines, font, _lookupMap=None):
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)
2016-01-22 19:24:13 +01:00
return otl.buildSinglePosSubtable(values, font.getReverseGlyphMap())
2022-12-13 11:26:36 +00:00
2015-10-17 03:30:31 -03:00
def parsePair(lines, font, _lookupMap=None):
self = ot.PairPos()
2015-12-08 22:47:37 +01:00
self.ValueFormat1 = self.ValueFormat2 = 0
2016-12-27 13:37:11 -05:00
typ = lines.peeks()[0].split()[0].lower()
2015-12-08 22:47:37 +01:00
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(set(values.keys()), font)
2015-12-08 23:09:24 +01:00
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
2016-12-26 16:36:38 -05:00
pair.Value1 = values2[0]
pair.Value2 = values2[1] if self.ValueFormat2 else None
2015-12-08 23:09:24 +01:00
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]
2016-12-27 13:37:11 -05:00
while lines.peeks()[0].endswith("class definition begin"):
2015-12-08 22:47:37 +01:00
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, font, klass=klass)
2015-12-08 22:47:37 +01:00
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)
2016-12-26 17:14:39 -05:00
for rec1 in self.Class1Record:
for rec2 in rec1.Class2Record:
rec2.Value1 = ValueRecord(self.ValueFormat1, rec2.Value1)
rec2.Value2 = (
ValueRecord(self.ValueFormat2, rec2.Value2)
if self.ValueFormat2
else None
2022-12-13 11:26:36 +00:00
)
self.Coverage = makeCoverage(set(self.ClassDef1.classDefs.keys()), font)
2015-12-08 22:47:37 +01:00
else:
2015-12-09 18:11:54 +01:00
assert 0, typ
return self
2022-12-13 11:26:36 +00:00
2015-10-17 03:30:31 -03:00
def parseKernset(lines, font, _lookupMap=None):
2016-12-27 13:37:11 -05:00
typ = lines.peeks()[0].split()[0].lower()
2015-12-09 14:00:09 +01:00
if typ in ("left", "right"):
with lines.until(
("firstclass definition begin", "secondclass definition begin")
):
return parsePair(lines, font)
return parsePair(lines, font)
2022-12-13 11:26:36 +00:00
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
2022-12-13 11:26:36 +00:00
2015-12-08 23:40:41 +01:00
def parseCursive(lines, font, _lookupMap=None):
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)
2016-01-22 19:38:20 +01:00
return otl.buildCursivePosSubtable(records, font.getReverseGlyphMap())
2022-12-13 11:26:36 +00:00
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
2022-12-13 11:26:36 +00:00
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
2022-12-13 11:26:36 +00:00
2015-12-09 13:44:20 +01:00
def makeLigatureRecords(data, coverage, c, classCount):
records = [None] * len(coverage.glyphs)
idx = {g: i for i, g in enumerate(coverage.glyphs)}
2022-12-13 11:26:36 +00:00
2015-12-09 13:44:20 +01:00
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,
)
2022-12-13 11:26:36 +00:00
2015-12-09 13:44:20 +01:00
anchors = record.ComponentRecord[compIdx - 1].LigatureAnchor
assert anchors[klass] is None, (glyph, compIdx, klass)
anchors[klass] = anchor
return records
2022-12-13 11:26:36 +00:00
2015-12-09 12:21:17 +01:00
def parseMarkToSomething(lines, font, c):
self = c.Type()
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
}
maxKlass = 0
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
maxKlass = max(maxKlass, klass)
2022-12-13 11:26:36 +00:00
2015-12-09 13:44:20 +01:00
# Mark
markCoverage = makeCoverage(set(markData.keys()), font, c.MarkCoverageClass)
2015-12-09 13:44:20 +01:00
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)
self.ClassCount = maxKlass + 1
2022-12-13 11:26:36 +00:00
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())
baseCoverage = makeCoverage(
set([k[0] for k in baseData.keys()]), font, c.BaseCoverageClass
)
2015-12-09 13:44:20 +01:00
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)
2022-12-13 11:26:36 +00:00
return self
2022-12-13 11:26:36 +00:00
2015-12-09 12:21:17 +01:00
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)
2022-12-13 11:26:36 +00:00
2015-12-09 12:21:17 +01:00
class MarkToBaseHelper(MarkHelper):
Mark = "Mark"
Base = "Base"
Type = ot.MarkBasePos
2022-12-13 11:26:36 +00:00
2015-12-09 12:21:17 +01:00
class MarkToMarkHelper(MarkHelper):
Mark = "Mark1"
Base = "Mark2"
Type = ot.MarkMarkPos
2022-12-13 11:26:36 +00:00
2015-12-09 13:44:20 +01:00
class MarkToLigatureHelper(MarkHelper):
Mark = "Mark"
Base = "Ligature"
Type = ot.MarkLigPos
2022-12-13 11:26:36 +00:00
2015-12-09 12:21:17 +01:00
def parseMarkToBase(lines, font, _lookupMap=None):
return parseMarkToSomething(lines, font, MarkToBaseHelper())
2022-12-13 11:26:36 +00:00
def parseMarkToMark(lines, font, _lookupMap=None):
return parseMarkToSomething(lines, font, MarkToMarkHelper())
2022-12-13 11:26:36 +00:00
def parseMarkToLigature(lines, font, _lookupMap=None):
return parseMarkToSomething(lines, font, MarkToLigatureHelper())
2022-12-13 11:26:36 +00:00
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 []
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
def intSplitComma(line):
return [int(i) for i in line.split(",")] if line else []
2022-12-13 11:26:36 +00:00
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
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
self.Typ = Typ
self.Type = Type
self.Chain = Chain
self.ChainTyp = ChainTyp
self.InputIdx = InputIdx
self.DataLen = DataLen
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
self.LookupRecord = Type + "LookupRecord"
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
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)
2022-12-13 11:26:36 +00: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)
2022-12-13 11:26:36 +00: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]))
2022-12-13 11:26:36 +00:00
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,
)
2022-12-13 11:26:36 +00:00
2015-12-08 18:30:07 +01:00
def SetContextData(r, d):
(r.ClassDef,) = d
2022-12-13 11:26:36 +00:00
2015-12-08 18:30:07 +01:00
def SetChainContextData(r, d):
(r.BacktrackClassDef, r.InputClassDef, r.LookAheadClassDef) = d
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
RuleData = lambda r: (r.Class,)
ChainRuleData = lambda r: (r.Backtrack, r.Input, r.LookAhead)
2022-12-13 11:26:36 +00: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)
2022-12-13 11:26:36 +00: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]))
2022-12-13 11:26:36 +00:00
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
)
2022-12-13 11:26:36 +00:00
def SetRuleData(r, d):
(r.Coverage,) = d
(r.GlyphCount,) = (len(x) for x in d)
2022-12-13 11:26:36 +00:00
def ChainSetRuleData(r, d):
(r.BacktrackCoverage, r.InputCoverage, r.LookAheadCoverage) = d
(
r.BacktrackGlyphCount,
r.InputGlyphCount,
r.LookAheadGlyphCount,
) = (len(x) for x in d)
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
else:
assert 0, "unknown format: %s" % Format
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
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
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
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())
2022-12-13 11:26:36 +00:00
)
2015-12-07 21:54:53 +01:00
self.ClassDef = "InputClassDef" if Chain else "ClassDef"
self.ClassDefIndex = 1 if Chain else 0
self.Input = "Input" if Chain else "Class"
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
def parseLookupRecords(items, klassName, lookupMap=None):
2015-12-07 21:54:53 +01:00
klass = getattr(ot, klassName)
lst = []
for item in items:
rec = klass()
item = stripSplitComma(item)
2015-12-07 21:54:53 +01:00
assert len(item) == 2, item
idx = int(item[0])
assert idx > 0, idx
rec.SequenceIndex = idx - 1
setReference(mapLookup, lookupMap, item[1], setattr, rec, "LookupListIndex")
2015-12-07 21:54:53 +01:00
lst.append(rec)
return lst
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
def makeClassDef(classDefs, font, 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
2022-12-13 11:26:36 +00:00
2015-12-09 11:50:53 +01:00
def parseClassDef(lines, font, 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])
return makeClassDef(classDefs, font, klass)
2022-12-13 11:26:36 +00:00
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
if isinstance(glyphs, set):
glyphs = sorted(glyphs)
2015-12-08 16:09:29 +01:00
coverage = klass()
coverage.glyphs = sorted(set(glyphs), key=font.getGlyphID)
2015-12-07 21:54:53 +01:00
return coverage
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
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)
2022-12-13 11:26:36 +00:00
2015-12-08 16:09:29 +01:00
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)
2022-12-13 11:26:36 +00:00
)
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)
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
ruleset = getattr(ot, c.RuleSet)()
setattr(ruleset, c.Rule, thisRules)
setattr(ruleset, c.RuleCount, len(thisRules))
rulesets.append(ruleset)
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
setattr(self, c.RuleSet, rulesets)
setattr(self, c.RuleSetCount, len(rulesets))
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
2016-12-27 15:06:03 -05:00
def parseContext(lines, font, Type, lookupMap=None):
self = getattr(ot, Type)()
2016-12-27 13:37:11 -05:00
typ = lines.peeks()[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
log.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]
2016-04-27 16:55:56 -07:00
while len(line) < 1 + c.DataLen:
line.append("")
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, lookupMap)
2015-12-07 21:54:53 +01:00
rules.append((seq, recs))
2022-12-13 11:26:36 +00: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)
elif typ.endswith("class"):
2015-10-27 16:07:11 -07:00
self.Format = 2
log.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
2016-12-27 13:37:11 -05:00
while lines.peeks()[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, font, klass=klass)
2015-12-08 18:30:07 +01:00
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]
2016-04-27 16:55:56 -07:00
while len(line) < 1 + c.DataLen:
line.append("")
seq = tuple(intSplitComma(i) for i in line[1 : 1 + c.DataLen])
recs = parseLookupRecords(line[1 + c.DataLen :], c.LookupRecord, lookupMap)
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
log.debug("Parsing %s format %s", Type, self.Format)
c = ContextHelper(Type, self.Format)
coverages = tuple([] for i in range(c.DataLen))
2016-12-27 13:37:11 -05:00
while lines.peeks()[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]
recs = parseLookupRecords(line[1:], c.LookupRecord, lookupMap)
2015-12-08 16:09:29 +01:00
setattr(self, c.Type + "Count", len(recs))
setattr(self, c.LookupRecord, recs)
else:
assert 0, typ
2016-12-27 15:06:03 -05:00
return self
2022-12-13 11:26:36 +00:00
2015-10-17 03:30:31 -03:00
2016-12-27 15:06:03 -05:00
def parseContextSubst(lines, font, lookupMap=None):
return parseContext(lines, font, "ContextSubst", lookupMap=lookupMap)
2022-12-13 11:26:36 +00:00
2016-12-27 15:06:03 -05:00
def parseContextPos(lines, font, lookupMap=None):
return parseContext(lines, font, "ContextPos", lookupMap=lookupMap)
2022-12-13 11:26:36 +00:00
2016-12-27 15:06:03 -05:00
def parseChainedSubst(lines, font, lookupMap=None):
return parseContext(lines, font, "ChainContextSubst", lookupMap=lookupMap)
2022-12-13 11:26:36 +00:00
2016-12-27 15:06:03 -05:00
def parseChainedPos(lines, font, lookupMap=None):
return parseContext(lines, font, "ChainContextPos", lookupMap=lookupMap)
2022-12-13 11:26:36 +00:00
2015-12-07 21:54:53 +01:00
def parseReverseChainedSubst(lines, font, _lookupMap=None):
self = ot.ReverseChainSingleSubst()
self.Format = 1
coverages = ([], [])
2016-12-27 13:37:11 -05:00
while lines.peeks()[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(set(mapping.keys()), font)
self.Substitute = [mapping[k] for k in self.Coverage.glyphs]
self.GlyphCount = len(self.Substitute)
return self
2022-12-13 11:26:36 +00:00
def parseLookup(lines, tableTag, font, lookupMap=None):
line = lines.expect("lookup")
2015-12-08 14:37:33 +01:00
_, name, typ = line
log.debug("Parsing lookup type %s %s", typ, name)
2015-12-08 14:37:33 +01:00
lookup = ot.Lookup()
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]
2022-12-13 11:26:36 +00:00
with lines.until("lookup end"):
subtables = []
2022-12-13 11:26:36 +00:00
while lines.peek():
with lines.until(("% subtable", "subtable end")):
while lines.peek():
subtable = parseLookupSubTable(lines, font, lookupMap)
assert lookup.LookupType == subtable.LookupType
subtables.append(subtable)
2016-12-27 13:37:11 -05:00
if lines.peeks()[0] in ("% subtable", "subtable end"):
next(lines)
lines.expect("lookup end")
2022-12-13 11:26:36 +00:00
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 == 0:
# Remove this return when following is fixed:
# https://github.com/fonttools/fonttools/issues/789
return None
return lookup
2022-12-13 11:26:36 +00:00
2015-12-08 14:37:33 +01:00
def parseGSUBGPOS(lines, font, tableTag):
container = ttLib.getTableClass(tableTag)()
lookupMap = DeferredMapping()
featureMap = DeferredMapping()
assert tableTag in ("GSUB", "GPOS")
log.debug("Parsing %s", tableTag)
self = getattr(ot, tableTag)()
2016-12-26 15:29:09 -05:00
self.Version = 0x00010000
fields = {
"script table begin": (
"ScriptList",
lambda lines: parseScriptList(lines, featureMap),
2022-12-13 11:26:36 +00:00
),
"feature table begin": (
2016-01-22 18:11:58 +01:00
"FeatureList",
lambda lines: parseFeatureList(lines, lookupMap, featureMap),
2022-12-13 11:26:36 +00:00
),
"lookup": ("LookupList", None),
}
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:
log.debug("Skipping %s", lines.peek())
next(lines)
continue
attr, parser = fields[typ]
if typ == "lookup":
if self.LookupList is None:
self.LookupList = ot.LookupList()
self.LookupList.Lookup = []
_, name, _ = lines.peek()
lookup = parseLookup(lines, tableTag, font, lookupMap)
if lookupMap is not None:
assert name not in lookupMap, "Duplicate lookup name: %s" % name
lookupMap[name] = len(self.LookupList.Lookup)
else:
assert int(name) == len(self.LookupList.Lookup), "%d %d" % (
name,
len(self.Lookup),
)
self.LookupList.Lookup.append(lookup)
else:
assert getattr(self, attr) is None, attr
setattr(self, attr, parser(lines))
if self.LookupList:
self.LookupList.LookupCount = len(self.LookupList.Lookup)
if lookupMap is not None:
lookupMap.applyDeferredMappings()
if os.environ.get(LOOKUP_DEBUG_ENV_VAR):
if "Debg" not in font:
font["Debg"] = newTable("Debg")
font["Debg"].data = {}
debug = (
font["Debg"]
.data.setdefault(LOOKUP_DEBUG_INFO_KEY, {})
.setdefault(tableTag, {})
)
for name, lookup in lookupMap.items():
debug[str(lookup)] = ["", name, ""]
2023-03-02 20:42:37 +00:00
featureMap.applyDeferredMappings()
2016-06-30 12:45:44 -07:00
container.table = self
return container
2022-12-13 11:26:36 +00:00
2015-10-17 02:26:22 -03:00
def parseGSUB(lines, font):
return parseGSUBGPOS(lines, font, "GSUB")
2022-12-13 11:26:36 +00:00
def parseGPOS(lines, font):
return parseGSUBGPOS(lines, font, "GPOS")
2022-12-13 11:26:36 +00:00
2015-10-17 02:26:22 -03:00
2015-12-09 17:03:12 +01:00
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:]]
2016-01-22 19:52:19 +01:00
return otl.buildAttachList(points, font.getReverseGlyphMap())
2022-12-13 11:26:36 +00:00
2015-12-09 17:03:12 +01:00
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
2016-01-22 19:24:13 +01:00
return otl.buildLigCaretList(carets, {}, font.getReverseGlyphMap())
2022-12-13 11:26:36 +00:00
2015-12-09 17:03:12 +01:00
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 sorted(sets.items()):
self.Coverage[k] = makeCoverage(set(v), font)
2015-12-09 17:03:12 +01:00
return self
2022-12-13 11:26:36 +00:00
2015-12-09 17:03:12 +01:00
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)
2022-12-13 11:26:36 +00:00
2015-12-09 17:03:12 +01:00
def parseGDEF(lines, font):
container = ttLib.getTableClass("GDEF")()
log.debug("Parsing GDEF")
2015-12-09 17:03:12 +01:00
self = ot.GDEF()
fields = {
"class definition begin": (
"GlyphClassDef",
lambda lines, font: parseClassDef(lines, font, klass=ot.GlyphClassDef),
2022-12-13 11:26:36 +00:00
),
2015-12-09 17:03:12 +01:00
"attachment list begin": ("AttachList", parseAttachList),
"carets begin": ("LigCaretList", parseCaretList),
"mark attachment class definition begin": (
"MarkAttachClassDef",
lambda lines, font: parseClassDef(lines, font, klass=ot.MarkAttachClassDef),
2022-12-13 11:26:36 +00:00
),
2015-12-09 17:03:12 +01:00
"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:
log.debug("Skipping %s", typ)
2015-12-09 17:03:12 +01:00
next(lines)
continue
attr, parser = fields[typ]
assert getattr(self, attr) is None, attr
setattr(self, attr, parser(lines, font))
2016-12-26 15:29:09 -05:00
self.Version = 0x00010000 if self.MarkGlyphSetsDef is None else 0x00010002
2016-06-30 12:45:44 -07:00
container.table = self
return container
2022-12-13 11:26:36 +00:00
2016-06-30 12:45:44 -07:00
def parseCmap(lines, font):
container = ttLib.getTableClass("cmap")()
2016-06-30 12:45:44 -07:00
log.debug("Parsing cmap")
tables = []
while lines.peek() is not None:
lines.expect("cmap subtable %d" % len(tables))
platId, encId, fmt, lang = [
parseCmapId(lines, field)
for field in ("platformID", "encodingID", "format", "language")
]
table = cmap_classes[fmt](fmt)
table.platformID = platId
table.platEncID = encId
table.language = lang
table.cmap = {}
line = next(lines)
while line[0] != "end subtable":
table.cmap[int(line[0], 16)] = line[1]
line = next(lines)
tables.append(table)
container.tableVersion = 0
container.tables = tables
return container
2022-12-13 11:26:36 +00:00
2016-06-30 12:45:44 -07:00
def parseCmapId(lines, field):
line = next(lines)
assert field == line[0]
return int(line[1])
2022-12-13 11:26:36 +00:00
2015-10-17 02:26:22 -03:00
2016-01-19 16:40:37 +01:00
def parseTable(lines, font, tableTag=None):
log.debug("Parsing table")
2016-12-27 13:37:11 -05:00
line = lines.peeks()
2016-06-30 12:45:44 -07:00
tag = None
2016-01-19 16:40:37 +01:00
if line[0].split()[0] == "FontDame":
2016-06-30 12:45:44 -07:00
tag = line[0].split()[1]
elif " ".join(line[0].split()[:3]) == "Font Chef Table":
tag = line[0].split()[3]
if tag is not None:
2016-01-19 16:40:37 +01:00
next(lines)
2016-06-30 12:45:44 -07:00
tag = tag.ljust(4)
2016-01-19 16:40:37 +01:00
if tableTag is None:
tableTag = tag
else:
assert tableTag == tag, (tableTag, tag)
2022-12-13 11:26:36 +00:00
2016-01-19 16:40:37 +01:00
assert (
tableTag is not None
), "Don't know what table to parse and data doesn't specify"
2022-12-13 11:26:36 +00:00
return {
"GSUB": parseGSUB,
"GPOS": parseGPOS,
"GDEF": parseGDEF,
"cmap": parseCmap,
}[tableTag](lines, font)
2022-12-13 11:26:36 +00:00
2016-01-19 16:40:37 +01: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)
2022-12-13 11:26:36 +00:00
try:
2015-10-27 12:44:32 -07:00
self.filename = f.name
2022-12-13 11:26:36 +00:00
except:
2015-10-27 12:44:32 -07:00
self.filename = None
2016-04-27 16:55:56 -07:00
self.lines = iter(lines)
self.line = ""
2015-12-15 12:09:11 +01:00
self.lineno = 0
self.stoppers = []
self.buffer = None
2022-12-13 11:26:36 +00:00
2015-10-27 12:44:32 -07:00
def __iter__(self):
return self
2022-12-13 11:26:36 +00:00
def _next_line(self):
2015-12-15 12:09:11 +01:00
self.lineno += 1
2016-04-27 16:55:56 -07:00
line = self.line = next(self.lines)
line = [s.strip() for s in line.split("\t")]
if len(line) == 1 and not line[0]:
del line[0]
if line and not line[-1]:
log.warning("trailing tab found on line %d: %s" % (self.lineno, self.line))
while line and not line[-1]:
del line[-1]
return line
2022-12-13 11:26:36 +00:00
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
if line and line[0] and (line[0][0] != "%" or line[0] == "% subtable"):
return line
2022-12-13 11:26:36 +00:00
def _next_buffered(self):
if self.buffer:
ret = self.buffer
self.buffer = None
2016-12-27 13:37:11 -05:00
return ret
2022-12-13 11:26:36 +00:00
else:
return self._next_nonempty()
2022-12-13 11:26:36 +00:00
def __next__(self):
line = self._next_buffered()
if line[0].lower() in self.stoppers:
self.buffer = line
raise StopIteration
return line
2022-12-13 11:26:36 +00:00
def next(self):
return self.__next__()
2022-12-13 11:26:36 +00:00
def peek(self):
if not self.buffer:
2022-12-13 11:26:36 +00:00
try:
self.buffer = self._next_nonempty()
except StopIteration:
2016-12-27 13:37:11 -05:00
return None
if self.buffer[0].lower() in self.stoppers:
2016-12-27 13:37:11 -05:00
return None
return self.buffer
2022-12-13 11:26:36 +00:00
2016-12-27 13:37:11 -05:00
def peeks(self):
ret = self.peek()
return ret if ret is not None else ("",)
2022-12-13 11:26:36 +00:00
@contextmanager
def between(self, tag):
start = tag + " begin"
end = tag + " end"
self.expectendswith(start)
self.stoppers.append(end)
2022-12-13 11:26:36 +00:00
yield
del self.stoppers[-1]
self.expect(tag + " end")
2022-12-13 11:26:36 +00:00
@contextmanager
def until(self, tags):
if type(tags) is not tuple:
tags = (tags,)
self.stoppers.extend(tags)
2022-12-13 11:26:36 +00:00
yield
del self.stoppers[-len(tags) :]
2022-12-13 11:26:36 +00:00
def expect(self, s):
line = next(self)
tag = line[0].lower()
assert tag == s, "Expected '%s', got '%s'" % (s, tag)
return line
2022-12-13 11:26:36 +00:00
def expectendswith(self, s):
line = next(self)
tag = line[0].lower()
assert tag.endswith(s), "Expected '*%s', got '%s'" % (s, tag)
return line
2015-10-27 12:44:32 -07:00
2015-12-09 17:03:12 +01:00
def build(f, font, tableTag=None):
2020-09-03 14:12:49 +01:00
"""Convert a Monotype font layout file to an OpenType layout object
2020-07-22 14:31:17 +01:00
A font object must be passed, but this may be a "dummy" font; it is only
2020-09-03 14:12:49 +01:00
used for sorting glyph sets when making coverage tables and to hold the
OpenType layout table while it is being built.
2020-07-22 14:31:17 +01:00
Args:
f: A file object.
font (TTFont): A font object.
tableTag (string): If provided, asserts that the file contains data for the
given OpenType table.
Returns:
An object representing the table. (e.g. ``table_G_S_U_B_``)
"""
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
def main(args=None, font=None):
2022-08-18 07:40:13 -06:00
"""Convert a FontDame OTL file to TTX XML
2022-12-13 11:26:36 +00:00
2020-07-22 14:31:17 +01:00
Writes XML output to stdout.
2022-12-13 11:26:36 +00:00
2020-07-22 14:31:17 +01:00
Args:
args: Command line arguments (``--font``, ``--table``, input files).
"""
import sys
from fontTools import configLogger
from fontTools.misc.testTools import MockFont
2022-12-13 11:26:36 +00:00
if args is None:
args = sys.argv[1:]
2022-12-13 11:26:36 +00:00
# configure the library logger (for >= WARNING)
configLogger()
# comment this out to enable debug messages from mtiLib's logger
# log.setLevel(logging.DEBUG)
2022-12-13 11:26:36 +00:00
import argparse
2022-12-13 11:26:36 +00:00
parser = argparse.ArgumentParser(
"fonttools mtiLib",
description=main.__doc__,
)
2022-12-13 11:26:36 +00:00
parser.add_argument(
"--font",
"-f",
metavar="FILE",
dest="font",
help="Input TTF files (used for glyph classes and sorting coverage tables)",
)
parser.add_argument(
"--table",
"-t",
metavar="TABLE",
dest="tableTag",
help="Table to fill (sniffed from input file if not provided)",
)
parser.add_argument(
"inputs", metavar="FILE", type=str, nargs="+", help="Input FontDame .txt files"
)
2022-12-13 11:26:36 +00:00
args = parser.parse_args(args)
2022-12-13 11:26:36 +00:00
if font is None:
if args.font:
font = ttLib.TTFont(args.font)
else:
font = MockFont()
2022-12-13 11:26:36 +00:00
for f in args.inputs:
log.debug("Processing %s", f)
with open(f, "rt", encoding="utf-8") as f:
table = build(f, font, tableTag=args.tableTag)
blob = table.compile(font) # Make sure it compiles
2015-12-07 12:12:11 +01:00
decompiled = table.__class__()
decompiled.decompile(blob, font) # Make sure it decompiles!
2022-12-13 11:26:36 +00:00
2015-12-09 17:03:12 +01:00
# continue
2015-12-07 12:12:11 +01:00
from fontTools.misc import xmlWriter
2022-12-13 11:26:36 +00:00
2015-12-07 12:12:11 +01:00
tag = table.tableTag
writer = xmlWriter.XMLWriter(sys.stdout)
writer.begintag(tag)
writer.newline()
# table.toXML(writer, font)
decompiled.toXML(writer, font)
2015-12-07 12:12:11 +01:00
writer.endtag(tag)
writer.newline()
2022-12-13 11:26:36 +00:00
2015-12-09 11:48:56 +01:00
if __name__ == "__main__":
import sys
2022-12-13 11:26:36 +00:00
sys.exit(main())