blob: f119291916e21df6baf10cc7bb698c2ca559c157 [file] [log] [blame]
Behdad Esfahbod1ae29592014-01-14 15:07:50 +08001from __future__ import print_function, division, absolute_import
Behdad Esfahbod30e691e2013-11-27 17:27:45 -05002from fontTools.misc.py23 import *
Behdad Esfahbod8413c102013-09-17 16:59:39 -04003from fontTools.misc import sstruct
Matt Fontainec33b0a22013-08-19 14:13:05 -04004from fontTools.misc.textTools import safeEval, readHex, hexStr, deHexStr
Behdad Esfahbod2b06aaa2013-11-27 02:34:11 -05005from .BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGlyphMetrics, smallGlyphMetricsFormat
Behdad Esfahbod30e691e2013-11-27 17:27:45 -05006from . import DefaultTable
7import itertools
8import os
9import struct
Matt Fontainec33b0a22013-08-19 14:13:05 -040010
11ebdtTableVersionFormat = """
12 > # big endian
13 version: 16.16F
14"""
15
16ebdtComponentFormat = """
17 > # big endian
18 glyphCode: H
19 xOffset: b
20 yOffset: b
21"""
22
23class 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 Esfahbod4b775ee2013-11-27 03:48:06 -050054 dataIter = zip(indexSubTable.names, indexSubTable.locations)
Matt Fontainec33b0a22013-08-19 14:13:05 -040055 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 Esfahbod4b775ee2013-11-27 03:48:06 -050084 for curStrike, curGlyphDict in zip(locator.strikes, self.strikeData):
Matt Fontainec33b0a22013-08-19 14:13:05 -040085 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 Esfahbod18316aa2013-11-27 21:17:35 -0500116 return bytesjoin(dataList)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400117
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 Esfahbod4b775ee2013-11-27 03:48:06 -0500124 for curStrike, curGlyphDict in zip(locator.strikes, self.strikeData):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400125 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 Esfahbod3a9fd302013-11-27 03:19:32 -0500147 def fromXML(self, name, attrs, content, ttFont):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400148 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 Esfahbodb774f9f2013-11-27 05:17:37 -0500157 if not isinstance(element, tuple):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400158 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 Esfahbod3a9fd302013-11-27 03:19:32 -0500165 curGlyph.fromXML(name, attrs, content, ttFont)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400166 assert glyphName not in bitmapGlyphDict, "Duplicate glyphs with the same name '%s' in the same strike." % glyphName
167 bitmapGlyphDict[glyphName] = curGlyph
168 else:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500169 print("Warning: %s being ignored by %s", name, self.__class__.__name__)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400170
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 Esfahbod9e6ef942013-12-04 16:31:44 -0500175 assert self.strikeData[strikeIndex] is None, "Duplicate strike EBDT indices."
Matt Fontainec33b0a22013-08-19 14:13:05 -0400176 self.strikeData[strikeIndex] = bitmapGlyphDict
177
Behdad Esfahbode388db52013-11-28 14:26:58 -0500178class EbdtComponent(object):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400179
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 Esfahbod3a9fd302013-11-27 03:19:32 -0500189 def fromXML(self, name, attrs, content, ttFont):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400190 self.name = attrs['name']
191 componentNames = set(sstruct.getformat(ebdtComponentFormat)[1][1:])
192 for element in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -0500193 if not isinstance(element, tuple):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400194 continue
195 name, attrs, content = element
196 if name in componentNames:
197 vars(self)[name] = safeEval(attrs['value'])
198 else:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500199 print("Warning: unknown name '%s' being ignored by EbdtComponent." % name)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400200
201# Helper functions for dealing with binary.
202
203def _data2binary(data, numBits):
204 binaryList = []
205 for curByte in data:
Behdad Esfahbod319c5fd2013-11-27 18:13:48 -0500206 value = byteord(curByte)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400207 numBitsCut = min(8, numBits)
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500208 for i in range(numBitsCut):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400209 if value & 0x1:
210 binaryList.append('1')
211 else:
212 binaryList.append('0')
213 value = value >> 1
214 numBits -= numBitsCut
Behdad Esfahbod18316aa2013-11-27 21:17:35 -0500215 return strjoin(binaryList)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400216
217def _binary2data(binary):
218 byteList = []
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500219 for bitLoc in range(0, len(binary), 8):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400220 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 Esfahbodb7a2d792013-11-27 15:19:40 -0500226 byteList.append(bytechr(curByte))
Behdad Esfahbod18316aa2013-11-27 21:17:35 -0500227 return bytesjoin(byteList)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400228
229def _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
243def _reverseBytes(data):
244 if len(data) != 1:
Behdad Esfahbod18316aa2013-11-27 21:17:35 -0500245 return bytesjoin(map(_reverseBytes, data))
Behdad Esfahbod319c5fd2013-11-27 18:13:48 -0500246 byte = byteord(data)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400247 result = 0
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500248 for i in range(8):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400249 result = result << 1
250 result |= byte & 1
251 byte = byte >> 1
Behdad Esfahbodb7a2d792013-11-27 15:19:40 -0500252 return bytechr(result)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400253
254# This section of code is for reading and writing image data to/from XML.
255
256def _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 Esfahbod3a9fd302013-11-27 03:19:32 -0500263def _readRawImageData(bitmapObject, name, attrs, content, ttFont):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400264 bitmapObject.imageData = readHex(content)
265
266def _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 Esfahbod97dea0a2013-11-27 03:34:48 -0500274 for curRow in range(metrics.height):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400275 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 Esfahbod3a9fd302013-11-27 03:19:32 -0500281def _readRowImageData(bitmapObject, name, attrs, content, ttFont):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400282 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 Esfahbodb774f9f2013-11-27 05:17:37 -0500289 if not isinstance(element, tuple):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400290 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
297def _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 Esfahbod97dea0a2013-11-27 03:34:48 -0500308 for curRow in range(metrics.height):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400309 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 Esfahbod18316aa2013-11-27 21:17:35 -0500312 rowData = strjoin(map(binaryConv.get, rowData))
Matt Fontainec33b0a22013-08-19 14:13:05 -0400313 writer.simpletag('row', value=rowData)
314 writer.newline()
315 writer.endtag('bitwiseimagedata')
316 writer.newline()
317
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500318def _readBitwiseImageData(bitmapObject, name, attrs, content, ttFont):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400319 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 Esfahbodb774f9f2013-11-27 05:17:37 -0500330 if not isinstance(element, tuple):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400331 continue
332 name, attr, content = element
333 if name == 'row':
Behdad Esfahbod4b775ee2013-11-27 03:48:06 -0500334 mapParams = zip(attr['value'], itertools.repeat('1'))
Behdad Esfahbod18316aa2013-11-27 21:17:35 -0500335 rowData = strjoin(itertools.starmap(binaryConv.get, mapParams))
Matt Fontainec33b0a22013-08-19 14:13:05 -0400336 dataRows.append(_binary2data(rowData))
337
338 bitmapObject.setRows(dataRows, bitDepth=bitDepth, metrics=metrics, reverseBytes=True)
339
340def _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 Esfahbod3a9fd302013-11-27 03:19:32 -0500356def _readExtFileImageData(bitmapObject, name, attrs, content, ttFont):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400357 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 Esfahbode388db52013-11-28 14:26:58 -0500367class BitmapGlyph(object):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400368
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 Esfahbod4be8db02013-11-27 23:06:20 -0500385 # TODO Currently non-lazy decompilation is untested here...
386 #if not ttFont.lazy:
387 # self.decompile()
388 # del self.data
Matt Fontainec33b0a22013-08-19 14:13:05 -0400389
390 def __getattr__(self, attr):
391 # Allow lazy decompile.
392 if attr[:2] == '__':
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500393 raise AttributeError(attr)
Behdad Esfahbod3d8d5cd2013-11-24 19:19:50 -0500394 if not hasattr(self, "data"):
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500395 raise AttributeError(attr)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400396 self.decompile()
Behdad Esfahbod3d8d5cd2013-11-24 19:19:50 -0500397 del self.data
Matt Fontainec33b0a22013-08-19 14:13:05 -0400398 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 Esfahbod3a9fd302013-11-27 03:19:32 -0500415 def fromXML(self, name, attrs, content, ttFont):
416 self.readMetrics(name, attrs, content, ttFont)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400417 for element in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -0500418 if not isinstance(element, tuple):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400419 continue
420 name, attr, content = element
Behdad Esfahbodfaaca762013-11-27 23:37:57 -0500421 if not name.endswith('imagedata'):
422 continue
Matt Fontainec33b0a22013-08-19 14:13:05 -0400423 # Chop off 'imagedata' from the tag to get just the option.
424 option = name[:-len('imagedata')]
Behdad Esfahbodfaaca762013-11-27 23:37:57 -0500425 assert option in self.__class__.xmlDataFunctions
426 self.readData(name, attrs, content, ttFont)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400427
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 Esfahbod3a9fd302013-11-27 03:19:32 -0500434 def readMetrics(self, name, attrs, content, ttFont):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400435 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 Esfahbod3a9fd302013-11-27 03:19:32 -0500444 def readData(self, name, attrs, content, ttFont):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400445 # Chop off 'imagedata' from the tag to get just the option.
446 option = name[:-len('imagedata')]
447 writeFunc, readFunc = self.__class__.xmlDataFunctions[option]
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500448 readFunc(self, name, attrs, content, ttFont)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400449
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.
454def _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 Esfahbode388db52013-11-28 14:26:58 -0500462 class BitmapPlusMetricsMixin(object):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400463
464 def writeMetrics(self, writer, ttFont):
465 self.metrics.toXML(writer, ttFont)
466
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500467 def readMetrics(self, name, attrs, content, ttFont):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400468 for element in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -0500469 if not isinstance(element, tuple):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400470 continue
471 name, attrs, content = element
472 if name == curMetricsName:
473 self.metrics = metricsClass()
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500474 self.metrics.fromXML(name, attrs, content, ttFont)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400475 elif name == oppositeMetricsName:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500476 print("Warning: %s being ignored in format %d." % oppositeMetricsName, self.getFormat())
Matt Fontainec33b0a22013-08-19 14:13:05 -0400477
478 return BitmapPlusMetricsMixin
479
480# Since there are only two types of mixin's just create them here.
481BitmapPlusBigMetricsMixin = _createBitmapPlusMetricsMixin(BigGlyphMetrics)
482BitmapPlusSmallMetricsMixin = _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 Esfahbode388db52013-11-28 14:26:58 -0500487class BitAlignedBitmapMixin(object):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400488
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 Esfahbod9e6ef942013-12-04 16:31:44 -0500495 if metrics is None:
Matt Fontainec33b0a22013-08-19 14:13:05 -0400496 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 Esfahbod97dea0a2013-11-27 03:34:48 -0500517 for curBit in range(*stepRange):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400518 endBit = min(curBit+8, bitRange[1])
519 numBits = endBit - curBit
520 cutPoint = curBit % 8
Behdad Esfahbod32c10ee2013-11-27 17:46:17 -0500521 firstByteLoc = curBit // 8
522 secondByteLoc = endBit // 8
Matt Fontainec33b0a22013-08-19 14:13:05 -0400523 if firstByteLoc < secondByteLoc:
524 numBitsCut = 8 - cutPoint
525 else:
526 numBitsCut = endBit - curBit
527 curByte = _reverseBytes(self.imageData[firstByteLoc])
Behdad Esfahbod319c5fd2013-11-27 18:13:48 -0500528 firstHalf = byteord(curByte) >> cutPoint
Matt Fontainec33b0a22013-08-19 14:13:05 -0400529 firstHalf = ((1<<numBitsCut)-1) & firstHalf
530 newByte = firstHalf
531 if firstByteLoc < secondByteLoc and secondByteLoc < len(self.imageData):
532 curByte = _reverseBytes(self.imageData[secondByteLoc])
Behdad Esfahbod319c5fd2013-11-27 18:13:48 -0500533 secondHalf = byteord(curByte) << numBitsCut
Matt Fontainec33b0a22013-08-19 14:13:05 -0400534 newByte = (firstHalf | secondHalf) & ((1<<numBits)-1)
Behdad Esfahbodb7a2d792013-11-27 15:19:40 -0500535 dataList.append(bytechr(newByte))
Matt Fontainec33b0a22013-08-19 14:13:05 -0400536
537 # The way the data is kept is opposite the algorithm used.
Behdad Esfahbod18316aa2013-11-27 21:17:35 -0500538 data = bytesjoin(dataList)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400539 if not reverseBytes:
540 data = _reverseBytes(data)
541 return data
542
543 def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500544 if metrics is None:
Matt Fontainec33b0a22013-08-19 14:13:05 -0400545 metrics = self.metrics
546 if not reverseBytes:
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500547 dataRows = list(map(_reverseBytes, dataRows))
Matt Fontainec33b0a22013-08-19 14:13:05 -0400548
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 Esfahbod32c10ee2013-11-27 17:46:17 -0500551 numBytes = (self._getBitRange(len(dataRows), bitDepth, metrics)[0] + 7) // 8
Matt Fontainec33b0a22013-08-19 14:13:05 -0400552 ordDataList = [0] * numBytes
553 for row, data in enumerate(dataRows):
554 bitRange = self._getBitRange(row, bitDepth, metrics)
555 stepRange = bitRange + (8,)
Behdad Esfahbod4b775ee2013-11-27 03:48:06 -0500556 for curBit, curByte in zip(range(*stepRange), data):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400557 endBit = min(curBit+8, bitRange[1])
558 cutPoint = curBit % 8
Behdad Esfahbod32c10ee2013-11-27 17:46:17 -0500559 firstByteLoc = curBit // 8
560 secondByteLoc = endBit // 8
Matt Fontainec33b0a22013-08-19 14:13:05 -0400561 if firstByteLoc < secondByteLoc:
562 numBitsCut = 8 - cutPoint
563 else:
564 numBitsCut = endBit - curBit
Behdad Esfahbod319c5fd2013-11-27 18:13:48 -0500565 curByte = byteord(curByte)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400566 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 Esfahbod18316aa2013-11-27 21:17:35 -0500573 self.imageData = _reverseBytes(bytesjoin(map(bytechr, ordDataList)))
Matt Fontainec33b0a22013-08-19 14:13:05 -0400574
Behdad Esfahbode388db52013-11-28 14:26:58 -0500575class ByteAlignedBitmapMixin(object):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400576
577 def _getByteRange(self, row, bitDepth, metrics):
Behdad Esfahbod32c10ee2013-11-27 17:46:17 -0500578 rowBytes = (bitDepth * metrics.width + 7) // 8
Matt Fontainec33b0a22013-08-19 14:13:05 -0400579 byteOffset = row * rowBytes
580 return (byteOffset, byteOffset+rowBytes)
581
582 def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500583 if metrics is None:
Matt Fontainec33b0a22013-08-19 14:13:05 -0400584 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 Esfahbod9e6ef942013-12-04 16:31:44 -0500593 if metrics is None:
Matt Fontainec33b0a22013-08-19 14:13:05 -0400594 metrics = self.metrics
595 if reverseBytes:
596 dataRows = map(_reverseBytes, dataRows)
Behdad Esfahbod18316aa2013-11-27 21:17:35 -0500597 self.imageData = bytesjoin(dataRows)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400598
599class 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
611class 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
623class 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
631class 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
643class 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
655class 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 Esfahbod3a9fd302013-11-27 03:19:32 -0500673 def fromXML(self, name, attrs, content, ttFont):
674 self.readMetrics(name, attrs, content, ttFont)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400675 for element in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -0500676 if not isinstance(element, tuple):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400677 continue
678 name, attr, content = element
679 if name == 'components':
680 self.componentArray = []
681 for compElement in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -0500682 if not isinstance(compElement, tuple):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400683 continue
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500684 name, attrs, content = compElement
Matt Fontainec33b0a22013-08-19 14:13:05 -0400685 if name == 'ebdtComponent':
686 curComponent = EbdtComponent()
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500687 curComponent.fromXML(name, attrs, content, ttFont)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400688 self.componentArray.append(curComponent)
689 else:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500690 print("Warning: '%s' being ignored in component array." % name)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400691
692
693class 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 Esfahbod97dea0a2013-11-27 03:34:48 -0500703 for i in range(numComponents):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400704 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 Esfahbod18316aa2013-11-27 21:17:35 -0500712 dataList.append(b'\0')
Matt Fontainec33b0a22013-08-19 14:13:05 -0400713 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 Esfahbod18316aa2013-11-27 21:17:35 -0500717 return bytesjoin(dataList)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400718
719
720class 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 Esfahbod97dea0a2013-11-27 03:34:48 -0500728 for i in range(numComponents):
Matt Fontainec33b0a22013-08-19 14:13:05 -0400729 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 Esfahbod18316aa2013-11-27 21:17:35 -0500741 return bytesjoin(dataList)
Matt Fontainec33b0a22013-08-19 14:13:05 -0400742
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.
746ebdt_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 }