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