__init__.py 46 KB

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