E_B_D_T_.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  1. from fontTools.misc import sstruct
  2. from fontTools.misc.textTools import (
  3. bytechr,
  4. byteord,
  5. bytesjoin,
  6. strjoin,
  7. safeEval,
  8. readHex,
  9. hexStr,
  10. deHexStr,
  11. )
  12. from .BitmapGlyphMetrics import (
  13. BigGlyphMetrics,
  14. bigGlyphMetricsFormat,
  15. SmallGlyphMetrics,
  16. smallGlyphMetricsFormat,
  17. )
  18. from . import DefaultTable
  19. import itertools
  20. import os
  21. import struct
  22. import logging
  23. log = logging.getLogger(__name__)
  24. ebdtTableVersionFormat = """
  25. > # big endian
  26. version: 16.16F
  27. """
  28. ebdtComponentFormat = """
  29. > # big endian
  30. glyphCode: H
  31. xOffset: b
  32. yOffset: b
  33. """
  34. class table_E_B_D_T_(DefaultTable.DefaultTable):
  35. # Keep a reference to the name of the data locator table.
  36. locatorName = "EBLC"
  37. # This method can be overridden in subclasses to support new formats
  38. # without changing the other implementation. Also can be used as a
  39. # convenience method for coverting a font file to an alternative format.
  40. def getImageFormatClass(self, imageFormat):
  41. return ebdt_bitmap_classes[imageFormat]
  42. def decompile(self, data, ttFont):
  43. # Get the version but don't advance the slice.
  44. # Most of the lookup for this table is done relative
  45. # to the begining so slice by the offsets provided
  46. # in the EBLC table.
  47. sstruct.unpack2(ebdtTableVersionFormat, data, self)
  48. # Keep a dict of glyphs that have been seen so they aren't remade.
  49. # This dict maps intervals of data to the BitmapGlyph.
  50. glyphDict = {}
  51. # Pull out the EBLC table and loop through glyphs.
  52. # A strike is a concept that spans both tables.
  53. # The actual bitmap data is stored in the EBDT.
  54. locator = ttFont[self.__class__.locatorName]
  55. self.strikeData = []
  56. for curStrike in locator.strikes:
  57. bitmapGlyphDict = {}
  58. self.strikeData.append(bitmapGlyphDict)
  59. for indexSubTable in curStrike.indexSubTables:
  60. dataIter = zip(indexSubTable.names, indexSubTable.locations)
  61. for curName, curLoc in dataIter:
  62. # Don't create duplicate data entries for the same glyphs.
  63. # Instead just use the structures that already exist if they exist.
  64. if curLoc in glyphDict:
  65. curGlyph = glyphDict[curLoc]
  66. else:
  67. curGlyphData = data[slice(*curLoc)]
  68. imageFormatClass = self.getImageFormatClass(
  69. indexSubTable.imageFormat
  70. )
  71. curGlyph = imageFormatClass(curGlyphData, ttFont)
  72. glyphDict[curLoc] = curGlyph
  73. bitmapGlyphDict[curName] = curGlyph
  74. def compile(self, ttFont):
  75. dataList = []
  76. dataList.append(sstruct.pack(ebdtTableVersionFormat, self))
  77. dataSize = len(dataList[0])
  78. # Keep a dict of glyphs that have been seen so they aren't remade.
  79. # This dict maps the id of the BitmapGlyph to the interval
  80. # in the data.
  81. glyphDict = {}
  82. # Go through the bitmap glyph data. Just in case the data for a glyph
  83. # changed the size metrics should be recalculated. There are a variety
  84. # of formats and they get stored in the EBLC table. That is why
  85. # recalculation is defered to the EblcIndexSubTable class and just
  86. # pass what is known about bitmap glyphs from this particular table.
  87. locator = ttFont[self.__class__.locatorName]
  88. for curStrike, curGlyphDict in zip(locator.strikes, self.strikeData):
  89. for curIndexSubTable in curStrike.indexSubTables:
  90. dataLocations = []
  91. for curName in curIndexSubTable.names:
  92. # Handle the data placement based on seeing the glyph or not.
  93. # Just save a reference to the location if the glyph has already
  94. # been saved in compile. This code assumes that glyphs will only
  95. # be referenced multiple times from indexFormat5. By luck the
  96. # code may still work when referencing poorly ordered fonts with
  97. # duplicate references. If there is a font that is unlucky the
  98. # respective compile methods for the indexSubTables will fail
  99. # their assertions. All fonts seem to follow this assumption.
  100. # More complicated packing may be needed if a counter-font exists.
  101. glyph = curGlyphDict[curName]
  102. objectId = id(glyph)
  103. if objectId not in glyphDict:
  104. data = glyph.compile(ttFont)
  105. data = curIndexSubTable.padBitmapData(data)
  106. startByte = dataSize
  107. dataSize += len(data)
  108. endByte = dataSize
  109. dataList.append(data)
  110. dataLoc = (startByte, endByte)
  111. glyphDict[objectId] = dataLoc
  112. else:
  113. dataLoc = glyphDict[objectId]
  114. dataLocations.append(dataLoc)
  115. # Just use the new data locations in the indexSubTable.
  116. # The respective compile implementations will take care
  117. # of any of the problems in the convertion that may arise.
  118. curIndexSubTable.locations = dataLocations
  119. return bytesjoin(dataList)
  120. def toXML(self, writer, ttFont):
  121. # When exporting to XML if one of the data export formats
  122. # requires metrics then those metrics may be in the locator.
  123. # In this case populate the bitmaps with "export metrics".
  124. if ttFont.bitmapGlyphDataFormat in ("row", "bitwise"):
  125. locator = ttFont[self.__class__.locatorName]
  126. for curStrike, curGlyphDict in zip(locator.strikes, self.strikeData):
  127. for curIndexSubTable in curStrike.indexSubTables:
  128. for curName in curIndexSubTable.names:
  129. glyph = curGlyphDict[curName]
  130. # I'm not sure which metrics have priority here.
  131. # For now if both metrics exist go with glyph metrics.
  132. if hasattr(glyph, "metrics"):
  133. glyph.exportMetrics = glyph.metrics
  134. else:
  135. glyph.exportMetrics = curIndexSubTable.metrics
  136. glyph.exportBitDepth = curStrike.bitmapSizeTable.bitDepth
  137. writer.simpletag("header", [("version", self.version)])
  138. writer.newline()
  139. locator = ttFont[self.__class__.locatorName]
  140. for strikeIndex, bitmapGlyphDict in enumerate(self.strikeData):
  141. writer.begintag("strikedata", [("index", strikeIndex)])
  142. writer.newline()
  143. for curName, curBitmap in bitmapGlyphDict.items():
  144. curBitmap.toXML(strikeIndex, curName, writer, ttFont)
  145. writer.endtag("strikedata")
  146. writer.newline()
  147. def fromXML(self, name, attrs, content, ttFont):
  148. if name == "header":
  149. self.version = safeEval(attrs["version"])
  150. elif name == "strikedata":
  151. if not hasattr(self, "strikeData"):
  152. self.strikeData = []
  153. strikeIndex = safeEval(attrs["index"])
  154. bitmapGlyphDict = {}
  155. for element in content:
  156. if not isinstance(element, tuple):
  157. continue
  158. name, attrs, content = element
  159. if name[4:].startswith(_bitmapGlyphSubclassPrefix[4:]):
  160. imageFormat = safeEval(name[len(_bitmapGlyphSubclassPrefix) :])
  161. glyphName = attrs["name"]
  162. imageFormatClass = self.getImageFormatClass(imageFormat)
  163. curGlyph = imageFormatClass(None, None)
  164. curGlyph.fromXML(name, attrs, content, ttFont)
  165. assert glyphName not in bitmapGlyphDict, (
  166. "Duplicate glyphs with the same name '%s' in the same strike."
  167. % glyphName
  168. )
  169. bitmapGlyphDict[glyphName] = curGlyph
  170. else:
  171. log.warning("%s being ignored by %s", name, self.__class__.__name__)
  172. # Grow the strike data array to the appropriate size. The XML
  173. # format allows the strike index value to be out of order.
  174. if strikeIndex >= len(self.strikeData):
  175. self.strikeData += [None] * (strikeIndex + 1 - len(self.strikeData))
  176. assert (
  177. self.strikeData[strikeIndex] is None
  178. ), "Duplicate strike EBDT indices."
  179. self.strikeData[strikeIndex] = bitmapGlyphDict
  180. class EbdtComponent(object):
  181. def toXML(self, writer, ttFont):
  182. writer.begintag("ebdtComponent", [("name", self.name)])
  183. writer.newline()
  184. for componentName in sstruct.getformat(ebdtComponentFormat)[1][1:]:
  185. writer.simpletag(componentName, value=getattr(self, componentName))
  186. writer.newline()
  187. writer.endtag("ebdtComponent")
  188. writer.newline()
  189. def fromXML(self, name, attrs, content, ttFont):
  190. self.name = attrs["name"]
  191. componentNames = set(sstruct.getformat(ebdtComponentFormat)[1][1:])
  192. for element in content:
  193. if not isinstance(element, tuple):
  194. continue
  195. name, attrs, content = element
  196. if name in componentNames:
  197. vars(self)[name] = safeEval(attrs["value"])
  198. else:
  199. log.warning("unknown name '%s' being ignored by EbdtComponent.", name)
  200. # Helper functions for dealing with binary.
  201. def _data2binary(data, numBits):
  202. binaryList = []
  203. for curByte in data:
  204. value = byteord(curByte)
  205. numBitsCut = min(8, numBits)
  206. for i in range(numBitsCut):
  207. if value & 0x1:
  208. binaryList.append("1")
  209. else:
  210. binaryList.append("0")
  211. value = value >> 1
  212. numBits -= numBitsCut
  213. return strjoin(binaryList)
  214. def _binary2data(binary):
  215. byteList = []
  216. for bitLoc in range(0, len(binary), 8):
  217. byteString = binary[bitLoc : bitLoc + 8]
  218. curByte = 0
  219. for curBit in reversed(byteString):
  220. curByte = curByte << 1
  221. if curBit == "1":
  222. curByte |= 1
  223. byteList.append(bytechr(curByte))
  224. return bytesjoin(byteList)
  225. def _memoize(f):
  226. class memodict(dict):
  227. def __missing__(self, key):
  228. ret = f(key)
  229. if isinstance(key, int) or len(key) == 1:
  230. self[key] = ret
  231. return ret
  232. return memodict().__getitem__
  233. # 00100111 -> 11100100 per byte, not to be confused with little/big endian.
  234. # Bitmap data per byte is in the order that binary is written on the page
  235. # with the least significant bit as far right as possible. This is the
  236. # opposite of what makes sense algorithmically and hence this function.
  237. @_memoize
  238. def _reverseBytes(data):
  239. r"""
  240. >>> bin(ord(_reverseBytes(0b00100111)))
  241. '0b11100100'
  242. >>> _reverseBytes(b'\x00\xf0')
  243. b'\x00\x0f'
  244. """
  245. if isinstance(data, bytes) and len(data) != 1:
  246. return bytesjoin(map(_reverseBytes, data))
  247. byte = byteord(data)
  248. result = 0
  249. for i in range(8):
  250. result = result << 1
  251. result |= byte & 1
  252. byte = byte >> 1
  253. return bytechr(result)
  254. # This section of code is for reading and writing image data to/from XML.
  255. def _writeRawImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
  256. writer.begintag("rawimagedata")
  257. writer.newline()
  258. writer.dumphex(bitmapObject.imageData)
  259. writer.endtag("rawimagedata")
  260. writer.newline()
  261. def _readRawImageData(bitmapObject, name, attrs, content, ttFont):
  262. bitmapObject.imageData = readHex(content)
  263. def _writeRowImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
  264. metrics = bitmapObject.exportMetrics
  265. del bitmapObject.exportMetrics
  266. bitDepth = bitmapObject.exportBitDepth
  267. del bitmapObject.exportBitDepth
  268. writer.begintag(
  269. "rowimagedata", bitDepth=bitDepth, width=metrics.width, height=metrics.height
  270. )
  271. writer.newline()
  272. for curRow in range(metrics.height):
  273. rowData = bitmapObject.getRow(curRow, bitDepth=bitDepth, metrics=metrics)
  274. writer.simpletag("row", value=hexStr(rowData))
  275. writer.newline()
  276. writer.endtag("rowimagedata")
  277. writer.newline()
  278. def _readRowImageData(bitmapObject, name, attrs, content, ttFont):
  279. bitDepth = safeEval(attrs["bitDepth"])
  280. metrics = SmallGlyphMetrics()
  281. metrics.width = safeEval(attrs["width"])
  282. metrics.height = safeEval(attrs["height"])
  283. dataRows = []
  284. for element in content:
  285. if not isinstance(element, tuple):
  286. continue
  287. name, attr, content = element
  288. # Chop off 'imagedata' from the tag to get just the option.
  289. if name == "row":
  290. dataRows.append(deHexStr(attr["value"]))
  291. bitmapObject.setRows(dataRows, bitDepth=bitDepth, metrics=metrics)
  292. def _writeBitwiseImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
  293. metrics = bitmapObject.exportMetrics
  294. del bitmapObject.exportMetrics
  295. bitDepth = bitmapObject.exportBitDepth
  296. del bitmapObject.exportBitDepth
  297. # A dict for mapping binary to more readable/artistic ASCII characters.
  298. binaryConv = {"0": ".", "1": "@"}
  299. writer.begintag(
  300. "bitwiseimagedata",
  301. bitDepth=bitDepth,
  302. width=metrics.width,
  303. height=metrics.height,
  304. )
  305. writer.newline()
  306. for curRow in range(metrics.height):
  307. rowData = bitmapObject.getRow(
  308. curRow, bitDepth=1, metrics=metrics, reverseBytes=True
  309. )
  310. rowData = _data2binary(rowData, metrics.width)
  311. # Make the output a readable ASCII art form.
  312. rowData = strjoin(map(binaryConv.get, rowData))
  313. writer.simpletag("row", value=rowData)
  314. writer.newline()
  315. writer.endtag("bitwiseimagedata")
  316. writer.newline()
  317. def _readBitwiseImageData(bitmapObject, name, attrs, content, ttFont):
  318. bitDepth = safeEval(attrs["bitDepth"])
  319. metrics = SmallGlyphMetrics()
  320. metrics.width = safeEval(attrs["width"])
  321. metrics.height = safeEval(attrs["height"])
  322. # A dict for mapping from ASCII to binary. All characters are considered
  323. # a '1' except space, period and '0' which maps to '0'.
  324. binaryConv = {" ": "0", ".": "0", "0": "0"}
  325. dataRows = []
  326. for element in content:
  327. if not isinstance(element, tuple):
  328. continue
  329. name, attr, content = element
  330. if name == "row":
  331. mapParams = zip(attr["value"], itertools.repeat("1"))
  332. rowData = strjoin(itertools.starmap(binaryConv.get, mapParams))
  333. dataRows.append(_binary2data(rowData))
  334. bitmapObject.setRows(
  335. dataRows, bitDepth=bitDepth, metrics=metrics, reverseBytes=True
  336. )
  337. def _writeExtFileImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
  338. try:
  339. folder = os.path.dirname(writer.file.name)
  340. except AttributeError:
  341. # fall back to current directory if output file's directory isn't found
  342. folder = "."
  343. folder = os.path.join(folder, "bitmaps")
  344. filename = glyphName + bitmapObject.fileExtension
  345. if not os.path.isdir(folder):
  346. os.makedirs(folder)
  347. folder = os.path.join(folder, "strike%d" % strikeIndex)
  348. if not os.path.isdir(folder):
  349. os.makedirs(folder)
  350. fullPath = os.path.join(folder, filename)
  351. writer.simpletag("extfileimagedata", value=fullPath)
  352. writer.newline()
  353. with open(fullPath, "wb") as file:
  354. file.write(bitmapObject.imageData)
  355. def _readExtFileImageData(bitmapObject, name, attrs, content, ttFont):
  356. fullPath = attrs["value"]
  357. with open(fullPath, "rb") as file:
  358. bitmapObject.imageData = file.read()
  359. # End of XML writing code.
  360. # Important information about the naming scheme. Used for identifying formats
  361. # in XML.
  362. _bitmapGlyphSubclassPrefix = "ebdt_bitmap_format_"
  363. class BitmapGlyph(object):
  364. # For the external file format. This can be changed in subclasses. This way
  365. # when the extfile option is turned on files have the form: glyphName.ext
  366. # The default is just a flat binary file with no meaning.
  367. fileExtension = ".bin"
  368. # Keep track of reading and writing of various forms.
  369. xmlDataFunctions = {
  370. "raw": (_writeRawImageData, _readRawImageData),
  371. "row": (_writeRowImageData, _readRowImageData),
  372. "bitwise": (_writeBitwiseImageData, _readBitwiseImageData),
  373. "extfile": (_writeExtFileImageData, _readExtFileImageData),
  374. }
  375. def __init__(self, data, ttFont):
  376. self.data = data
  377. self.ttFont = ttFont
  378. # TODO Currently non-lazy decompilation is untested here...
  379. # if not ttFont.lazy:
  380. # self.decompile()
  381. # del self.data
  382. def __getattr__(self, attr):
  383. # Allow lazy decompile.
  384. if attr[:2] == "__":
  385. raise AttributeError(attr)
  386. if attr == "data":
  387. raise AttributeError(attr)
  388. self.decompile()
  389. del self.data
  390. return getattr(self, attr)
  391. def ensureDecompiled(self, recurse=False):
  392. if hasattr(self, "data"):
  393. self.decompile()
  394. del self.data
  395. # Not a fan of this but it is needed for safer safety checking.
  396. def getFormat(self):
  397. return safeEval(self.__class__.__name__[len(_bitmapGlyphSubclassPrefix) :])
  398. def toXML(self, strikeIndex, glyphName, writer, ttFont):
  399. writer.begintag(self.__class__.__name__, [("name", glyphName)])
  400. writer.newline()
  401. self.writeMetrics(writer, ttFont)
  402. # Use the internal write method to write using the correct output format.
  403. self.writeData(strikeIndex, glyphName, writer, ttFont)
  404. writer.endtag(self.__class__.__name__)
  405. writer.newline()
  406. def fromXML(self, name, attrs, content, ttFont):
  407. self.readMetrics(name, attrs, content, ttFont)
  408. for element in content:
  409. if not isinstance(element, tuple):
  410. continue
  411. name, attr, content = element
  412. if not name.endswith("imagedata"):
  413. continue
  414. # Chop off 'imagedata' from the tag to get just the option.
  415. option = name[: -len("imagedata")]
  416. assert option in self.__class__.xmlDataFunctions
  417. self.readData(name, attr, content, ttFont)
  418. # Some of the glyphs have the metrics. This allows for metrics to be
  419. # added if the glyph format has them. Default behavior is to do nothing.
  420. def writeMetrics(self, writer, ttFont):
  421. pass
  422. # The opposite of write metrics.
  423. def readMetrics(self, name, attrs, content, ttFont):
  424. pass
  425. def writeData(self, strikeIndex, glyphName, writer, ttFont):
  426. try:
  427. writeFunc, readFunc = self.__class__.xmlDataFunctions[
  428. ttFont.bitmapGlyphDataFormat
  429. ]
  430. except KeyError:
  431. writeFunc = _writeRawImageData
  432. writeFunc(strikeIndex, glyphName, self, writer, ttFont)
  433. def readData(self, name, attrs, content, ttFont):
  434. # Chop off 'imagedata' from the tag to get just the option.
  435. option = name[: -len("imagedata")]
  436. writeFunc, readFunc = self.__class__.xmlDataFunctions[option]
  437. readFunc(self, name, attrs, content, ttFont)
  438. # A closure for creating a mixin for the two types of metrics handling.
  439. # Most of the code is very similar so its easier to deal with here.
  440. # Everything works just by passing the class that the mixin is for.
  441. def _createBitmapPlusMetricsMixin(metricsClass):
  442. # Both metrics names are listed here to make meaningful error messages.
  443. metricStrings = [BigGlyphMetrics.__name__, SmallGlyphMetrics.__name__]
  444. curMetricsName = metricsClass.__name__
  445. # Find which metrics this is for and determine the opposite name.
  446. metricsId = metricStrings.index(curMetricsName)
  447. oppositeMetricsName = metricStrings[1 - metricsId]
  448. class BitmapPlusMetricsMixin(object):
  449. def writeMetrics(self, writer, ttFont):
  450. self.metrics.toXML(writer, ttFont)
  451. def readMetrics(self, name, attrs, content, ttFont):
  452. for element in content:
  453. if not isinstance(element, tuple):
  454. continue
  455. name, attrs, content = element
  456. if name == curMetricsName:
  457. self.metrics = metricsClass()
  458. self.metrics.fromXML(name, attrs, content, ttFont)
  459. elif name == oppositeMetricsName:
  460. log.warning(
  461. "Warning: %s being ignored in format %d.",
  462. oppositeMetricsName,
  463. self.getFormat(),
  464. )
  465. return BitmapPlusMetricsMixin
  466. # Since there are only two types of mixin's just create them here.
  467. BitmapPlusBigMetricsMixin = _createBitmapPlusMetricsMixin(BigGlyphMetrics)
  468. BitmapPlusSmallMetricsMixin = _createBitmapPlusMetricsMixin(SmallGlyphMetrics)
  469. # Data that is bit aligned can be tricky to deal with. These classes implement
  470. # helper functionality for dealing with the data and getting a particular row
  471. # of bitwise data. Also helps implement fancy data export/import in XML.
  472. class BitAlignedBitmapMixin(object):
  473. def _getBitRange(self, row, bitDepth, metrics):
  474. rowBits = bitDepth * metrics.width
  475. bitOffset = row * rowBits
  476. return (bitOffset, bitOffset + rowBits)
  477. def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False):
  478. if metrics is None:
  479. metrics = self.metrics
  480. assert 0 <= row and row < metrics.height, "Illegal row access in bitmap"
  481. # Loop through each byte. This can cover two bytes in the original data or
  482. # a single byte if things happen to be aligned. The very last entry might
  483. # not be aligned so take care to trim the binary data to size and pad with
  484. # zeros in the row data. Bit aligned data is somewhat tricky.
  485. #
  486. # Example of data cut. Data cut represented in x's.
  487. # '|' represents byte boundary.
  488. # data = ...0XX|XXXXXX00|000... => XXXXXXXX
  489. # or
  490. # data = ...0XX|XXXX0000|000... => XXXXXX00
  491. # or
  492. # data = ...000|XXXXXXXX|000... => XXXXXXXX
  493. # or
  494. # data = ...000|00XXXX00|000... => XXXX0000
  495. #
  496. dataList = []
  497. bitRange = self._getBitRange(row, bitDepth, metrics)
  498. stepRange = bitRange + (8,)
  499. for curBit in range(*stepRange):
  500. endBit = min(curBit + 8, bitRange[1])
  501. numBits = endBit - curBit
  502. cutPoint = curBit % 8
  503. firstByteLoc = curBit // 8
  504. secondByteLoc = endBit // 8
  505. if firstByteLoc < secondByteLoc:
  506. numBitsCut = 8 - cutPoint
  507. else:
  508. numBitsCut = endBit - curBit
  509. curByte = _reverseBytes(self.imageData[firstByteLoc])
  510. firstHalf = byteord(curByte) >> cutPoint
  511. firstHalf = ((1 << numBitsCut) - 1) & firstHalf
  512. newByte = firstHalf
  513. if firstByteLoc < secondByteLoc and secondByteLoc < len(self.imageData):
  514. curByte = _reverseBytes(self.imageData[secondByteLoc])
  515. secondHalf = byteord(curByte) << numBitsCut
  516. newByte = (firstHalf | secondHalf) & ((1 << numBits) - 1)
  517. dataList.append(bytechr(newByte))
  518. # The way the data is kept is opposite the algorithm used.
  519. data = bytesjoin(dataList)
  520. if not reverseBytes:
  521. data = _reverseBytes(data)
  522. return data
  523. def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False):
  524. if metrics is None:
  525. metrics = self.metrics
  526. if not reverseBytes:
  527. dataRows = list(map(_reverseBytes, dataRows))
  528. # Keep track of a list of ordinal values as they are easier to modify
  529. # than a list of strings. Map to actual strings later.
  530. numBytes = (self._getBitRange(len(dataRows), bitDepth, metrics)[0] + 7) // 8
  531. ordDataList = [0] * numBytes
  532. for row, data in enumerate(dataRows):
  533. bitRange = self._getBitRange(row, bitDepth, metrics)
  534. stepRange = bitRange + (8,)
  535. for curBit, curByte in zip(range(*stepRange), data):
  536. endBit = min(curBit + 8, bitRange[1])
  537. cutPoint = curBit % 8
  538. firstByteLoc = curBit // 8
  539. secondByteLoc = endBit // 8
  540. if firstByteLoc < secondByteLoc:
  541. numBitsCut = 8 - cutPoint
  542. else:
  543. numBitsCut = endBit - curBit
  544. curByte = byteord(curByte)
  545. firstByte = curByte & ((1 << numBitsCut) - 1)
  546. ordDataList[firstByteLoc] |= firstByte << cutPoint
  547. if firstByteLoc < secondByteLoc and secondByteLoc < numBytes:
  548. secondByte = (curByte >> numBitsCut) & ((1 << 8 - numBitsCut) - 1)
  549. ordDataList[secondByteLoc] |= secondByte
  550. # Save the image data with the bits going the correct way.
  551. self.imageData = _reverseBytes(bytesjoin(map(bytechr, ordDataList)))
  552. class ByteAlignedBitmapMixin(object):
  553. def _getByteRange(self, row, bitDepth, metrics):
  554. rowBytes = (bitDepth * metrics.width + 7) // 8
  555. byteOffset = row * rowBytes
  556. return (byteOffset, byteOffset + rowBytes)
  557. def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False):
  558. if metrics is None:
  559. metrics = self.metrics
  560. assert 0 <= row and row < metrics.height, "Illegal row access in bitmap"
  561. byteRange = self._getByteRange(row, bitDepth, metrics)
  562. data = self.imageData[slice(*byteRange)]
  563. if reverseBytes:
  564. data = _reverseBytes(data)
  565. return data
  566. def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False):
  567. if metrics is None:
  568. metrics = self.metrics
  569. if reverseBytes:
  570. dataRows = map(_reverseBytes, dataRows)
  571. self.imageData = bytesjoin(dataRows)
  572. class ebdt_bitmap_format_1(
  573. ByteAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph
  574. ):
  575. def decompile(self):
  576. self.metrics = SmallGlyphMetrics()
  577. dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics)
  578. self.imageData = data
  579. def compile(self, ttFont):
  580. data = sstruct.pack(smallGlyphMetricsFormat, self.metrics)
  581. return data + self.imageData
  582. class ebdt_bitmap_format_2(
  583. BitAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph
  584. ):
  585. def decompile(self):
  586. self.metrics = SmallGlyphMetrics()
  587. dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics)
  588. self.imageData = data
  589. def compile(self, ttFont):
  590. data = sstruct.pack(smallGlyphMetricsFormat, self.metrics)
  591. return data + self.imageData
  592. class ebdt_bitmap_format_5(BitAlignedBitmapMixin, BitmapGlyph):
  593. def decompile(self):
  594. self.imageData = self.data
  595. def compile(self, ttFont):
  596. return self.imageData
  597. class ebdt_bitmap_format_6(
  598. ByteAlignedBitmapMixin, BitmapPlusBigMetricsMixin, BitmapGlyph
  599. ):
  600. def decompile(self):
  601. self.metrics = BigGlyphMetrics()
  602. dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics)
  603. self.imageData = data
  604. def compile(self, ttFont):
  605. data = sstruct.pack(bigGlyphMetricsFormat, self.metrics)
  606. return data + self.imageData
  607. class ebdt_bitmap_format_7(
  608. BitAlignedBitmapMixin, BitmapPlusBigMetricsMixin, BitmapGlyph
  609. ):
  610. def decompile(self):
  611. self.metrics = BigGlyphMetrics()
  612. dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics)
  613. self.imageData = data
  614. def compile(self, ttFont):
  615. data = sstruct.pack(bigGlyphMetricsFormat, self.metrics)
  616. return data + self.imageData
  617. class ComponentBitmapGlyph(BitmapGlyph):
  618. def toXML(self, strikeIndex, glyphName, writer, ttFont):
  619. writer.begintag(self.__class__.__name__, [("name", glyphName)])
  620. writer.newline()
  621. self.writeMetrics(writer, ttFont)
  622. writer.begintag("components")
  623. writer.newline()
  624. for curComponent in self.componentArray:
  625. curComponent.toXML(writer, ttFont)
  626. writer.endtag("components")
  627. writer.newline()
  628. writer.endtag(self.__class__.__name__)
  629. writer.newline()
  630. def fromXML(self, name, attrs, content, ttFont):
  631. self.readMetrics(name, attrs, content, ttFont)
  632. for element in content:
  633. if not isinstance(element, tuple):
  634. continue
  635. name, attr, content = element
  636. if name == "components":
  637. self.componentArray = []
  638. for compElement in content:
  639. if not isinstance(compElement, tuple):
  640. continue
  641. name, attrs, content = compElement
  642. if name == "ebdtComponent":
  643. curComponent = EbdtComponent()
  644. curComponent.fromXML(name, attrs, content, ttFont)
  645. self.componentArray.append(curComponent)
  646. else:
  647. log.warning("'%s' being ignored in component array.", name)
  648. class ebdt_bitmap_format_8(BitmapPlusSmallMetricsMixin, ComponentBitmapGlyph):
  649. def decompile(self):
  650. self.metrics = SmallGlyphMetrics()
  651. dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics)
  652. data = data[1:]
  653. (numComponents,) = struct.unpack(">H", data[:2])
  654. data = data[2:]
  655. self.componentArray = []
  656. for i in range(numComponents):
  657. curComponent = EbdtComponent()
  658. dummy, data = sstruct.unpack2(ebdtComponentFormat, data, curComponent)
  659. curComponent.name = self.ttFont.getGlyphName(curComponent.glyphCode)
  660. self.componentArray.append(curComponent)
  661. def compile(self, ttFont):
  662. dataList = []
  663. dataList.append(sstruct.pack(smallGlyphMetricsFormat, self.metrics))
  664. dataList.append(b"\0")
  665. dataList.append(struct.pack(">H", len(self.componentArray)))
  666. for curComponent in self.componentArray:
  667. curComponent.glyphCode = ttFont.getGlyphID(curComponent.name)
  668. dataList.append(sstruct.pack(ebdtComponentFormat, curComponent))
  669. return bytesjoin(dataList)
  670. class ebdt_bitmap_format_9(BitmapPlusBigMetricsMixin, ComponentBitmapGlyph):
  671. def decompile(self):
  672. self.metrics = BigGlyphMetrics()
  673. dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics)
  674. (numComponents,) = struct.unpack(">H", data[:2])
  675. data = data[2:]
  676. self.componentArray = []
  677. for i in range(numComponents):
  678. curComponent = EbdtComponent()
  679. dummy, data = sstruct.unpack2(ebdtComponentFormat, data, curComponent)
  680. curComponent.name = self.ttFont.getGlyphName(curComponent.glyphCode)
  681. self.componentArray.append(curComponent)
  682. def compile(self, ttFont):
  683. dataList = []
  684. dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
  685. dataList.append(struct.pack(">H", len(self.componentArray)))
  686. for curComponent in self.componentArray:
  687. curComponent.glyphCode = ttFont.getGlyphID(curComponent.name)
  688. dataList.append(sstruct.pack(ebdtComponentFormat, curComponent))
  689. return bytesjoin(dataList)
  690. # Dictionary of bitmap formats to the class representing that format
  691. # currently only the ones listed in this map are the ones supported.
  692. ebdt_bitmap_classes = {
  693. 1: ebdt_bitmap_format_1,
  694. 2: ebdt_bitmap_format_2,
  695. 5: ebdt_bitmap_format_5,
  696. 6: ebdt_bitmap_format_6,
  697. 7: ebdt_bitmap_format_7,
  698. 8: ebdt_bitmap_format_8,
  699. 9: ebdt_bitmap_format_9,
  700. }