E_B_L_C_.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. from fontTools.misc import sstruct
  2. from . import DefaultTable
  3. from fontTools.misc.textTools import bytesjoin, safeEval
  4. from .BitmapGlyphMetrics import (
  5. BigGlyphMetrics,
  6. bigGlyphMetricsFormat,
  7. SmallGlyphMetrics,
  8. smallGlyphMetricsFormat,
  9. )
  10. import struct
  11. import itertools
  12. from collections import deque
  13. import logging
  14. log = logging.getLogger(__name__)
  15. eblcHeaderFormat = """
  16. > # big endian
  17. version: 16.16F
  18. numSizes: I
  19. """
  20. # The table format string is split to handle sbitLineMetrics simply.
  21. bitmapSizeTableFormatPart1 = """
  22. > # big endian
  23. indexSubTableArrayOffset: I
  24. indexTablesSize: I
  25. numberOfIndexSubTables: I
  26. colorRef: I
  27. """
  28. # The compound type for hori and vert.
  29. sbitLineMetricsFormat = """
  30. > # big endian
  31. ascender: b
  32. descender: b
  33. widthMax: B
  34. caretSlopeNumerator: b
  35. caretSlopeDenominator: b
  36. caretOffset: b
  37. minOriginSB: b
  38. minAdvanceSB: b
  39. maxBeforeBL: b
  40. minAfterBL: b
  41. pad1: b
  42. pad2: b
  43. """
  44. # hori and vert go between the two parts.
  45. bitmapSizeTableFormatPart2 = """
  46. > # big endian
  47. startGlyphIndex: H
  48. endGlyphIndex: H
  49. ppemX: B
  50. ppemY: B
  51. bitDepth: B
  52. flags: b
  53. """
  54. indexSubTableArrayFormat = ">HHL"
  55. indexSubTableArraySize = struct.calcsize(indexSubTableArrayFormat)
  56. indexSubHeaderFormat = ">HHL"
  57. indexSubHeaderSize = struct.calcsize(indexSubHeaderFormat)
  58. codeOffsetPairFormat = ">HH"
  59. codeOffsetPairSize = struct.calcsize(codeOffsetPairFormat)
  60. class table_E_B_L_C_(DefaultTable.DefaultTable):
  61. dependencies = ["EBDT"]
  62. # This method can be overridden in subclasses to support new formats
  63. # without changing the other implementation. Also can be used as a
  64. # convenience method for coverting a font file to an alternative format.
  65. def getIndexFormatClass(self, indexFormat):
  66. return eblc_sub_table_classes[indexFormat]
  67. def decompile(self, data, ttFont):
  68. # Save the original data because offsets are from the start of the table.
  69. origData = data
  70. i = 0
  71. dummy = sstruct.unpack(eblcHeaderFormat, data[:8], self)
  72. i += 8
  73. self.strikes = []
  74. for curStrikeIndex in range(self.numSizes):
  75. curStrike = Strike()
  76. self.strikes.append(curStrike)
  77. curTable = curStrike.bitmapSizeTable
  78. dummy = sstruct.unpack2(
  79. bitmapSizeTableFormatPart1, data[i : i + 16], curTable
  80. )
  81. i += 16
  82. for metric in ("hori", "vert"):
  83. metricObj = SbitLineMetrics()
  84. vars(curTable)[metric] = metricObj
  85. dummy = sstruct.unpack2(
  86. sbitLineMetricsFormat, data[i : i + 12], metricObj
  87. )
  88. i += 12
  89. dummy = sstruct.unpack(
  90. bitmapSizeTableFormatPart2, data[i : i + 8], curTable
  91. )
  92. i += 8
  93. for curStrike in self.strikes:
  94. curTable = curStrike.bitmapSizeTable
  95. for subtableIndex in range(curTable.numberOfIndexSubTables):
  96. i = (
  97. curTable.indexSubTableArrayOffset
  98. + subtableIndex * indexSubTableArraySize
  99. )
  100. tup = struct.unpack(
  101. indexSubTableArrayFormat, data[i : i + indexSubTableArraySize]
  102. )
  103. (firstGlyphIndex, lastGlyphIndex, additionalOffsetToIndexSubtable) = tup
  104. i = curTable.indexSubTableArrayOffset + additionalOffsetToIndexSubtable
  105. tup = struct.unpack(
  106. indexSubHeaderFormat, data[i : i + indexSubHeaderSize]
  107. )
  108. (indexFormat, imageFormat, imageDataOffset) = tup
  109. indexFormatClass = self.getIndexFormatClass(indexFormat)
  110. indexSubTable = indexFormatClass(data[i + indexSubHeaderSize :], ttFont)
  111. indexSubTable.firstGlyphIndex = firstGlyphIndex
  112. indexSubTable.lastGlyphIndex = lastGlyphIndex
  113. indexSubTable.additionalOffsetToIndexSubtable = (
  114. additionalOffsetToIndexSubtable
  115. )
  116. indexSubTable.indexFormat = indexFormat
  117. indexSubTable.imageFormat = imageFormat
  118. indexSubTable.imageDataOffset = imageDataOffset
  119. indexSubTable.decompile() # https://github.com/fonttools/fonttools/issues/317
  120. curStrike.indexSubTables.append(indexSubTable)
  121. def compile(self, ttFont):
  122. dataList = []
  123. self.numSizes = len(self.strikes)
  124. dataList.append(sstruct.pack(eblcHeaderFormat, self))
  125. # Data size of the header + bitmapSizeTable needs to be calculated
  126. # in order to form offsets. This value will hold the size of the data
  127. # in dataList after all the data is consolidated in dataList.
  128. dataSize = len(dataList[0])
  129. # The table will be structured in the following order:
  130. # (0) header
  131. # (1) Each bitmapSizeTable [1 ... self.numSizes]
  132. # (2) Alternate between indexSubTableArray and indexSubTable
  133. # for each bitmapSizeTable present.
  134. #
  135. # The issue is maintaining the proper offsets when table information
  136. # gets moved around. All offsets and size information must be recalculated
  137. # when building the table to allow editing within ttLib and also allow easy
  138. # import/export to and from XML. All of this offset information is lost
  139. # when exporting to XML so everything must be calculated fresh so importing
  140. # from XML will work cleanly. Only byte offset and size information is
  141. # calculated fresh. Count information like numberOfIndexSubTables is
  142. # checked through assertions. If the information in this table was not
  143. # touched or was changed properly then these types of values should match.
  144. #
  145. # The table will be rebuilt the following way:
  146. # (0) Precompute the size of all the bitmapSizeTables. This is needed to
  147. # compute the offsets properly.
  148. # (1) For each bitmapSizeTable compute the indexSubTable and
  149. # indexSubTableArray pair. The indexSubTable must be computed first
  150. # so that the offset information in indexSubTableArray can be
  151. # calculated. Update the data size after each pairing.
  152. # (2) Build each bitmapSizeTable.
  153. # (3) Consolidate all the data into the main dataList in the correct order.
  154. for _ in self.strikes:
  155. dataSize += sstruct.calcsize(bitmapSizeTableFormatPart1)
  156. dataSize += len(("hori", "vert")) * sstruct.calcsize(sbitLineMetricsFormat)
  157. dataSize += sstruct.calcsize(bitmapSizeTableFormatPart2)
  158. indexSubTablePairDataList = []
  159. for curStrike in self.strikes:
  160. curTable = curStrike.bitmapSizeTable
  161. curTable.numberOfIndexSubTables = len(curStrike.indexSubTables)
  162. curTable.indexSubTableArrayOffset = dataSize
  163. # Precompute the size of the indexSubTableArray. This information
  164. # is important for correctly calculating the new value for
  165. # additionalOffsetToIndexSubtable.
  166. sizeOfSubTableArray = (
  167. curTable.numberOfIndexSubTables * indexSubTableArraySize
  168. )
  169. lowerBound = dataSize
  170. dataSize += sizeOfSubTableArray
  171. upperBound = dataSize
  172. indexSubTableDataList = []
  173. for indexSubTable in curStrike.indexSubTables:
  174. indexSubTable.additionalOffsetToIndexSubtable = (
  175. dataSize - curTable.indexSubTableArrayOffset
  176. )
  177. glyphIds = list(map(ttFont.getGlyphID, indexSubTable.names))
  178. indexSubTable.firstGlyphIndex = min(glyphIds)
  179. indexSubTable.lastGlyphIndex = max(glyphIds)
  180. data = indexSubTable.compile(ttFont)
  181. indexSubTableDataList.append(data)
  182. dataSize += len(data)
  183. curTable.startGlyphIndex = min(
  184. ist.firstGlyphIndex for ist in curStrike.indexSubTables
  185. )
  186. curTable.endGlyphIndex = max(
  187. ist.lastGlyphIndex for ist in curStrike.indexSubTables
  188. )
  189. for i in curStrike.indexSubTables:
  190. data = struct.pack(
  191. indexSubHeaderFormat,
  192. i.firstGlyphIndex,
  193. i.lastGlyphIndex,
  194. i.additionalOffsetToIndexSubtable,
  195. )
  196. indexSubTablePairDataList.append(data)
  197. indexSubTablePairDataList.extend(indexSubTableDataList)
  198. curTable.indexTablesSize = dataSize - curTable.indexSubTableArrayOffset
  199. for curStrike in self.strikes:
  200. curTable = curStrike.bitmapSizeTable
  201. data = sstruct.pack(bitmapSizeTableFormatPart1, curTable)
  202. dataList.append(data)
  203. for metric in ("hori", "vert"):
  204. metricObj = vars(curTable)[metric]
  205. data = sstruct.pack(sbitLineMetricsFormat, metricObj)
  206. dataList.append(data)
  207. data = sstruct.pack(bitmapSizeTableFormatPart2, curTable)
  208. dataList.append(data)
  209. dataList.extend(indexSubTablePairDataList)
  210. return bytesjoin(dataList)
  211. def toXML(self, writer, ttFont):
  212. writer.simpletag("header", [("version", self.version)])
  213. writer.newline()
  214. for curIndex, curStrike in enumerate(self.strikes):
  215. curStrike.toXML(curIndex, writer, ttFont)
  216. def fromXML(self, name, attrs, content, ttFont):
  217. if name == "header":
  218. self.version = safeEval(attrs["version"])
  219. elif name == "strike":
  220. if not hasattr(self, "strikes"):
  221. self.strikes = []
  222. strikeIndex = safeEval(attrs["index"])
  223. curStrike = Strike()
  224. curStrike.fromXML(name, attrs, content, ttFont, self)
  225. # Grow the strike array to the appropriate size. The XML format
  226. # allows for the strike index value to be out of order.
  227. if strikeIndex >= len(self.strikes):
  228. self.strikes += [None] * (strikeIndex + 1 - len(self.strikes))
  229. assert self.strikes[strikeIndex] is None, "Duplicate strike EBLC indices."
  230. self.strikes[strikeIndex] = curStrike
  231. class Strike(object):
  232. def __init__(self):
  233. self.bitmapSizeTable = BitmapSizeTable()
  234. self.indexSubTables = []
  235. def toXML(self, strikeIndex, writer, ttFont):
  236. writer.begintag("strike", [("index", strikeIndex)])
  237. writer.newline()
  238. self.bitmapSizeTable.toXML(writer, ttFont)
  239. writer.comment(
  240. "GlyphIds are written but not read. The firstGlyphIndex and\nlastGlyphIndex values will be recalculated by the compiler."
  241. )
  242. writer.newline()
  243. for indexSubTable in self.indexSubTables:
  244. indexSubTable.toXML(writer, ttFont)
  245. writer.endtag("strike")
  246. writer.newline()
  247. def fromXML(self, name, attrs, content, ttFont, locator):
  248. for element in content:
  249. if not isinstance(element, tuple):
  250. continue
  251. name, attrs, content = element
  252. if name == "bitmapSizeTable":
  253. self.bitmapSizeTable.fromXML(name, attrs, content, ttFont)
  254. elif name.startswith(_indexSubTableSubclassPrefix):
  255. indexFormat = safeEval(name[len(_indexSubTableSubclassPrefix) :])
  256. indexFormatClass = locator.getIndexFormatClass(indexFormat)
  257. indexSubTable = indexFormatClass(None, None)
  258. indexSubTable.indexFormat = indexFormat
  259. indexSubTable.fromXML(name, attrs, content, ttFont)
  260. self.indexSubTables.append(indexSubTable)
  261. class BitmapSizeTable(object):
  262. # Returns all the simple metric names that bitmap size table
  263. # cares about in terms of XML creation.
  264. def _getXMLMetricNames(self):
  265. dataNames = sstruct.getformat(bitmapSizeTableFormatPart1)[1]
  266. dataNames = dataNames + sstruct.getformat(bitmapSizeTableFormatPart2)[1]
  267. # Skip the first 3 data names because they are byte offsets and counts.
  268. return dataNames[3:]
  269. def toXML(self, writer, ttFont):
  270. writer.begintag("bitmapSizeTable")
  271. writer.newline()
  272. for metric in ("hori", "vert"):
  273. getattr(self, metric).toXML(metric, writer, ttFont)
  274. for metricName in self._getXMLMetricNames():
  275. writer.simpletag(metricName, value=getattr(self, metricName))
  276. writer.newline()
  277. writer.endtag("bitmapSizeTable")
  278. writer.newline()
  279. def fromXML(self, name, attrs, content, ttFont):
  280. # Create a lookup for all the simple names that make sense to
  281. # bitmap size table. Only read the information from these names.
  282. dataNames = set(self._getXMLMetricNames())
  283. for element in content:
  284. if not isinstance(element, tuple):
  285. continue
  286. name, attrs, content = element
  287. if name == "sbitLineMetrics":
  288. direction = attrs["direction"]
  289. assert direction in (
  290. "hori",
  291. "vert",
  292. ), "SbitLineMetrics direction specified invalid."
  293. metricObj = SbitLineMetrics()
  294. metricObj.fromXML(name, attrs, content, ttFont)
  295. vars(self)[direction] = metricObj
  296. elif name in dataNames:
  297. vars(self)[name] = safeEval(attrs["value"])
  298. else:
  299. log.warning("unknown name '%s' being ignored in BitmapSizeTable.", name)
  300. class SbitLineMetrics(object):
  301. def toXML(self, name, writer, ttFont):
  302. writer.begintag("sbitLineMetrics", [("direction", name)])
  303. writer.newline()
  304. for metricName in sstruct.getformat(sbitLineMetricsFormat)[1]:
  305. writer.simpletag(metricName, value=getattr(self, metricName))
  306. writer.newline()
  307. writer.endtag("sbitLineMetrics")
  308. writer.newline()
  309. def fromXML(self, name, attrs, content, ttFont):
  310. metricNames = set(sstruct.getformat(sbitLineMetricsFormat)[1])
  311. for element in content:
  312. if not isinstance(element, tuple):
  313. continue
  314. name, attrs, content = element
  315. if name in metricNames:
  316. vars(self)[name] = safeEval(attrs["value"])
  317. # Important information about the naming scheme. Used for identifying subtables.
  318. _indexSubTableSubclassPrefix = "eblc_index_sub_table_"
  319. class EblcIndexSubTable(object):
  320. def __init__(self, data, ttFont):
  321. self.data = data
  322. self.ttFont = ttFont
  323. # TODO Currently non-lazy decompiling doesn't work for this class...
  324. # if not ttFont.lazy:
  325. # self.decompile()
  326. # del self.data, self.ttFont
  327. def __getattr__(self, attr):
  328. # Allow lazy decompile.
  329. if attr[:2] == "__":
  330. raise AttributeError(attr)
  331. if attr == "data":
  332. raise AttributeError(attr)
  333. self.decompile()
  334. return getattr(self, attr)
  335. def ensureDecompiled(self, recurse=False):
  336. if hasattr(self, "data"):
  337. self.decompile()
  338. # This method just takes care of the indexSubHeader. Implementing subclasses
  339. # should call it to compile the indexSubHeader and then continue compiling
  340. # the remainder of their unique format.
  341. def compile(self, ttFont):
  342. return struct.pack(
  343. indexSubHeaderFormat,
  344. self.indexFormat,
  345. self.imageFormat,
  346. self.imageDataOffset,
  347. )
  348. # Creates the XML for bitmap glyphs. Each index sub table basically makes
  349. # the same XML except for specific metric information that is written
  350. # out via a method call that a subclass implements optionally.
  351. def toXML(self, writer, ttFont):
  352. writer.begintag(
  353. self.__class__.__name__,
  354. [
  355. ("imageFormat", self.imageFormat),
  356. ("firstGlyphIndex", self.firstGlyphIndex),
  357. ("lastGlyphIndex", self.lastGlyphIndex),
  358. ],
  359. )
  360. writer.newline()
  361. self.writeMetrics(writer, ttFont)
  362. # Write out the names as thats all thats needed to rebuild etc.
  363. # For font debugging of consecutive formats the ids are also written.
  364. # The ids are not read when moving from the XML format.
  365. glyphIds = map(ttFont.getGlyphID, self.names)
  366. for glyphName, glyphId in zip(self.names, glyphIds):
  367. writer.simpletag("glyphLoc", name=glyphName, id=glyphId)
  368. writer.newline()
  369. writer.endtag(self.__class__.__name__)
  370. writer.newline()
  371. def fromXML(self, name, attrs, content, ttFont):
  372. # Read all the attributes. Even though the glyph indices are
  373. # recalculated, they are still read in case there needs to
  374. # be an immediate export of the data.
  375. self.imageFormat = safeEval(attrs["imageFormat"])
  376. self.firstGlyphIndex = safeEval(attrs["firstGlyphIndex"])
  377. self.lastGlyphIndex = safeEval(attrs["lastGlyphIndex"])
  378. self.readMetrics(name, attrs, content, ttFont)
  379. self.names = []
  380. for element in content:
  381. if not isinstance(element, tuple):
  382. continue
  383. name, attrs, content = element
  384. if name == "glyphLoc":
  385. self.names.append(attrs["name"])
  386. # A helper method that writes the metrics for the index sub table. It also
  387. # is responsible for writing the image size for fixed size data since fixed
  388. # size is not recalculated on compile. Default behavior is to do nothing.
  389. def writeMetrics(self, writer, ttFont):
  390. pass
  391. # A helper method that is the inverse of writeMetrics.
  392. def readMetrics(self, name, attrs, content, ttFont):
  393. pass
  394. # This method is for fixed glyph data sizes. There are formats where
  395. # the glyph data is fixed but are actually composite glyphs. To handle
  396. # this the font spec in indexSubTable makes the data the size of the
  397. # fixed size by padding the component arrays. This function abstracts
  398. # out this padding process. Input is data unpadded. Output is data
  399. # padded only in fixed formats. Default behavior is to return the data.
  400. def padBitmapData(self, data):
  401. return data
  402. # Remove any of the glyph locations and names that are flagged as skipped.
  403. # This only occurs in formats {1,3}.
  404. def removeSkipGlyphs(self):
  405. # Determines if a name, location pair is a valid data location.
  406. # Skip glyphs are marked when the size is equal to zero.
  407. def isValidLocation(args):
  408. (name, (startByte, endByte)) = args
  409. return startByte < endByte
  410. # Remove all skip glyphs.
  411. dataPairs = list(filter(isValidLocation, zip(self.names, self.locations)))
  412. self.names, self.locations = list(map(list, zip(*dataPairs)))
  413. # A closure for creating a custom mixin. This is done because formats 1 and 3
  414. # are very similar. The only difference between them is the size per offset
  415. # value. Code put in here should handle both cases generally.
  416. def _createOffsetArrayIndexSubTableMixin(formatStringForDataType):
  417. # Prep the data size for the offset array data format.
  418. dataFormat = ">" + formatStringForDataType
  419. offsetDataSize = struct.calcsize(dataFormat)
  420. class OffsetArrayIndexSubTableMixin(object):
  421. def decompile(self):
  422. numGlyphs = self.lastGlyphIndex - self.firstGlyphIndex + 1
  423. indexingOffsets = [
  424. glyphIndex * offsetDataSize for glyphIndex in range(numGlyphs + 2)
  425. ]
  426. indexingLocations = zip(indexingOffsets, indexingOffsets[1:])
  427. offsetArray = [
  428. struct.unpack(dataFormat, self.data[slice(*loc)])[0]
  429. for loc in indexingLocations
  430. ]
  431. glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1))
  432. modifiedOffsets = [offset + self.imageDataOffset for offset in offsetArray]
  433. self.locations = list(zip(modifiedOffsets, modifiedOffsets[1:]))
  434. self.names = list(map(self.ttFont.getGlyphName, glyphIds))
  435. self.removeSkipGlyphs()
  436. del self.data, self.ttFont
  437. def compile(self, ttFont):
  438. # First make sure that all the data lines up properly. Formats 1 and 3
  439. # must have all its data lined up consecutively. If not this will fail.
  440. for curLoc, nxtLoc in zip(self.locations, self.locations[1:]):
  441. assert (
  442. curLoc[1] == nxtLoc[0]
  443. ), "Data must be consecutive in indexSubTable offset formats"
  444. glyphIds = list(map(ttFont.getGlyphID, self.names))
  445. # Make sure that all ids are sorted strictly increasing.
  446. assert all(glyphIds[i] < glyphIds[i + 1] for i in range(len(glyphIds) - 1))
  447. # Run a simple algorithm to add skip glyphs to the data locations at
  448. # the places where an id is not present.
  449. idQueue = deque(glyphIds)
  450. locQueue = deque(self.locations)
  451. allGlyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1))
  452. allLocations = []
  453. for curId in allGlyphIds:
  454. if curId != idQueue[0]:
  455. allLocations.append((locQueue[0][0], locQueue[0][0]))
  456. else:
  457. idQueue.popleft()
  458. allLocations.append(locQueue.popleft())
  459. # Now that all the locations are collected, pack them appropriately into
  460. # offsets. This is the form where offset[i] is the location and
  461. # offset[i+1]-offset[i] is the size of the data location.
  462. offsets = list(allLocations[0]) + [loc[1] for loc in allLocations[1:]]
  463. # Image data offset must be less than or equal to the minimum of locations.
  464. # This offset may change the value for round tripping but is safer and
  465. # allows imageDataOffset to not be required to be in the XML version.
  466. self.imageDataOffset = min(offsets)
  467. offsetArray = [offset - self.imageDataOffset for offset in offsets]
  468. dataList = [EblcIndexSubTable.compile(self, ttFont)]
  469. dataList += [
  470. struct.pack(dataFormat, offsetValue) for offsetValue in offsetArray
  471. ]
  472. # Take care of any padding issues. Only occurs in format 3.
  473. if offsetDataSize * len(offsetArray) % 4 != 0:
  474. dataList.append(struct.pack(dataFormat, 0))
  475. return bytesjoin(dataList)
  476. return OffsetArrayIndexSubTableMixin
  477. # A Mixin for functionality shared between the different kinds
  478. # of fixed sized data handling. Both kinds have big metrics so
  479. # that kind of special processing is also handled in this mixin.
  480. class FixedSizeIndexSubTableMixin(object):
  481. def writeMetrics(self, writer, ttFont):
  482. writer.simpletag("imageSize", value=self.imageSize)
  483. writer.newline()
  484. self.metrics.toXML(writer, ttFont)
  485. def readMetrics(self, name, attrs, content, ttFont):
  486. for element in content:
  487. if not isinstance(element, tuple):
  488. continue
  489. name, attrs, content = element
  490. if name == "imageSize":
  491. self.imageSize = safeEval(attrs["value"])
  492. elif name == BigGlyphMetrics.__name__:
  493. self.metrics = BigGlyphMetrics()
  494. self.metrics.fromXML(name, attrs, content, ttFont)
  495. elif name == SmallGlyphMetrics.__name__:
  496. log.warning(
  497. "SmallGlyphMetrics being ignored in format %d.", self.indexFormat
  498. )
  499. def padBitmapData(self, data):
  500. # Make sure that the data isn't bigger than the fixed size.
  501. assert len(data) <= self.imageSize, (
  502. "Data in indexSubTable format %d must be less than the fixed size."
  503. % self.indexFormat
  504. )
  505. # Pad the data so that it matches the fixed size.
  506. pad = (self.imageSize - len(data)) * b"\0"
  507. return data + pad
  508. class eblc_index_sub_table_1(
  509. _createOffsetArrayIndexSubTableMixin("L"), EblcIndexSubTable
  510. ):
  511. pass
  512. class eblc_index_sub_table_2(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
  513. def decompile(self):
  514. (self.imageSize,) = struct.unpack(">L", self.data[:4])
  515. self.metrics = BigGlyphMetrics()
  516. sstruct.unpack2(bigGlyphMetricsFormat, self.data[4:], self.metrics)
  517. glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1))
  518. offsets = [
  519. self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds) + 1)
  520. ]
  521. self.locations = list(zip(offsets, offsets[1:]))
  522. self.names = list(map(self.ttFont.getGlyphName, glyphIds))
  523. del self.data, self.ttFont
  524. def compile(self, ttFont):
  525. glyphIds = list(map(ttFont.getGlyphID, self.names))
  526. # Make sure all the ids are consecutive. This is required by Format 2.
  527. assert glyphIds == list(
  528. range(self.firstGlyphIndex, self.lastGlyphIndex + 1)
  529. ), "Format 2 ids must be consecutive."
  530. self.imageDataOffset = min(next(iter(zip(*self.locations))))
  531. dataList = [EblcIndexSubTable.compile(self, ttFont)]
  532. dataList.append(struct.pack(">L", self.imageSize))
  533. dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
  534. return bytesjoin(dataList)
  535. class eblc_index_sub_table_3(
  536. _createOffsetArrayIndexSubTableMixin("H"), EblcIndexSubTable
  537. ):
  538. pass
  539. class eblc_index_sub_table_4(EblcIndexSubTable):
  540. def decompile(self):
  541. (numGlyphs,) = struct.unpack(">L", self.data[:4])
  542. data = self.data[4:]
  543. indexingOffsets = [
  544. glyphIndex * codeOffsetPairSize for glyphIndex in range(numGlyphs + 2)
  545. ]
  546. indexingLocations = zip(indexingOffsets, indexingOffsets[1:])
  547. glyphArray = [
  548. struct.unpack(codeOffsetPairFormat, data[slice(*loc)])
  549. for loc in indexingLocations
  550. ]
  551. glyphIds, offsets = list(map(list, zip(*glyphArray)))
  552. # There are one too many glyph ids. Get rid of the last one.
  553. glyphIds.pop()
  554. offsets = [offset + self.imageDataOffset for offset in offsets]
  555. self.locations = list(zip(offsets, offsets[1:]))
  556. self.names = list(map(self.ttFont.getGlyphName, glyphIds))
  557. del self.data, self.ttFont
  558. def compile(self, ttFont):
  559. # First make sure that all the data lines up properly. Format 4
  560. # must have all its data lined up consecutively. If not this will fail.
  561. for curLoc, nxtLoc in zip(self.locations, self.locations[1:]):
  562. assert (
  563. curLoc[1] == nxtLoc[0]
  564. ), "Data must be consecutive in indexSubTable format 4"
  565. offsets = list(self.locations[0]) + [loc[1] for loc in self.locations[1:]]
  566. # Image data offset must be less than or equal to the minimum of locations.
  567. # Resetting this offset may change the value for round tripping but is safer
  568. # and allows imageDataOffset to not be required to be in the XML version.
  569. self.imageDataOffset = min(offsets)
  570. offsets = [offset - self.imageDataOffset for offset in offsets]
  571. glyphIds = list(map(ttFont.getGlyphID, self.names))
  572. # Create an iterator over the ids plus a padding value.
  573. idsPlusPad = list(itertools.chain(glyphIds, [0]))
  574. dataList = [EblcIndexSubTable.compile(self, ttFont)]
  575. dataList.append(struct.pack(">L", len(glyphIds)))
  576. tmp = [
  577. struct.pack(codeOffsetPairFormat, *cop) for cop in zip(idsPlusPad, offsets)
  578. ]
  579. dataList += tmp
  580. data = bytesjoin(dataList)
  581. return data
  582. class eblc_index_sub_table_5(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
  583. def decompile(self):
  584. self.origDataLen = 0
  585. (self.imageSize,) = struct.unpack(">L", self.data[:4])
  586. data = self.data[4:]
  587. self.metrics, data = sstruct.unpack2(
  588. bigGlyphMetricsFormat, data, BigGlyphMetrics()
  589. )
  590. (numGlyphs,) = struct.unpack(">L", data[:4])
  591. data = data[4:]
  592. glyphIds = [
  593. struct.unpack(">H", data[2 * i : 2 * (i + 1)])[0] for i in range(numGlyphs)
  594. ]
  595. offsets = [
  596. self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds) + 1)
  597. ]
  598. self.locations = list(zip(offsets, offsets[1:]))
  599. self.names = list(map(self.ttFont.getGlyphName, glyphIds))
  600. del self.data, self.ttFont
  601. def compile(self, ttFont):
  602. self.imageDataOffset = min(next(iter(zip(*self.locations))))
  603. dataList = [EblcIndexSubTable.compile(self, ttFont)]
  604. dataList.append(struct.pack(">L", self.imageSize))
  605. dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
  606. glyphIds = list(map(ttFont.getGlyphID, self.names))
  607. dataList.append(struct.pack(">L", len(glyphIds)))
  608. dataList += [struct.pack(">H", curId) for curId in glyphIds]
  609. if len(glyphIds) % 2 == 1:
  610. dataList.append(struct.pack(">H", 0))
  611. return bytesjoin(dataList)
  612. # Dictionary of indexFormat to the class representing that format.
  613. eblc_sub_table_classes = {
  614. 1: eblc_index_sub_table_1,
  615. 2: eblc_index_sub_table_2,
  616. 3: eblc_index_sub_table_3,
  617. 4: eblc_index_sub_table_4,
  618. 5: eblc_index_sub_table_5,
  619. }