__init__.py 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402
  1. #!/usr/bin/python
  2. # FontDame-to-FontTools for OpenType Layout tables
  3. #
  4. # Source language spec is available at:
  5. # http://monotype.github.io/OpenType_Table_Source/otl_source.html
  6. # https://github.com/Monotype/OpenType_Table_Source/
  7. from fontTools import ttLib
  8. from fontTools.ttLib.tables._c_m_a_p import cmap_classes
  9. from fontTools.ttLib.tables import otTables as ot
  10. from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
  11. from fontTools.otlLib import builder as otl
  12. from contextlib import contextmanager
  13. from fontTools.ttLib import newTable
  14. from fontTools.feaLib.lookupDebugInfo import LOOKUP_DEBUG_ENV_VAR, LOOKUP_DEBUG_INFO_KEY
  15. from operator import setitem
  16. import os
  17. import logging
  18. class MtiLibError(Exception):
  19. pass
  20. class ReferenceNotFoundError(MtiLibError):
  21. pass
  22. class FeatureNotFoundError(ReferenceNotFoundError):
  23. pass
  24. class LookupNotFoundError(ReferenceNotFoundError):
  25. pass
  26. log = logging.getLogger("fontTools.mtiLib")
  27. def makeGlyph(s):
  28. if s[:2] in ["U ", "u "]:
  29. return ttLib.TTFont._makeGlyphName(int(s[2:], 16))
  30. elif s[:2] == "# ":
  31. return "glyph%.5d" % int(s[2:])
  32. assert s.find(" ") < 0, "Space found in glyph name: %s" % s
  33. assert s, "Glyph name is empty"
  34. return s
  35. def makeGlyphs(l):
  36. return [makeGlyph(g) for g in l]
  37. def mapLookup(sym, mapping):
  38. # Lookups are addressed by name. So resolved them using a map if available.
  39. # Fallback to parsing as lookup index if a map isn't provided.
  40. if mapping is not None:
  41. try:
  42. idx = mapping[sym]
  43. except KeyError:
  44. raise LookupNotFoundError(sym)
  45. else:
  46. idx = int(sym)
  47. return idx
  48. def mapFeature(sym, mapping):
  49. # Features are referenced by index according the spec. So, if symbol is an
  50. # integer, use it directly. Otherwise look up in the map if provided.
  51. try:
  52. idx = int(sym)
  53. except ValueError:
  54. try:
  55. idx = mapping[sym]
  56. except KeyError:
  57. raise FeatureNotFoundError(sym)
  58. return idx
  59. def setReference(mapper, mapping, sym, setter, collection, key):
  60. try:
  61. mapped = mapper(sym, mapping)
  62. except ReferenceNotFoundError as e:
  63. try:
  64. if mapping is not None:
  65. mapping.addDeferredMapping(
  66. lambda ref: setter(collection, key, ref), sym, e
  67. )
  68. return
  69. except AttributeError:
  70. pass
  71. raise
  72. setter(collection, key, mapped)
  73. class DeferredMapping(dict):
  74. def __init__(self):
  75. self._deferredMappings = []
  76. def addDeferredMapping(self, setter, sym, e):
  77. log.debug("Adding deferred mapping for symbol '%s' %s", sym, type(e).__name__)
  78. self._deferredMappings.append((setter, sym, e))
  79. def applyDeferredMappings(self):
  80. for setter, sym, e in self._deferredMappings:
  81. log.debug(
  82. "Applying deferred mapping for symbol '%s' %s", sym, type(e).__name__
  83. )
  84. try:
  85. mapped = self[sym]
  86. except KeyError:
  87. raise e
  88. setter(mapped)
  89. log.debug("Set to %s", mapped)
  90. self._deferredMappings = []
  91. def parseScriptList(lines, featureMap=None):
  92. self = ot.ScriptList()
  93. records = []
  94. with lines.between("script table"):
  95. for line in lines:
  96. while len(line) < 4:
  97. line.append("")
  98. scriptTag, langSysTag, defaultFeature, features = line
  99. log.debug("Adding script %s language-system %s", scriptTag, langSysTag)
  100. langSys = ot.LangSys()
  101. langSys.LookupOrder = None
  102. if defaultFeature:
  103. setReference(
  104. mapFeature,
  105. featureMap,
  106. defaultFeature,
  107. setattr,
  108. langSys,
  109. "ReqFeatureIndex",
  110. )
  111. else:
  112. langSys.ReqFeatureIndex = 0xFFFF
  113. syms = stripSplitComma(features)
  114. langSys.FeatureIndex = theList = [3] * len(syms)
  115. for i, sym in enumerate(syms):
  116. setReference(mapFeature, featureMap, sym, setitem, theList, i)
  117. langSys.FeatureCount = len(langSys.FeatureIndex)
  118. script = [s for s in records if s.ScriptTag == scriptTag]
  119. if script:
  120. script = script[0].Script
  121. else:
  122. scriptRec = ot.ScriptRecord()
  123. scriptRec.ScriptTag = scriptTag + " " * (4 - len(scriptTag))
  124. scriptRec.Script = ot.Script()
  125. records.append(scriptRec)
  126. script = scriptRec.Script
  127. script.DefaultLangSys = None
  128. script.LangSysRecord = []
  129. script.LangSysCount = 0
  130. if langSysTag == "default":
  131. script.DefaultLangSys = langSys
  132. else:
  133. langSysRec = ot.LangSysRecord()
  134. langSysRec.LangSysTag = langSysTag + " " * (4 - len(langSysTag))
  135. langSysRec.LangSys = langSys
  136. script.LangSysRecord.append(langSysRec)
  137. script.LangSysCount = len(script.LangSysRecord)
  138. for script in records:
  139. script.Script.LangSysRecord = sorted(
  140. script.Script.LangSysRecord, key=lambda rec: rec.LangSysTag
  141. )
  142. self.ScriptRecord = sorted(records, key=lambda rec: rec.ScriptTag)
  143. self.ScriptCount = len(self.ScriptRecord)
  144. return self
  145. def parseFeatureList(lines, lookupMap=None, featureMap=None):
  146. self = ot.FeatureList()
  147. self.FeatureRecord = []
  148. with lines.between("feature table"):
  149. for line in lines:
  150. name, featureTag, lookups = line
  151. if featureMap is not None:
  152. assert name not in featureMap, "Duplicate feature name: %s" % name
  153. featureMap[name] = len(self.FeatureRecord)
  154. # If feature name is integer, make sure it matches its index.
  155. try:
  156. assert int(name) == len(self.FeatureRecord), "%d %d" % (
  157. name,
  158. len(self.FeatureRecord),
  159. )
  160. except ValueError:
  161. pass
  162. featureRec = ot.FeatureRecord()
  163. featureRec.FeatureTag = featureTag
  164. featureRec.Feature = ot.Feature()
  165. self.FeatureRecord.append(featureRec)
  166. feature = featureRec.Feature
  167. feature.FeatureParams = None
  168. syms = stripSplitComma(lookups)
  169. feature.LookupListIndex = theList = [None] * len(syms)
  170. for i, sym in enumerate(syms):
  171. setReference(mapLookup, lookupMap, sym, setitem, theList, i)
  172. feature.LookupCount = len(feature.LookupListIndex)
  173. self.FeatureCount = len(self.FeatureRecord)
  174. return self
  175. def parseLookupFlags(lines):
  176. flags = 0
  177. filterset = None
  178. allFlags = [
  179. "righttoleft",
  180. "ignorebaseglyphs",
  181. "ignoreligatures",
  182. "ignoremarks",
  183. "markattachmenttype",
  184. "markfiltertype",
  185. ]
  186. while lines.peeks()[0].lower() in allFlags:
  187. line = next(lines)
  188. flag = {
  189. "righttoleft": 0x0001,
  190. "ignorebaseglyphs": 0x0002,
  191. "ignoreligatures": 0x0004,
  192. "ignoremarks": 0x0008,
  193. }.get(line[0].lower())
  194. if flag:
  195. assert line[1].lower() in ["yes", "no"], line[1]
  196. if line[1].lower() == "yes":
  197. flags |= flag
  198. continue
  199. if line[0].lower() == "markattachmenttype":
  200. flags |= int(line[1]) << 8
  201. continue
  202. if line[0].lower() == "markfiltertype":
  203. flags |= 0x10
  204. filterset = int(line[1])
  205. return flags, filterset
  206. def parseSingleSubst(lines, font, _lookupMap=None):
  207. mapping = {}
  208. for line in lines:
  209. assert len(line) == 2, line
  210. line = makeGlyphs(line)
  211. mapping[line[0]] = line[1]
  212. return otl.buildSingleSubstSubtable(mapping)
  213. def parseMultiple(lines, font, _lookupMap=None):
  214. mapping = {}
  215. for line in lines:
  216. line = makeGlyphs(line)
  217. mapping[line[0]] = line[1:]
  218. return otl.buildMultipleSubstSubtable(mapping)
  219. def parseAlternate(lines, font, _lookupMap=None):
  220. mapping = {}
  221. for line in lines:
  222. line = makeGlyphs(line)
  223. mapping[line[0]] = line[1:]
  224. return otl.buildAlternateSubstSubtable(mapping)
  225. def parseLigature(lines, font, _lookupMap=None):
  226. mapping = {}
  227. for line in lines:
  228. assert len(line) >= 2, line
  229. line = makeGlyphs(line)
  230. mapping[tuple(line[1:])] = line[0]
  231. return otl.buildLigatureSubstSubtable(mapping)
  232. def parseSinglePos(lines, font, _lookupMap=None):
  233. values = {}
  234. for line in lines:
  235. assert len(line) == 3, line
  236. w = line[0].title().replace(" ", "")
  237. assert w in valueRecordFormatDict
  238. g = makeGlyph(line[1])
  239. v = int(line[2])
  240. if g not in values:
  241. values[g] = ValueRecord()
  242. assert not hasattr(values[g], w), (g, w)
  243. setattr(values[g], w, v)
  244. return otl.buildSinglePosSubtable(values, font.getReverseGlyphMap())
  245. def parsePair(lines, font, _lookupMap=None):
  246. self = ot.PairPos()
  247. self.ValueFormat1 = self.ValueFormat2 = 0
  248. typ = lines.peeks()[0].split()[0].lower()
  249. if typ in ("left", "right"):
  250. self.Format = 1
  251. values = {}
  252. for line in lines:
  253. assert len(line) == 4, line
  254. side = line[0].split()[0].lower()
  255. assert side in ("left", "right"), side
  256. what = line[0][len(side) :].title().replace(" ", "")
  257. mask = valueRecordFormatDict[what][0]
  258. glyph1, glyph2 = makeGlyphs(line[1:3])
  259. value = int(line[3])
  260. if not glyph1 in values:
  261. values[glyph1] = {}
  262. if not glyph2 in values[glyph1]:
  263. values[glyph1][glyph2] = (ValueRecord(), ValueRecord())
  264. rec2 = values[glyph1][glyph2]
  265. if side == "left":
  266. self.ValueFormat1 |= mask
  267. vr = rec2[0]
  268. else:
  269. self.ValueFormat2 |= mask
  270. vr = rec2[1]
  271. assert not hasattr(vr, what), (vr, what)
  272. setattr(vr, what, value)
  273. self.Coverage = makeCoverage(set(values.keys()), font)
  274. self.PairSet = []
  275. for glyph1 in self.Coverage.glyphs:
  276. values1 = values[glyph1]
  277. pairset = ot.PairSet()
  278. records = pairset.PairValueRecord = []
  279. for glyph2 in sorted(values1.keys(), key=font.getGlyphID):
  280. values2 = values1[glyph2]
  281. pair = ot.PairValueRecord()
  282. pair.SecondGlyph = glyph2
  283. pair.Value1 = values2[0]
  284. pair.Value2 = values2[1] if self.ValueFormat2 else None
  285. records.append(pair)
  286. pairset.PairValueCount = len(pairset.PairValueRecord)
  287. self.PairSet.append(pairset)
  288. self.PairSetCount = len(self.PairSet)
  289. elif typ.endswith("class"):
  290. self.Format = 2
  291. classDefs = [None, None]
  292. while lines.peeks()[0].endswith("class definition begin"):
  293. typ = lines.peek()[0][: -len("class definition begin")].lower()
  294. idx, klass = {
  295. "first": (0, ot.ClassDef1),
  296. "second": (1, ot.ClassDef2),
  297. }[typ]
  298. assert classDefs[idx] is None
  299. classDefs[idx] = parseClassDef(lines, font, klass=klass)
  300. self.ClassDef1, self.ClassDef2 = classDefs
  301. self.Class1Count, self.Class2Count = (
  302. 1 + max(c.classDefs.values()) for c in classDefs
  303. )
  304. self.Class1Record = [ot.Class1Record() for i in range(self.Class1Count)]
  305. for rec1 in self.Class1Record:
  306. rec1.Class2Record = [ot.Class2Record() for j in range(self.Class2Count)]
  307. for rec2 in rec1.Class2Record:
  308. rec2.Value1 = ValueRecord()
  309. rec2.Value2 = ValueRecord()
  310. for line in lines:
  311. assert len(line) == 4, line
  312. side = line[0].split()[0].lower()
  313. assert side in ("left", "right"), side
  314. what = line[0][len(side) :].title().replace(" ", "")
  315. mask = valueRecordFormatDict[what][0]
  316. class1, class2, value = (int(x) for x in line[1:4])
  317. rec2 = self.Class1Record[class1].Class2Record[class2]
  318. if side == "left":
  319. self.ValueFormat1 |= mask
  320. vr = rec2.Value1
  321. else:
  322. self.ValueFormat2 |= mask
  323. vr = rec2.Value2
  324. assert not hasattr(vr, what), (vr, what)
  325. setattr(vr, what, value)
  326. for rec1 in self.Class1Record:
  327. for rec2 in rec1.Class2Record:
  328. rec2.Value1 = ValueRecord(self.ValueFormat1, rec2.Value1)
  329. rec2.Value2 = (
  330. ValueRecord(self.ValueFormat2, rec2.Value2)
  331. if self.ValueFormat2
  332. else None
  333. )
  334. self.Coverage = makeCoverage(set(self.ClassDef1.classDefs.keys()), font)
  335. else:
  336. assert 0, typ
  337. return self
  338. def parseKernset(lines, font, _lookupMap=None):
  339. typ = lines.peeks()[0].split()[0].lower()
  340. if typ in ("left", "right"):
  341. with lines.until(
  342. ("firstclass definition begin", "secondclass definition begin")
  343. ):
  344. return parsePair(lines, font)
  345. return parsePair(lines, font)
  346. def makeAnchor(data, klass=ot.Anchor):
  347. assert len(data) <= 2
  348. anchor = klass()
  349. anchor.Format = 1
  350. anchor.XCoordinate, anchor.YCoordinate = intSplitComma(data[0])
  351. if len(data) > 1 and data[1] != "":
  352. anchor.Format = 2
  353. anchor.AnchorPoint = int(data[1])
  354. return anchor
  355. def parseCursive(lines, font, _lookupMap=None):
  356. records = {}
  357. for line in lines:
  358. assert len(line) in [3, 4], line
  359. idx, klass = {
  360. "entry": (0, ot.EntryAnchor),
  361. "exit": (1, ot.ExitAnchor),
  362. }[line[0]]
  363. glyph = makeGlyph(line[1])
  364. if glyph not in records:
  365. records[glyph] = [None, None]
  366. assert records[glyph][idx] is None, (glyph, idx)
  367. records[glyph][idx] = makeAnchor(line[2:], klass)
  368. return otl.buildCursivePosSubtable(records, font.getReverseGlyphMap())
  369. def makeMarkRecords(data, coverage, c):
  370. records = []
  371. for glyph in coverage.glyphs:
  372. klass, anchor = data[glyph]
  373. record = c.MarkRecordClass()
  374. record.Class = klass
  375. setattr(record, c.MarkAnchor, anchor)
  376. records.append(record)
  377. return records
  378. def makeBaseRecords(data, coverage, c, classCount):
  379. records = []
  380. idx = {}
  381. for glyph in coverage.glyphs:
  382. idx[glyph] = len(records)
  383. record = c.BaseRecordClass()
  384. anchors = [None] * classCount
  385. setattr(record, c.BaseAnchor, anchors)
  386. records.append(record)
  387. for (glyph, klass), anchor in data.items():
  388. record = records[idx[glyph]]
  389. anchors = getattr(record, c.BaseAnchor)
  390. assert anchors[klass] is None, (glyph, klass)
  391. anchors[klass] = anchor
  392. return records
  393. def makeLigatureRecords(data, coverage, c, classCount):
  394. records = [None] * len(coverage.glyphs)
  395. idx = {g: i for i, g in enumerate(coverage.glyphs)}
  396. for (glyph, klass, compIdx, compCount), anchor in data.items():
  397. record = records[idx[glyph]]
  398. if record is None:
  399. record = records[idx[glyph]] = ot.LigatureAttach()
  400. record.ComponentCount = compCount
  401. record.ComponentRecord = [ot.ComponentRecord() for i in range(compCount)]
  402. for compRec in record.ComponentRecord:
  403. compRec.LigatureAnchor = [None] * classCount
  404. assert record.ComponentCount == compCount, (
  405. glyph,
  406. record.ComponentCount,
  407. compCount,
  408. )
  409. anchors = record.ComponentRecord[compIdx - 1].LigatureAnchor
  410. assert anchors[klass] is None, (glyph, compIdx, klass)
  411. anchors[klass] = anchor
  412. return records
  413. def parseMarkToSomething(lines, font, c):
  414. self = c.Type()
  415. self.Format = 1
  416. markData = {}
  417. baseData = {}
  418. Data = {
  419. "mark": (markData, c.MarkAnchorClass),
  420. "base": (baseData, c.BaseAnchorClass),
  421. "ligature": (baseData, c.BaseAnchorClass),
  422. }
  423. maxKlass = 0
  424. for line in lines:
  425. typ = line[0]
  426. assert typ in ("mark", "base", "ligature")
  427. glyph = makeGlyph(line[1])
  428. data, anchorClass = Data[typ]
  429. extraItems = 2 if typ == "ligature" else 0
  430. extras = tuple(int(i) for i in line[2 : 2 + extraItems])
  431. klass = int(line[2 + extraItems])
  432. anchor = makeAnchor(line[3 + extraItems :], anchorClass)
  433. if typ == "mark":
  434. key, value = glyph, (klass, anchor)
  435. else:
  436. key, value = ((glyph, klass) + extras), anchor
  437. assert key not in data, key
  438. data[key] = value
  439. maxKlass = max(maxKlass, klass)
  440. # Mark
  441. markCoverage = makeCoverage(set(markData.keys()), font, c.MarkCoverageClass)
  442. markArray = c.MarkArrayClass()
  443. markRecords = makeMarkRecords(markData, markCoverage, c)
  444. setattr(markArray, c.MarkRecord, markRecords)
  445. setattr(markArray, c.MarkCount, len(markRecords))
  446. setattr(self, c.MarkCoverage, markCoverage)
  447. setattr(self, c.MarkArray, markArray)
  448. self.ClassCount = maxKlass + 1
  449. # Base
  450. self.classCount = 0 if not baseData else 1 + max(k[1] for k, v in baseData.items())
  451. baseCoverage = makeCoverage(
  452. set([k[0] for k in baseData.keys()]), font, c.BaseCoverageClass
  453. )
  454. baseArray = c.BaseArrayClass()
  455. if c.Base == "Ligature":
  456. baseRecords = makeLigatureRecords(baseData, baseCoverage, c, self.classCount)
  457. else:
  458. baseRecords = makeBaseRecords(baseData, baseCoverage, c, self.classCount)
  459. setattr(baseArray, c.BaseRecord, baseRecords)
  460. setattr(baseArray, c.BaseCount, len(baseRecords))
  461. setattr(self, c.BaseCoverage, baseCoverage)
  462. setattr(self, c.BaseArray, baseArray)
  463. return self
  464. class MarkHelper(object):
  465. def __init__(self):
  466. for Which in ("Mark", "Base"):
  467. for What in ("Coverage", "Array", "Count", "Record", "Anchor"):
  468. key = Which + What
  469. if Which == "Mark" and What in ("Count", "Record", "Anchor"):
  470. value = key
  471. else:
  472. value = getattr(self, Which) + What
  473. if value == "LigatureRecord":
  474. value = "LigatureAttach"
  475. setattr(self, key, value)
  476. if What != "Count":
  477. klass = getattr(ot, value)
  478. setattr(self, key + "Class", klass)
  479. class MarkToBaseHelper(MarkHelper):
  480. Mark = "Mark"
  481. Base = "Base"
  482. Type = ot.MarkBasePos
  483. class MarkToMarkHelper(MarkHelper):
  484. Mark = "Mark1"
  485. Base = "Mark2"
  486. Type = ot.MarkMarkPos
  487. class MarkToLigatureHelper(MarkHelper):
  488. Mark = "Mark"
  489. Base = "Ligature"
  490. Type = ot.MarkLigPos
  491. def parseMarkToBase(lines, font, _lookupMap=None):
  492. return parseMarkToSomething(lines, font, MarkToBaseHelper())
  493. def parseMarkToMark(lines, font, _lookupMap=None):
  494. return parseMarkToSomething(lines, font, MarkToMarkHelper())
  495. def parseMarkToLigature(lines, font, _lookupMap=None):
  496. return parseMarkToSomething(lines, font, MarkToLigatureHelper())
  497. def stripSplitComma(line):
  498. return [s.strip() for s in line.split(",")] if line else []
  499. def intSplitComma(line):
  500. return [int(i) for i in line.split(",")] if line else []
  501. # Copied from fontTools.subset
  502. class ContextHelper(object):
  503. def __init__(self, klassName, Format):
  504. if klassName.endswith("Subst"):
  505. Typ = "Sub"
  506. Type = "Subst"
  507. else:
  508. Typ = "Pos"
  509. Type = "Pos"
  510. if klassName.startswith("Chain"):
  511. Chain = "Chain"
  512. InputIdx = 1
  513. DataLen = 3
  514. else:
  515. Chain = ""
  516. InputIdx = 0
  517. DataLen = 1
  518. ChainTyp = Chain + Typ
  519. self.Typ = Typ
  520. self.Type = Type
  521. self.Chain = Chain
  522. self.ChainTyp = ChainTyp
  523. self.InputIdx = InputIdx
  524. self.DataLen = DataLen
  525. self.LookupRecord = Type + "LookupRecord"
  526. if Format == 1:
  527. Coverage = lambda r: r.Coverage
  528. ChainCoverage = lambda r: r.Coverage
  529. ContextData = lambda r: (None,)
  530. ChainContextData = lambda r: (None, None, None)
  531. SetContextData = None
  532. SetChainContextData = None
  533. RuleData = lambda r: (r.Input,)
  534. ChainRuleData = lambda r: (r.Backtrack, r.Input, r.LookAhead)
  535. def SetRuleData(r, d):
  536. (r.Input,) = d
  537. (r.GlyphCount,) = (len(x) + 1 for x in d)
  538. def ChainSetRuleData(r, d):
  539. (r.Backtrack, r.Input, r.LookAhead) = d
  540. (
  541. r.BacktrackGlyphCount,
  542. r.InputGlyphCount,
  543. r.LookAheadGlyphCount,
  544. ) = (len(d[0]), len(d[1]) + 1, len(d[2]))
  545. elif Format == 2:
  546. Coverage = lambda r: r.Coverage
  547. ChainCoverage = lambda r: r.Coverage
  548. ContextData = lambda r: (r.ClassDef,)
  549. ChainContextData = lambda r: (
  550. r.BacktrackClassDef,
  551. r.InputClassDef,
  552. r.LookAheadClassDef,
  553. )
  554. def SetContextData(r, d):
  555. (r.ClassDef,) = d
  556. def SetChainContextData(r, d):
  557. (r.BacktrackClassDef, r.InputClassDef, r.LookAheadClassDef) = d
  558. RuleData = lambda r: (r.Class,)
  559. ChainRuleData = lambda r: (r.Backtrack, r.Input, r.LookAhead)
  560. def SetRuleData(r, d):
  561. (r.Class,) = d
  562. (r.GlyphCount,) = (len(x) + 1 for x in d)
  563. def ChainSetRuleData(r, d):
  564. (r.Backtrack, r.Input, r.LookAhead) = d
  565. (
  566. r.BacktrackGlyphCount,
  567. r.InputGlyphCount,
  568. r.LookAheadGlyphCount,
  569. ) = (len(d[0]), len(d[1]) + 1, len(d[2]))
  570. elif Format == 3:
  571. Coverage = lambda r: r.Coverage[0]
  572. ChainCoverage = lambda r: r.InputCoverage[0]
  573. ContextData = None
  574. ChainContextData = None
  575. SetContextData = None
  576. SetChainContextData = None
  577. RuleData = lambda r: r.Coverage
  578. ChainRuleData = lambda r: (
  579. r.BacktrackCoverage + r.InputCoverage + r.LookAheadCoverage
  580. )
  581. def SetRuleData(r, d):
  582. (r.Coverage,) = d
  583. (r.GlyphCount,) = (len(x) for x in d)
  584. def ChainSetRuleData(r, d):
  585. (r.BacktrackCoverage, r.InputCoverage, r.LookAheadCoverage) = d
  586. (
  587. r.BacktrackGlyphCount,
  588. r.InputGlyphCount,
  589. r.LookAheadGlyphCount,
  590. ) = (len(x) for x in d)
  591. else:
  592. assert 0, "unknown format: %s" % Format
  593. if Chain:
  594. self.Coverage = ChainCoverage
  595. self.ContextData = ChainContextData
  596. self.SetContextData = SetChainContextData
  597. self.RuleData = ChainRuleData
  598. self.SetRuleData = ChainSetRuleData
  599. else:
  600. self.Coverage = Coverage
  601. self.ContextData = ContextData
  602. self.SetContextData = SetContextData
  603. self.RuleData = RuleData
  604. self.SetRuleData = SetRuleData
  605. if Format == 1:
  606. self.Rule = ChainTyp + "Rule"
  607. self.RuleCount = ChainTyp + "RuleCount"
  608. self.RuleSet = ChainTyp + "RuleSet"
  609. self.RuleSetCount = ChainTyp + "RuleSetCount"
  610. self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
  611. elif Format == 2:
  612. self.Rule = ChainTyp + "ClassRule"
  613. self.RuleCount = ChainTyp + "ClassRuleCount"
  614. self.RuleSet = ChainTyp + "ClassSet"
  615. self.RuleSetCount = ChainTyp + "ClassSetCount"
  616. self.Intersect = lambda glyphs, c, r: (
  617. c.intersect_class(glyphs, r)
  618. if c
  619. else (set(glyphs) if r == 0 else set())
  620. )
  621. self.ClassDef = "InputClassDef" if Chain else "ClassDef"
  622. self.ClassDefIndex = 1 if Chain else 0
  623. self.Input = "Input" if Chain else "Class"
  624. def parseLookupRecords(items, klassName, lookupMap=None):
  625. klass = getattr(ot, klassName)
  626. lst = []
  627. for item in items:
  628. rec = klass()
  629. item = stripSplitComma(item)
  630. assert len(item) == 2, item
  631. idx = int(item[0])
  632. assert idx > 0, idx
  633. rec.SequenceIndex = idx - 1
  634. setReference(mapLookup, lookupMap, item[1], setattr, rec, "LookupListIndex")
  635. lst.append(rec)
  636. return lst
  637. def makeClassDef(classDefs, font, klass=ot.Coverage):
  638. if not classDefs:
  639. return None
  640. self = klass()
  641. self.classDefs = dict(classDefs)
  642. return self
  643. def parseClassDef(lines, font, klass=ot.ClassDef):
  644. classDefs = {}
  645. with lines.between("class definition"):
  646. for line in lines:
  647. glyph = makeGlyph(line[0])
  648. assert glyph not in classDefs, glyph
  649. classDefs[glyph] = int(line[1])
  650. return makeClassDef(classDefs, font, klass)
  651. def makeCoverage(glyphs, font, klass=ot.Coverage):
  652. if not glyphs:
  653. return None
  654. if isinstance(glyphs, set):
  655. glyphs = sorted(glyphs)
  656. coverage = klass()
  657. coverage.glyphs = sorted(set(glyphs), key=font.getGlyphID)
  658. return coverage
  659. def parseCoverage(lines, font, klass=ot.Coverage):
  660. glyphs = []
  661. with lines.between("coverage definition"):
  662. for line in lines:
  663. glyphs.append(makeGlyph(line[0]))
  664. return makeCoverage(glyphs, font, klass)
  665. def bucketizeRules(self, c, rules, bucketKeys):
  666. buckets = {}
  667. for seq, recs in rules:
  668. buckets.setdefault(seq[c.InputIdx][0], []).append(
  669. (tuple(s[1 if i == c.InputIdx else 0 :] for i, s in enumerate(seq)), recs)
  670. )
  671. rulesets = []
  672. for firstGlyph in bucketKeys:
  673. if firstGlyph not in buckets:
  674. rulesets.append(None)
  675. continue
  676. thisRules = []
  677. for seq, recs in buckets[firstGlyph]:
  678. rule = getattr(ot, c.Rule)()
  679. c.SetRuleData(rule, seq)
  680. setattr(rule, c.Type + "Count", len(recs))
  681. setattr(rule, c.LookupRecord, recs)
  682. thisRules.append(rule)
  683. ruleset = getattr(ot, c.RuleSet)()
  684. setattr(ruleset, c.Rule, thisRules)
  685. setattr(ruleset, c.RuleCount, len(thisRules))
  686. rulesets.append(ruleset)
  687. setattr(self, c.RuleSet, rulesets)
  688. setattr(self, c.RuleSetCount, len(rulesets))
  689. def parseContext(lines, font, Type, lookupMap=None):
  690. self = getattr(ot, Type)()
  691. typ = lines.peeks()[0].split()[0].lower()
  692. if typ == "glyph":
  693. self.Format = 1
  694. log.debug("Parsing %s format %s", Type, self.Format)
  695. c = ContextHelper(Type, self.Format)
  696. rules = []
  697. for line in lines:
  698. assert line[0].lower() == "glyph", line[0]
  699. while len(line) < 1 + c.DataLen:
  700. line.append("")
  701. seq = tuple(makeGlyphs(stripSplitComma(i)) for i in line[1 : 1 + c.DataLen])
  702. recs = parseLookupRecords(line[1 + c.DataLen :], c.LookupRecord, lookupMap)
  703. rules.append((seq, recs))
  704. firstGlyphs = set(seq[c.InputIdx][0] for seq, recs in rules)
  705. self.Coverage = makeCoverage(firstGlyphs, font)
  706. bucketizeRules(self, c, rules, self.Coverage.glyphs)
  707. elif typ.endswith("class"):
  708. self.Format = 2
  709. log.debug("Parsing %s format %s", Type, self.Format)
  710. c = ContextHelper(Type, self.Format)
  711. classDefs = [None] * c.DataLen
  712. while lines.peeks()[0].endswith("class definition begin"):
  713. typ = lines.peek()[0][: -len("class definition begin")].lower()
  714. idx, klass = {
  715. 1: {
  716. "": (0, ot.ClassDef),
  717. },
  718. 3: {
  719. "backtrack": (0, ot.BacktrackClassDef),
  720. "": (1, ot.InputClassDef),
  721. "lookahead": (2, ot.LookAheadClassDef),
  722. },
  723. }[c.DataLen][typ]
  724. assert classDefs[idx] is None, idx
  725. classDefs[idx] = parseClassDef(lines, font, klass=klass)
  726. c.SetContextData(self, classDefs)
  727. rules = []
  728. for line in lines:
  729. assert line[0].lower().startswith("class"), line[0]
  730. while len(line) < 1 + c.DataLen:
  731. line.append("")
  732. seq = tuple(intSplitComma(i) for i in line[1 : 1 + c.DataLen])
  733. recs = parseLookupRecords(line[1 + c.DataLen :], c.LookupRecord, lookupMap)
  734. rules.append((seq, recs))
  735. firstClasses = set(seq[c.InputIdx][0] for seq, recs in rules)
  736. firstGlyphs = set(
  737. g for g, c in classDefs[c.InputIdx].classDefs.items() if c in firstClasses
  738. )
  739. self.Coverage = makeCoverage(firstGlyphs, font)
  740. bucketizeRules(self, c, rules, range(max(firstClasses) + 1))
  741. elif typ.endswith("coverage"):
  742. self.Format = 3
  743. log.debug("Parsing %s format %s", Type, self.Format)
  744. c = ContextHelper(Type, self.Format)
  745. coverages = tuple([] for i in range(c.DataLen))
  746. while lines.peeks()[0].endswith("coverage definition begin"):
  747. typ = lines.peek()[0][: -len("coverage definition begin")].lower()
  748. idx, klass = {
  749. 1: {
  750. "": (0, ot.Coverage),
  751. },
  752. 3: {
  753. "backtrack": (0, ot.BacktrackCoverage),
  754. "input": (1, ot.InputCoverage),
  755. "lookahead": (2, ot.LookAheadCoverage),
  756. },
  757. }[c.DataLen][typ]
  758. coverages[idx].append(parseCoverage(lines, font, klass=klass))
  759. c.SetRuleData(self, coverages)
  760. lines = list(lines)
  761. assert len(lines) == 1
  762. line = lines[0]
  763. assert line[0].lower() == "coverage", line[0]
  764. recs = parseLookupRecords(line[1:], c.LookupRecord, lookupMap)
  765. setattr(self, c.Type + "Count", len(recs))
  766. setattr(self, c.LookupRecord, recs)
  767. else:
  768. assert 0, typ
  769. return self
  770. def parseContextSubst(lines, font, lookupMap=None):
  771. return parseContext(lines, font, "ContextSubst", lookupMap=lookupMap)
  772. def parseContextPos(lines, font, lookupMap=None):
  773. return parseContext(lines, font, "ContextPos", lookupMap=lookupMap)
  774. def parseChainedSubst(lines, font, lookupMap=None):
  775. return parseContext(lines, font, "ChainContextSubst", lookupMap=lookupMap)
  776. def parseChainedPos(lines, font, lookupMap=None):
  777. return parseContext(lines, font, "ChainContextPos", lookupMap=lookupMap)
  778. def parseReverseChainedSubst(lines, font, _lookupMap=None):
  779. self = ot.ReverseChainSingleSubst()
  780. self.Format = 1
  781. coverages = ([], [])
  782. while lines.peeks()[0].endswith("coverage definition begin"):
  783. typ = lines.peek()[0][: -len("coverage definition begin")].lower()
  784. idx, klass = {
  785. "backtrack": (0, ot.BacktrackCoverage),
  786. "lookahead": (1, ot.LookAheadCoverage),
  787. }[typ]
  788. coverages[idx].append(parseCoverage(lines, font, klass=klass))
  789. self.BacktrackCoverage = coverages[0]
  790. self.BacktrackGlyphCount = len(self.BacktrackCoverage)
  791. self.LookAheadCoverage = coverages[1]
  792. self.LookAheadGlyphCount = len(self.LookAheadCoverage)
  793. mapping = {}
  794. for line in lines:
  795. assert len(line) == 2, line
  796. line = makeGlyphs(line)
  797. mapping[line[0]] = line[1]
  798. self.Coverage = makeCoverage(set(mapping.keys()), font)
  799. self.Substitute = [mapping[k] for k in self.Coverage.glyphs]
  800. self.GlyphCount = len(self.Substitute)
  801. return self
  802. def parseLookup(lines, tableTag, font, lookupMap=None):
  803. line = lines.expect("lookup")
  804. _, name, typ = line
  805. log.debug("Parsing lookup type %s %s", typ, name)
  806. lookup = ot.Lookup()
  807. lookup.LookupFlag, filterset = parseLookupFlags(lines)
  808. if filterset is not None:
  809. lookup.MarkFilteringSet = filterset
  810. lookup.LookupType, parseLookupSubTable = {
  811. "GSUB": {
  812. "single": (1, parseSingleSubst),
  813. "multiple": (2, parseMultiple),
  814. "alternate": (3, parseAlternate),
  815. "ligature": (4, parseLigature),
  816. "context": (5, parseContextSubst),
  817. "chained": (6, parseChainedSubst),
  818. "reversechained": (8, parseReverseChainedSubst),
  819. },
  820. "GPOS": {
  821. "single": (1, parseSinglePos),
  822. "pair": (2, parsePair),
  823. "kernset": (2, parseKernset),
  824. "cursive": (3, parseCursive),
  825. "mark to base": (4, parseMarkToBase),
  826. "mark to ligature": (5, parseMarkToLigature),
  827. "mark to mark": (6, parseMarkToMark),
  828. "context": (7, parseContextPos),
  829. "chained": (8, parseChainedPos),
  830. },
  831. }[tableTag][typ]
  832. with lines.until("lookup end"):
  833. subtables = []
  834. while lines.peek():
  835. with lines.until(("% subtable", "subtable end")):
  836. while lines.peek():
  837. subtable = parseLookupSubTable(lines, font, lookupMap)
  838. assert lookup.LookupType == subtable.LookupType
  839. subtables.append(subtable)
  840. if lines.peeks()[0] in ("% subtable", "subtable end"):
  841. next(lines)
  842. lines.expect("lookup end")
  843. lookup.SubTable = subtables
  844. lookup.SubTableCount = len(lookup.SubTable)
  845. if lookup.SubTableCount == 0:
  846. # Remove this return when following is fixed:
  847. # https://github.com/fonttools/fonttools/issues/789
  848. return None
  849. return lookup
  850. def parseGSUBGPOS(lines, font, tableTag):
  851. container = ttLib.getTableClass(tableTag)()
  852. lookupMap = DeferredMapping()
  853. featureMap = DeferredMapping()
  854. assert tableTag in ("GSUB", "GPOS")
  855. log.debug("Parsing %s", tableTag)
  856. self = getattr(ot, tableTag)()
  857. self.Version = 0x00010000
  858. fields = {
  859. "script table begin": (
  860. "ScriptList",
  861. lambda lines: parseScriptList(lines, featureMap),
  862. ),
  863. "feature table begin": (
  864. "FeatureList",
  865. lambda lines: parseFeatureList(lines, lookupMap, featureMap),
  866. ),
  867. "lookup": ("LookupList", None),
  868. }
  869. for attr, parser in fields.values():
  870. setattr(self, attr, None)
  871. while lines.peek() is not None:
  872. typ = lines.peek()[0].lower()
  873. if typ not in fields:
  874. log.debug("Skipping %s", lines.peek())
  875. next(lines)
  876. continue
  877. attr, parser = fields[typ]
  878. if typ == "lookup":
  879. if self.LookupList is None:
  880. self.LookupList = ot.LookupList()
  881. self.LookupList.Lookup = []
  882. _, name, _ = lines.peek()
  883. lookup = parseLookup(lines, tableTag, font, lookupMap)
  884. if lookupMap is not None:
  885. assert name not in lookupMap, "Duplicate lookup name: %s" % name
  886. lookupMap[name] = len(self.LookupList.Lookup)
  887. else:
  888. assert int(name) == len(self.LookupList.Lookup), "%d %d" % (
  889. name,
  890. len(self.Lookup),
  891. )
  892. self.LookupList.Lookup.append(lookup)
  893. else:
  894. assert getattr(self, attr) is None, attr
  895. setattr(self, attr, parser(lines))
  896. if self.LookupList:
  897. self.LookupList.LookupCount = len(self.LookupList.Lookup)
  898. if lookupMap is not None:
  899. lookupMap.applyDeferredMappings()
  900. if os.environ.get(LOOKUP_DEBUG_ENV_VAR):
  901. if "Debg" not in font:
  902. font["Debg"] = newTable("Debg")
  903. font["Debg"].data = {}
  904. debug = (
  905. font["Debg"]
  906. .data.setdefault(LOOKUP_DEBUG_INFO_KEY, {})
  907. .setdefault(tableTag, {})
  908. )
  909. for name, lookup in lookupMap.items():
  910. debug[str(lookup)] = ["", name, ""]
  911. featureMap.applyDeferredMappings()
  912. container.table = self
  913. return container
  914. def parseGSUB(lines, font):
  915. return parseGSUBGPOS(lines, font, "GSUB")
  916. def parseGPOS(lines, font):
  917. return parseGSUBGPOS(lines, font, "GPOS")
  918. def parseAttachList(lines, font):
  919. points = {}
  920. with lines.between("attachment list"):
  921. for line in lines:
  922. glyph = makeGlyph(line[0])
  923. assert glyph not in points, glyph
  924. points[glyph] = [int(i) for i in line[1:]]
  925. return otl.buildAttachList(points, font.getReverseGlyphMap())
  926. def parseCaretList(lines, font):
  927. carets = {}
  928. with lines.between("carets"):
  929. for line in lines:
  930. glyph = makeGlyph(line[0])
  931. assert glyph not in carets, glyph
  932. num = int(line[1])
  933. thisCarets = [int(i) for i in line[2:]]
  934. assert num == len(thisCarets), line
  935. carets[glyph] = thisCarets
  936. return otl.buildLigCaretList(carets, {}, font.getReverseGlyphMap())
  937. def makeMarkFilteringSets(sets, font):
  938. self = ot.MarkGlyphSetsDef()
  939. self.MarkSetTableFormat = 1
  940. self.MarkSetCount = 1 + max(sets.keys())
  941. self.Coverage = [None] * self.MarkSetCount
  942. for k, v in sorted(sets.items()):
  943. self.Coverage[k] = makeCoverage(set(v), font)
  944. return self
  945. def parseMarkFilteringSets(lines, font):
  946. sets = {}
  947. with lines.between("set definition"):
  948. for line in lines:
  949. assert len(line) == 2, line
  950. glyph = makeGlyph(line[0])
  951. # TODO accept set names
  952. st = int(line[1])
  953. if st not in sets:
  954. sets[st] = []
  955. sets[st].append(glyph)
  956. return makeMarkFilteringSets(sets, font)
  957. def parseGDEF(lines, font):
  958. container = ttLib.getTableClass("GDEF")()
  959. log.debug("Parsing GDEF")
  960. self = ot.GDEF()
  961. fields = {
  962. "class definition begin": (
  963. "GlyphClassDef",
  964. lambda lines, font: parseClassDef(lines, font, klass=ot.GlyphClassDef),
  965. ),
  966. "attachment list begin": ("AttachList", parseAttachList),
  967. "carets begin": ("LigCaretList", parseCaretList),
  968. "mark attachment class definition begin": (
  969. "MarkAttachClassDef",
  970. lambda lines, font: parseClassDef(lines, font, klass=ot.MarkAttachClassDef),
  971. ),
  972. "markfilter set definition begin": ("MarkGlyphSetsDef", parseMarkFilteringSets),
  973. }
  974. for attr, parser in fields.values():
  975. setattr(self, attr, None)
  976. while lines.peek() is not None:
  977. typ = lines.peek()[0].lower()
  978. if typ not in fields:
  979. log.debug("Skipping %s", typ)
  980. next(lines)
  981. continue
  982. attr, parser = fields[typ]
  983. assert getattr(self, attr) is None, attr
  984. setattr(self, attr, parser(lines, font))
  985. self.Version = 0x00010000 if self.MarkGlyphSetsDef is None else 0x00010002
  986. container.table = self
  987. return container
  988. def parseCmap(lines, font):
  989. container = ttLib.getTableClass("cmap")()
  990. log.debug("Parsing cmap")
  991. tables = []
  992. while lines.peek() is not None:
  993. lines.expect("cmap subtable %d" % len(tables))
  994. platId, encId, fmt, lang = [
  995. parseCmapId(lines, field)
  996. for field in ("platformID", "encodingID", "format", "language")
  997. ]
  998. table = cmap_classes[fmt](fmt)
  999. table.platformID = platId
  1000. table.platEncID = encId
  1001. table.language = lang
  1002. table.cmap = {}
  1003. line = next(lines)
  1004. while line[0] != "end subtable":
  1005. table.cmap[int(line[0], 16)] = line[1]
  1006. line = next(lines)
  1007. tables.append(table)
  1008. container.tableVersion = 0
  1009. container.tables = tables
  1010. return container
  1011. def parseCmapId(lines, field):
  1012. line = next(lines)
  1013. assert field == line[0]
  1014. return int(line[1])
  1015. def parseTable(lines, font, tableTag=None):
  1016. log.debug("Parsing table")
  1017. line = lines.peeks()
  1018. tag = None
  1019. if line[0].split()[0] == "FontDame":
  1020. tag = line[0].split()[1]
  1021. elif " ".join(line[0].split()[:3]) == "Font Chef Table":
  1022. tag = line[0].split()[3]
  1023. if tag is not None:
  1024. next(lines)
  1025. tag = tag.ljust(4)
  1026. if tableTag is None:
  1027. tableTag = tag
  1028. else:
  1029. assert tableTag == tag, (tableTag, tag)
  1030. assert (
  1031. tableTag is not None
  1032. ), "Don't know what table to parse and data doesn't specify"
  1033. return {
  1034. "GSUB": parseGSUB,
  1035. "GPOS": parseGPOS,
  1036. "GDEF": parseGDEF,
  1037. "cmap": parseCmap,
  1038. }[tableTag](lines, font)
  1039. class Tokenizer(object):
  1040. def __init__(self, f):
  1041. # TODO BytesIO / StringIO as needed? also, figure out whether we work on bytes or unicode
  1042. lines = iter(f)
  1043. try:
  1044. self.filename = f.name
  1045. except:
  1046. self.filename = None
  1047. self.lines = iter(lines)
  1048. self.line = ""
  1049. self.lineno = 0
  1050. self.stoppers = []
  1051. self.buffer = None
  1052. def __iter__(self):
  1053. return self
  1054. def _next_line(self):
  1055. self.lineno += 1
  1056. line = self.line = next(self.lines)
  1057. line = [s.strip() for s in line.split("\t")]
  1058. if len(line) == 1 and not line[0]:
  1059. del line[0]
  1060. if line and not line[-1]:
  1061. log.warning("trailing tab found on line %d: %s" % (self.lineno, self.line))
  1062. while line and not line[-1]:
  1063. del line[-1]
  1064. return line
  1065. def _next_nonempty(self):
  1066. while True:
  1067. line = self._next_line()
  1068. # Skip comments and empty lines
  1069. if line and line[0] and (line[0][0] != "%" or line[0] == "% subtable"):
  1070. return line
  1071. def _next_buffered(self):
  1072. if self.buffer:
  1073. ret = self.buffer
  1074. self.buffer = None
  1075. return ret
  1076. else:
  1077. return self._next_nonempty()
  1078. def __next__(self):
  1079. line = self._next_buffered()
  1080. if line[0].lower() in self.stoppers:
  1081. self.buffer = line
  1082. raise StopIteration
  1083. return line
  1084. def next(self):
  1085. return self.__next__()
  1086. def peek(self):
  1087. if not self.buffer:
  1088. try:
  1089. self.buffer = self._next_nonempty()
  1090. except StopIteration:
  1091. return None
  1092. if self.buffer[0].lower() in self.stoppers:
  1093. return None
  1094. return self.buffer
  1095. def peeks(self):
  1096. ret = self.peek()
  1097. return ret if ret is not None else ("",)
  1098. @contextmanager
  1099. def between(self, tag):
  1100. start = tag + " begin"
  1101. end = tag + " end"
  1102. self.expectendswith(start)
  1103. self.stoppers.append(end)
  1104. yield
  1105. del self.stoppers[-1]
  1106. self.expect(tag + " end")
  1107. @contextmanager
  1108. def until(self, tags):
  1109. if type(tags) is not tuple:
  1110. tags = (tags,)
  1111. self.stoppers.extend(tags)
  1112. yield
  1113. del self.stoppers[-len(tags) :]
  1114. def expect(self, s):
  1115. line = next(self)
  1116. tag = line[0].lower()
  1117. assert tag == s, "Expected '%s', got '%s'" % (s, tag)
  1118. return line
  1119. def expectendswith(self, s):
  1120. line = next(self)
  1121. tag = line[0].lower()
  1122. assert tag.endswith(s), "Expected '*%s', got '%s'" % (s, tag)
  1123. return line
  1124. def build(f, font, tableTag=None):
  1125. """Convert a Monotype font layout file to an OpenType layout object
  1126. A font object must be passed, but this may be a "dummy" font; it is only
  1127. used for sorting glyph sets when making coverage tables and to hold the
  1128. OpenType layout table while it is being built.
  1129. Args:
  1130. f: A file object.
  1131. font (TTFont): A font object.
  1132. tableTag (string): If provided, asserts that the file contains data for the
  1133. given OpenType table.
  1134. Returns:
  1135. An object representing the table. (e.g. ``table_G_S_U_B_``)
  1136. """
  1137. lines = Tokenizer(f)
  1138. return parseTable(lines, font, tableTag=tableTag)
  1139. def main(args=None, font=None):
  1140. """Convert a FontDame OTL file to TTX XML
  1141. Writes XML output to stdout.
  1142. Args:
  1143. args: Command line arguments (``--font``, ``--table``, input files).
  1144. """
  1145. import sys
  1146. from fontTools import configLogger
  1147. from fontTools.misc.testTools import MockFont
  1148. if args is None:
  1149. args = sys.argv[1:]
  1150. # configure the library logger (for >= WARNING)
  1151. configLogger()
  1152. # comment this out to enable debug messages from mtiLib's logger
  1153. # log.setLevel(logging.DEBUG)
  1154. import argparse
  1155. parser = argparse.ArgumentParser(
  1156. "fonttools mtiLib",
  1157. description=main.__doc__,
  1158. )
  1159. parser.add_argument(
  1160. "--font",
  1161. "-f",
  1162. metavar="FILE",
  1163. dest="font",
  1164. help="Input TTF files (used for glyph classes and sorting coverage tables)",
  1165. )
  1166. parser.add_argument(
  1167. "--table",
  1168. "-t",
  1169. metavar="TABLE",
  1170. dest="tableTag",
  1171. help="Table to fill (sniffed from input file if not provided)",
  1172. )
  1173. parser.add_argument(
  1174. "inputs", metavar="FILE", type=str, nargs="+", help="Input FontDame .txt files"
  1175. )
  1176. args = parser.parse_args(args)
  1177. if font is None:
  1178. if args.font:
  1179. font = ttLib.TTFont(args.font)
  1180. else:
  1181. font = MockFont()
  1182. for f in args.inputs:
  1183. log.debug("Processing %s", f)
  1184. with open(f, "rt", encoding="utf-8") as f:
  1185. table = build(f, font, tableTag=args.tableTag)
  1186. blob = table.compile(font) # Make sure it compiles
  1187. decompiled = table.__class__()
  1188. decompiled.decompile(blob, font) # Make sure it decompiles!
  1189. # continue
  1190. from fontTools.misc import xmlWriter
  1191. tag = table.tableTag
  1192. writer = xmlWriter.XMLWriter(sys.stdout)
  1193. writer.begintag(tag)
  1194. writer.newline()
  1195. # table.toXML(writer, font)
  1196. decompiled.toXML(writer, font)
  1197. writer.endtag(tag)
  1198. writer.newline()
  1199. if __name__ == "__main__":
  1200. import sys
  1201. sys.exit(main())