diff --git a/Doc/documentation.html b/Doc/documentation.html index ccb9a7d9f..748f5f749 100644 --- a/Doc/documentation.html +++ b/Doc/documentation.html @@ -42,7 +42,7 @@ When using TTX from the command line there are a bunch of extra options, these a The following tables are currently supported:
-BASE, CFF, DSIG, GDEF, GMAP, GPKG, GPOS, GSUB, JSTF, LTSH, META, OS/2, SING, TSI0, TSI1, TSI2, TSI3, TSI5, TSIB, TSID, TSIJ, TSIP, TSIS, TSIV, VORG, cmap, cvt, fpgm, gasp, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, post, prep, vhea and vmtx +BASE, CFF, DSIG, EBDT, EBLC, GDEF, GMAP, GPKG, GPOS, GSUB, JSTF, LTSH, META, OS/2, SING, TSI0, TSI1, TSI2, TSI3, TSI5, TSIB, TSID, TSIJ, TSIP, TSIS, TSIV, VORG, cmap, cvt, fpgm, gasp, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, post, prep, vhea and vmtx
Other tables are dumped as hexadecimal data. diff --git a/Lib/fontTools/ttLib/__init__.py b/Lib/fontTools/ttLib/__init__.py index 1b8117f8e..128bcea9a 100644 --- a/Lib/fontTools/ttLib/__init__.py +++ b/Lib/fontTools/ttLib/__init__.py @@ -211,7 +211,8 @@ class TTFont: file.close() def saveXML(self, fileOrPath, progress=None, - tables=None, skipTables=None, splitTables=0, disassembleInstructions=1): + tables=None, skipTables=None, splitTables=0, disassembleInstructions=1, + bitmapGlyphDataFormat='raw'): """Export the font as TTX (an XML-based text file), or as a series of text files when splitTables is true. In the latter case, the 'fileOrPath' argument should be a path to a directory. @@ -223,6 +224,7 @@ class TTFont: import xmlWriter self.disassembleInstructions = disassembleInstructions + self.bitmapGlyphDataFormat = bitmapGlyphDataFormat if not tables: tables = self.keys() if "GlyphOrder" not in tables: diff --git a/Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py b/Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py new file mode 100644 index 000000000..9cf466797 --- /dev/null +++ b/Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py @@ -0,0 +1,57 @@ +# Since bitmap glyph metrics are shared between EBLC and EBDT +# this class gets its own python file. +import sstruct +from types import TupleType +from fontTools.misc.textTools import safeEval + + +bigGlyphMetricsFormat = """ + > # big endian + height: B + width: B + horiBearingX: b + horiBearingY: b + horiAdvance: B + vertBearingX: b + vertBearingY: b + vertAdvance: B +""" + +smallGlyphMetricsFormat = """ + > # big endian + height: B + width: B + BearingX: b + BearingY: b + Advance: B +""" + +class BitmapGlyphMetrics: + + def toXML(self, writer, ttFont): + writer.begintag(self.__class__.__name__) + writer.newline() + for metricName in sstruct.getformat(self.__class__.binaryFormat)[1]: + writer.simpletag(metricName, value=getattr(self, metricName)) + writer.newline() + writer.endtag(self.__class__.__name__) + writer.newline() + + def fromXML(self, (name, attrs, content), ttFont): + metricNames = set(sstruct.getformat(self.__class__.binaryFormat)[1]) + for element in content: + if type(element) != TupleType: + continue + name, attrs, content = element + # Make sure this is a metric that is needed by GlyphMetrics. + if name in metricNames: + vars(self)[name] = safeEval(attrs['value']) + else: + print "Warning: unknown name '%s' being ignored in %s." % name, self.__class__.__name__ + + +class BigGlyphMetrics(BitmapGlyphMetrics): + binaryFormat = bigGlyphMetricsFormat + +class SmallGlyphMetrics(BitmapGlyphMetrics): + binaryFormat = smallGlyphMetricsFormat diff --git a/Lib/fontTools/ttLib/tables/E_B_D_T_.py b/Lib/fontTools/ttLib/tables/E_B_D_T_.py new file mode 100644 index 000000000..5e6269373 --- /dev/null +++ b/Lib/fontTools/ttLib/tables/E_B_D_T_.py @@ -0,0 +1,749 @@ + +import DefaultTable +import os +import string +import struct +import sstruct +import itertools +from types import TupleType +from fontTools.misc.textTools import safeEval, readHex, hexStr, deHexStr +from BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGlyphMetrics, smallGlyphMetricsFormat + +ebdtTableVersionFormat = """ + > # big endian + version: 16.16F +""" + +ebdtComponentFormat = """ + > # big endian + glyphCode: H + xOffset: b + yOffset: b +""" + +class table_E_B_D_T_(DefaultTable.DefaultTable): + + # Keep a reference to the name of the data locator table. + locatorName = 'EBLC' + + # This method can be overridden in subclasses to support new formats + # without changing the other implementation. Also can be used as a + # convenience method for coverting a font file to an alternative format. + def getImageFormatClass(self, imageFormat): + return ebdt_bitmap_classes[imageFormat] + + def decompile(self, data, ttFont): + # Get the version but don't advance the slice. + # Most of the lookup for this table is done relative + # to the begining so slice by the offsets provided + # in the EBLC table. + sstruct.unpack2(ebdtTableVersionFormat, data, self) + + # Keep a dict of glyphs that have been seen so they aren't remade. + # This dict maps intervals of data to the BitmapGlyph. + glyphDict = {} + + # Pull out the EBLC table and loop through glyphs. + # A strike is a concept that spans both tables. + # The actual bitmap data is stored in the EBDT. + locator = ttFont[self.__class__.locatorName] + self.strikeData = [] + for curStrike in locator.strikes: + bitmapGlyphDict = {} + self.strikeData.append(bitmapGlyphDict) + for indexSubTable in curStrike.indexSubTables: + dataIter = itertools.izip(indexSubTable.names, indexSubTable.locations) + for curName, curLoc in dataIter: + # Don't create duplicate data entries for the same glyphs. + # Instead just use the structures that already exist if they exist. + if curLoc in glyphDict: + curGlyph = glyphDict[curLoc] + else: + curGlyphData = data[slice(*curLoc)] + imageFormatClass = self.getImageFormatClass(indexSubTable.imageFormat) + curGlyph = imageFormatClass(curGlyphData, ttFont) + glyphDict[curLoc] = curGlyph + bitmapGlyphDict[curName] = curGlyph + + def compile(self, ttFont): + + dataList = [] + dataList.append(sstruct.pack(ebdtTableVersionFormat, self)) + dataSize = len(dataList[0]) + + # Keep a dict of glyphs that have been seen so they aren't remade. + # This dict maps the id of the BitmapGlyph to the interval + # in the data. + glyphDict = {} + + # Go through the bitmap glyph data. Just in case the data for a glyph + # changed the size metrics should be recalculated. There are a variety + # of formats and they get stored in the EBLC table. That is why + # recalculation is defered to the EblcIndexSubTable class and just + # pass what is known about bitmap glyphs from this particular table. + locator = ttFont[self.__class__.locatorName] + for curStrike, curGlyphDict in itertools.izip(locator.strikes, self.strikeData): + for curIndexSubTable in curStrike.indexSubTables: + dataLocations = [] + for curName in curIndexSubTable.names: + # Handle the data placement based on seeing the glyph or not. + # Just save a reference to the location if the glyph has already + # been saved in compile. This code assumes that glyphs will only + # be referenced multiple times from indexFormat5. By luck the + # code may still work when referencing poorly ordered fonts with + # duplicate references. If there is a font that is unlucky the + # respective compile methods for the indexSubTables will fail + # their assertions. All fonts seem to follow this assumption. + # More complicated packing may be needed if a counter-font exists. + glyph = curGlyphDict[curName] + objectId = id(glyph) + if objectId not in glyphDict: + data = glyph.compile(ttFont) + data = curIndexSubTable.padBitmapData(data) + startByte = dataSize + dataSize += len(data) + endByte = dataSize + dataList.append(data) + dataLoc = (startByte, endByte) + glyphDict[objectId] = dataLoc + else: + dataLoc = glyphDict[objectId] + dataLocations.append(dataLoc) + # Just use the new data locations in the indexSubTable. + # The respective compile implementations will take care + # of any of the problems in the convertion that may arise. + curIndexSubTable.locations = dataLocations + + return string.join(dataList, "") + + def toXML(self, writer, ttFont): + # When exporting to XML if one of the data export formats + # requires metrics then those metrics may be in the locator. + # In this case populate the bitmaps with "export metrics". + if ttFont.bitmapGlyphDataFormat in ('row', 'bitwise'): + locator = ttFont[self.__class__.locatorName] + for curStrike, curGlyphDict in itertools.izip(locator.strikes, self.strikeData): + for curIndexSubTable in curStrike.indexSubTables: + for curName in curIndexSubTable.names: + glyph = curGlyphDict[curName] + # I'm not sure which metrics have priority here. + # For now if both metrics exist go with glyph metrics. + if hasattr(glyph, 'metrics'): + glyph.exportMetrics = glyph.metrics + else: + glyph.exportMetrics = curIndexSubTable.metrics + glyph.exportBitDepth = curStrike.bitmapSizeTable.bitDepth + + writer.simpletag("header", [('version', self.version)]) + writer.newline() + locator = ttFont[self.__class__.locatorName] + for strikeIndex, bitmapGlyphDict in enumerate(self.strikeData): + writer.begintag('strikedata', [('index', strikeIndex)]) + writer.newline() + for curName, curBitmap in bitmapGlyphDict.items(): + curBitmap.toXML(strikeIndex, curName, writer, ttFont) + writer.endtag('strikedata') + writer.newline() + + def fromXML(self, (name, attrs, content), ttFont): + if name == 'header': + self.version = safeEval(attrs['version']) + elif name == 'strikedata': + if not hasattr(self, 'strikeData'): + self.strikeData = [] + strikeIndex = safeEval(attrs['index']) + + bitmapGlyphDict = {} + for element in content: + if type(element) != TupleType: + continue + name, attrs, content = element + if name[4:].startswith(_bitmapGlyphSubclassPrefix[4:]): + imageFormat = safeEval(name[len(_bitmapGlyphSubclassPrefix):]) + glyphName = attrs['name'] + imageFormatClass = self.getImageFormatClass(imageFormat) + curGlyph = imageFormatClass(None, None) + curGlyph.fromXML(element, ttFont) + assert glyphName not in bitmapGlyphDict, "Duplicate glyphs with the same name '%s' in the same strike." % glyphName + bitmapGlyphDict[glyphName] = curGlyph + else: + print "Warning: %s being ignored by %s", name, self.__class__.__name__ + + # Grow the strike data array to the appropriate size. The XML + # format allows the strike index value to be out of order. + if strikeIndex >= len(self.strikeData): + self.strikeData += [None] * (strikeIndex + 1 - len(self.strikeData)) + assert self.strikeData[strikeIndex] == None, "Duplicate strike EBDT indices." + self.strikeData[strikeIndex] = bitmapGlyphDict + +class EbdtComponent: + + def toXML(self, writer, ttFont): + writer.begintag('ebdtComponent', [('name', self.name)]) + writer.newline() + for componentName in sstruct.getformat(ebdtComponentFormat)[1][1:]: + writer.simpletag(componentName, value=getattr(self, componentName)) + writer.newline() + writer.endtag('ebdtComponent') + writer.newline() + + def fromXML(self, (name, attrs, content), ttFont): + self.name = attrs['name'] + componentNames = set(sstruct.getformat(ebdtComponentFormat)[1][1:]) + for element in content: + if type(element) != TupleType: + continue + name, attrs, content = element + if name in componentNames: + vars(self)[name] = safeEval(attrs['value']) + else: + print "Warning: unknown name '%s' being ignored by EbdtComponent." % name + +# Helper functions for dealing with binary. + +def _data2binary(data, numBits): + binaryList = [] + for curByte in data: + value = ord(curByte) + numBitsCut = min(8, numBits) + for i in xrange(numBitsCut): + if value & 0x1: + binaryList.append('1') + else: + binaryList.append('0') + value = value >> 1 + numBits -= numBitsCut + return string.join(binaryList, "") + +def _binary2data(binary): + byteList = [] + for bitLoc in xrange(0, len(binary), 8): + byteString = binary[bitLoc:bitLoc+8] + curByte = 0 + for curBit in reversed(byteString): + curByte = curByte << 1 + if curBit == '1': + curByte |= 1 + byteList.append(chr(curByte)) + return string.join(byteList, "") + +def _memoize(f): + class memodict(dict): + def __missing__(self, key): + ret = f(key) + if len(key) == 1: + self[key] = ret + return ret + return memodict().__getitem__ + +# 00100111 -> 11100100 per byte, not to be confused with little/big endian. +# Bitmap data per byte is in the order that binary is written on the page +# with the least significant bit as far right as possible. This is the +# opposite of what makes sense algorithmically and hence this function. +@_memoize +def _reverseBytes(data): + if len(data) != 1: + return string.join(map(_reverseBytes, data), "") + byte = ord(data) + result = 0 + for i in xrange(8): + result = result << 1 + result |= byte & 1 + byte = byte >> 1 + return chr(result) + +# This section of code is for reading and writing image data to/from XML. + +def _writeRawImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont): + writer.begintag('rawimagedata') + writer.newline() + writer.dumphex(bitmapObject.imageData) + writer.endtag('rawimagedata') + writer.newline() + +def _readRawImageData(bitmapObject, (name, attrs, content), ttFont): + bitmapObject.imageData = readHex(content) + +def _writeRowImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont): + metrics = bitmapObject.exportMetrics + del bitmapObject.exportMetrics + bitDepth = bitmapObject.exportBitDepth + del bitmapObject.exportBitDepth + + writer.begintag('rowimagedata', bitDepth=bitDepth, width=metrics.width, height=metrics.height) + writer.newline() + for curRow in xrange(metrics.height): + rowData = bitmapObject.getRow(curRow, bitDepth=bitDepth, metrics=metrics) + writer.simpletag('row', value=hexStr(rowData)) + writer.newline() + writer.endtag('rowimagedata') + writer.newline() + +def _readRowImageData(bitmapObject, (name, attrs, content), ttFont): + bitDepth = safeEval(attrs['bitDepth']) + metrics = SmallGlyphMetrics() + metrics.width = safeEval(attrs['width']) + metrics.height = safeEval(attrs['height']) + + dataRows = [] + for element in content: + if type(element) != TupleType: + continue + name, attr, content = element + # Chop off 'imagedata' from the tag to get just the option. + if name == 'row': + dataRows.append(deHexStr(attr['value'])) + bitmapObject.setRows(dataRows, bitDepth=bitDepth, metrics=metrics) + +def _writeBitwiseImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont): + metrics = bitmapObject.exportMetrics + del bitmapObject.exportMetrics + bitDepth = bitmapObject.exportBitDepth + del bitmapObject.exportBitDepth + + # A dict for mapping binary to more readable/artistic ASCII characters. + binaryConv = {'0':'.', '1':'@'} + + writer.begintag('bitwiseimagedata', bitDepth=bitDepth, width=metrics.width, height=metrics.height) + writer.newline() + for curRow in xrange(metrics.height): + rowData = bitmapObject.getRow(curRow, bitDepth=1, metrics=metrics, reverseBytes=True) + rowData = _data2binary(rowData, metrics.width) + # Make the output a readable ASCII art form. + rowData = string.join(map(binaryConv.get, rowData), "") + writer.simpletag('row', value=rowData) + writer.newline() + writer.endtag('bitwiseimagedata') + writer.newline() + +def _readBitwiseImageData(bitmapObject, (name, attrs, content), ttFont): + bitDepth = safeEval(attrs['bitDepth']) + metrics = SmallGlyphMetrics() + metrics.width = safeEval(attrs['width']) + metrics.height = safeEval(attrs['height']) + + # A dict for mapping from ASCII to binary. All characters are considered + # a '1' except space, period and '0' which maps to '0'. + binaryConv = {' ':'0', '.':'0', '0':'0'} + + dataRows = [] + for element in content: + if type(element) != TupleType: + continue + name, attr, content = element + if name == 'row': + mapParams = itertools.izip(attr['value'], itertools.repeat('1')) + rowData = string.join(itertools.starmap(binaryConv.get, mapParams), "") + dataRows.append(_binary2data(rowData)) + + bitmapObject.setRows(dataRows, bitDepth=bitDepth, metrics=metrics, reverseBytes=True) + +def _writeExtFileImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont): + folder = 'bitmaps/' + filename = glyphName + bitmapObject.fileExtension + if not os.path.isdir(folder): + os.makedirs(folder) + folder += 'strike%d/' % strikeIndex + if not os.path.isdir(folder): + os.makedirs(folder) + + fullPath = folder + filename + writer.simpletag('extfileimagedata', value=fullPath) + writer.newline() + + with open(fullPath, "wb") as file: + file.write(bitmapObject.imageData) + +def _readExtFileImageData(bitmapObject, (name, attrs, content), ttFont): + fullPath = attrs['value'] + with open(fullPath, "rb") as file: + bitmapObject.imageData = file.read() + +# End of XML writing code. + +# Important information about the naming scheme. Used for identifying formats +# in XML. +_bitmapGlyphSubclassPrefix = 'ebdt_bitmap_format_' + +class BitmapGlyph: + + # For the external file format. This can be changed in subclasses. This way + # when the extfile option is turned on files have the form: glyphName.ext + # The default is just a flat binary file with no meaning. + fileExtension = '.bin' + + # Keep track of reading and writing of various forms. + xmlDataFunctions = { + 'raw': (_writeRawImageData, _readRawImageData), + 'row': (_writeRowImageData, _readRowImageData), + 'bitwise': (_writeBitwiseImageData, _readBitwiseImageData), + 'extfile': (_writeExtFileImageData, _readExtFileImageData), + } + + def __init__(self, data, ttFont): + self.data = data + self.ttFont = ttFont + + def __getattr__(self, attr): + # Allow lazy decompile. + if attr[:2] == '__': + raise AttributeError, attr + if self.data == None: + raise AttributeError, attr + self.decompile() + self.data = None + return getattr(self, attr) + + # Not a fan of this but it is needed for safer safety checking. + def getFormat(self): + return safeEval(self.__class__.__name__[len(_bitmapGlyphSubclassPrefix):]) + + def toXML(self, strikeIndex, glyphName, writer, ttFont): + writer.begintag(self.__class__.__name__, [('name', glyphName)]) + writer.newline() + + self.writeMetrics(writer, ttFont) + # Use the internal write method to write using the correct output format. + self.writeData(strikeIndex, glyphName, writer, ttFont) + + writer.endtag(self.__class__.__name__) + writer.newline() + + def fromXML(self, (name, attrs, content), ttFont): + self.readMetrics((name, attrs, content), ttFont) + for element in content: + if type(element) != TupleType: + continue + name, attr, content = element + # Chop off 'imagedata' from the tag to get just the option. + option = name[:-len('imagedata')] + if option in self.__class__.xmlDataFunctions: + self.readData(element, ttFont) + + # Some of the glyphs have the metrics. This allows for metrics to be + # added if the glyph format has them. Default behavior is to do nothing. + def writeMetrics(self, writer, ttFont): + pass + + # The opposite of write metrics. + def readMetrics(self, (name, attrs, content), ttFont): + pass + + def writeData(self, strikeIndex, glyphName, writer, ttFont): + try: + writeFunc, readFunc = self.__class__.xmlDataFunctions[ttFont.bitmapGlyphDataFormat] + except KeyError: + writeFunc = _writeRawImageData + writeFunc(strikeIndex, glyphName, self, writer, ttFont) + + def readData(self, (name, attrs, content), ttFont): + # Chop off 'imagedata' from the tag to get just the option. + option = name[:-len('imagedata')] + writeFunc, readFunc = self.__class__.xmlDataFunctions[option] + readFunc(self, (name, attrs, content), ttFont) + + +# A closure for creating a mixin for the two types of metrics handling. +# Most of the code is very similar so its easier to deal with here. +# Everything works just by passing the class that the mixin is for. +def _createBitmapPlusMetricsMixin(metricsClass): + # Both metrics names are listed here to make meaningful error messages. + metricStrings = [BigGlyphMetrics.__name__, SmallGlyphMetrics.__name__] + curMetricsName = metricsClass.__name__ + # Find which metrics this is for and determine the opposite name. + metricsId = metricStrings.index(curMetricsName) + oppositeMetricsName = metricStrings[1-metricsId] + + class BitmapPlusMetricsMixin: + + def writeMetrics(self, writer, ttFont): + self.metrics.toXML(writer, ttFont) + + def readMetrics(self, (name, attrs, content), ttFont): + for element in content: + if type(element) != TupleType: + continue + name, attrs, content = element + if name == curMetricsName: + self.metrics = metricsClass() + self.metrics.fromXML(element, ttFont) + elif name == oppositeMetricsName: + print "Warning: %s being ignored in format %d." % oppositeMetricsName, self.getFormat() + + return BitmapPlusMetricsMixin + +# Since there are only two types of mixin's just create them here. +BitmapPlusBigMetricsMixin = _createBitmapPlusMetricsMixin(BigGlyphMetrics) +BitmapPlusSmallMetricsMixin = _createBitmapPlusMetricsMixin(SmallGlyphMetrics) + +# Data that is bit aligned can be tricky to deal with. These classes implement +# helper functionality for dealing with the data and getting a particular row +# of bitwise data. Also helps implement fancy data export/import in XML. +class BitAlignedBitmapMixin: + + def _getBitRange(self, row, bitDepth, metrics): + rowBits = (bitDepth * metrics.width) + bitOffset = row * rowBits + return (bitOffset, bitOffset+rowBits) + + def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False): + if metrics == None: + metrics = self.metrics + assert 0 <= row and row < metrics.height, "Illegal row access in bitmap" + + # Loop through each byte. This can cover two bytes in the original data or + # a single byte if things happen to be aligned. The very last entry might + # not be aligned so take care to trim the binary data to size and pad with + # zeros in the row data. Bit aligned data is somewhat tricky. + # + # Example of data cut. Data cut represented in x's. + # '|' represents byte boundary. + # data = ...0XX|XXXXXX00|000... => XXXXXXXX + # or + # data = ...0XX|XXXX0000|000... => XXXXXX00 + # or + # data = ...000|XXXXXXXX|000... => XXXXXXXX + # or + # data = ...000|00XXXX00|000... => XXXX0000 + # + dataList = [] + bitRange = self._getBitRange(row, bitDepth, metrics) + stepRange = bitRange + (8,) + for curBit in xrange(*stepRange): + endBit = min(curBit+8, bitRange[1]) + numBits = endBit - curBit + cutPoint = curBit % 8 + firstByteLoc = curBit / 8 + secondByteLoc = endBit / 8 + if firstByteLoc < secondByteLoc: + numBitsCut = 8 - cutPoint + else: + numBitsCut = endBit - curBit + curByte = _reverseBytes(self.imageData[firstByteLoc]) + firstHalf = ord(curByte) >> cutPoint + firstHalf = ((1<> numBitsCut) & ((1<<8-numBitsCut)-1) + ordDataList[secondByteLoc] |= secondByte + + # Save the image data with the bits going the correct way. + self.imageData = _reverseBytes(string.join(map(chr, ordDataList), "")) + +class ByteAlignedBitmapMixin: + + def _getByteRange(self, row, bitDepth, metrics): + rowBytes = (bitDepth * metrics.width + 7) / 8 + byteOffset = row * rowBytes + return (byteOffset, byteOffset+rowBytes) + + def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False): + if metrics == None: + metrics = self.metrics + assert 0 <= row and row < metrics.height, "Illegal row access in bitmap" + byteRange = self._getByteRange(row, bitDepth, metrics) + data = self.imageData[slice(*byteRange)] + if reverseBytes: + data = _reverseBytes(data) + return data + + def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False): + if metrics == None: + metrics = self.metrics + if reverseBytes: + dataRows = map(_reverseBytes, dataRows) + self.imageData = string.join(dataRows, "") + +class ebdt_bitmap_format_1(ByteAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph): + + def decompile(self): + self.metrics = SmallGlyphMetrics() + dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics) + self.imageData = data + + def compile(self, ttFont): + data = sstruct.pack(smallGlyphMetricsFormat, self.metrics) + return data + self.imageData + + +class ebdt_bitmap_format_2(BitAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph): + + def decompile(self): + self.metrics = SmallGlyphMetrics() + dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics) + self.imageData = data + + def compile(self, ttFont): + data = sstruct.pack(smallGlyphMetricsFormat, self.metrics) + return data + self.imageData + + +class ebdt_bitmap_format_5(BitAlignedBitmapMixin, BitmapGlyph): + + def decompile(self): + self.imageData = self.data + + def compile(self, ttFont): + return self.imageData + +class ebdt_bitmap_format_6(ByteAlignedBitmapMixin, BitmapPlusBigMetricsMixin, BitmapGlyph): + + def decompile(self): + self.metrics = BigGlyphMetrics() + dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics) + self.imageData = data + + def compile(self, ttFont): + data = sstruct.pack(bigGlyphMetricsFormat, self.metrics) + return data + self.imageData + + +class ebdt_bitmap_format_7(BitAlignedBitmapMixin, BitmapPlusBigMetricsMixin, BitmapGlyph): + + def decompile(self): + self.metrics = BigGlyphMetrics() + dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics) + self.imageData = data + + def compile(self, ttFont): + data = sstruct.pack(bigGlyphMetricsFormat, self.metrics) + return data + self.imageData + + +class ComponentBitmapGlyph(BitmapGlyph): + + def toXML(self, strikeIndex, glyphName, writer, ttFont): + writer.begintag(self.__class__.__name__, [('name', glyphName)]) + writer.newline() + + self.writeMetrics(writer, ttFont) + + writer.begintag('components') + writer.newline() + for curComponent in self.componentArray: + curComponent.toXML(writer, ttFont) + writer.endtag('components') + writer.newline() + + writer.endtag(self.__class__.__name__) + writer.newline() + + def fromXML(self, (name, attrs, content), ttFont): + self.readMetrics((name, attrs, content), ttFont) + for element in content: + if type(element) != TupleType: + continue + name, attr, content = element + if name == 'components': + self.componentArray = [] + for compElement in content: + if type(compElement) != TupleType: + continue + name, attr, content = compElement + if name == 'ebdtComponent': + curComponent = EbdtComponent() + curComponent.fromXML(compElement, ttFont) + self.componentArray.append(curComponent) + else: + print "Warning: '%s' being ignored in component array." % name + + +class ebdt_bitmap_format_8(BitmapPlusSmallMetricsMixin, ComponentBitmapGlyph): + + def decompile(self): + self.metrics = SmallGlyphMetrics() + dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics) + data = data[1:] + + (numComponents,) = struct.unpack(">H", data[:2]) + data = data[2:] + self.componentArray = [] + for i in xrange(numComponents): + curComponent = EbdtComponent() + dummy, data = sstruct.unpack2(ebdtComponentFormat, data, curComponent) + curComponent.name = self.ttFont.getGlyphName(curComponent.glyphCode) + self.componentArray.append(curComponent) + + def compile(self, ttFont): + dataList = [] + dataList.append(sstruct.pack(smallGlyphMetricsFormat, self.metrics)) + dataList.append('\0') + dataList.append(struct.pack(">H", len(self.componentArray))) + for curComponent in self.componentArray: + curComponent.glyphCode = ttFont.getGlyphID(curComponent.name) + dataList.append(sstruct.pack(ebdtComponentFormat, curComponent)) + return string.join(dataList, "") + + +class ebdt_bitmap_format_9(BitmapPlusBigMetricsMixin, ComponentBitmapGlyph): + + def decompile(self): + self.metrics = BigGlyphMetrics() + dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics) + (numComponents,) = struct.unpack(">H", data[:2]) + data = data[2:] + self.componentArray = [] + for i in xrange(numComponents): + curComponent = EbdtComponent() + dummy, data = sstruct.unpack2(ebdtComponentFormat, data, curComponent) + curComponent.name = self.ttFont.getGlyphName(curComponent.glyphCode) + self.componentArray.append(curComponent) + + def compile(self, ttFont): + dataList = [] + dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics)) + dataList.append(struct.pack(">H", len(self.componentArray))) + for curComponent in self.componentArray: + curComponent.glyphCode = ttFont.getGlyphID(curComponent.name) + dataList.append(sstruct.pack(ebdtComponentFormat, curComponent)) + return string.join(dataList, "") + + +# Dictionary of bitmap formats to the class representing that format +# currently only the ones listed in this map are the ones supported. +ebdt_bitmap_classes = { + 1: ebdt_bitmap_format_1, + 2: ebdt_bitmap_format_2, + 5: ebdt_bitmap_format_5, + 6: ebdt_bitmap_format_6, + 7: ebdt_bitmap_format_7, + 8: ebdt_bitmap_format_8, + 9: ebdt_bitmap_format_9, + } diff --git a/Lib/fontTools/ttLib/tables/E_B_L_C_.py b/Lib/fontTools/ttLib/tables/E_B_L_C_.py new file mode 100644 index 000000000..bb6e41545 --- /dev/null +++ b/Lib/fontTools/ttLib/tables/E_B_L_C_.py @@ -0,0 +1,614 @@ + +import DefaultTable +import string +import struct +import sstruct +import itertools +from types import TupleType +from collections import deque +from fontTools.misc.textTools import safeEval +from BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGlyphMetrics, smallGlyphMetricsFormat + +eblcHeaderFormat = """ + > # big endian + version: 16.16F + numSizes: I +""" +# The table format string is split to handle sbitLineMetrics simply. +bitmapSizeTableFormatPart1 = """ + > # big endian + indexSubTableArrayOffset: I + indexTablesSize: I + numberOfIndexSubTables: I + colorRef: I +""" +# The compound type for hori and vert. +sbitLineMetricsFormat = """ + > # big endian + ascender: b + descender: b + widthMax: B + caretSlopeNumerator: b + caretSlopeDenominator: b + caretOffset: b + minOriginSB: b + minAdvanceSB: b + maxBeforeBL: b + minAfterBL: b + pad1: b + pad2: b +""" +# hori and vert go between the two parts. +bitmapSizeTableFormatPart2 = """ + > # big endian + startGlyphIndex: H + endGlyphIndex: H + ppemX: B + ppemY: B + bitDepth: B + flags: b +""" + +indexSubTableArrayFormat = ">HHL" +indexSubTableArraySize = struct.calcsize(indexSubTableArrayFormat) + +indexSubHeaderFormat = ">HHL" +indexSubHeaderSize = struct.calcsize(indexSubHeaderFormat) + +codeOffsetPairFormat = ">HH" +codeOffsetPairSize = struct.calcsize(codeOffsetPairFormat) + +class table_E_B_L_C_(DefaultTable.DefaultTable): + + dependencies = ['EBDT'] + + # This method can be overridden in subclasses to support new formats + # without changing the other implementation. Also can be used as a + # convenience method for coverting a font file to an alternative format. + def getIndexFormatClass(self, indexFormat): + return eblc_sub_table_classes[indexFormat] + + def decompile(self, data, ttFont): + + # Save the original data because offsets are from the start of the table. + origData = data + + dummy, data = sstruct.unpack2(eblcHeaderFormat, data, self) + + self.strikes = [] + for curStrikeIndex in xrange(self.numSizes): + curStrike = Strike() + self.strikes.append(curStrike) + curTable = curStrike.bitmapSizeTable + dummy, data = sstruct.unpack2(bitmapSizeTableFormatPart1, data, curTable) + for metric in ('hori', 'vert'): + metricObj = SbitLineMetrics() + vars(curTable)[metric] = metricObj + dummy, data = sstruct.unpack2(sbitLineMetricsFormat, data, metricObj) + dummy, data = sstruct.unpack2(bitmapSizeTableFormatPart2, data, curTable) + + for curStrike in self.strikes: + curTable = curStrike.bitmapSizeTable + for subtableIndex in xrange(curTable.numberOfIndexSubTables): + lowerBound = curTable.indexSubTableArrayOffset + subtableIndex * indexSubTableArraySize + upperBound = lowerBound + indexSubTableArraySize + data = origData[lowerBound:upperBound] + + tup = struct.unpack(indexSubTableArrayFormat, data) + (firstGlyphIndex, lastGlyphIndex, additionalOffsetToIndexSubtable) = tup + offsetToIndexSubTable = curTable.indexSubTableArrayOffset + additionalOffsetToIndexSubtable + data = origData[offsetToIndexSubTable:] + + tup = struct.unpack(indexSubHeaderFormat, data[:indexSubHeaderSize]) + (indexFormat, imageFormat, imageDataOffset) = tup + + indexFormatClass = self.getIndexFormatClass(indexFormat) + indexSubTable = indexFormatClass(data[indexSubHeaderSize:], ttFont) + indexSubTable.firstGlyphIndex = firstGlyphIndex + indexSubTable.lastGlyphIndex = lastGlyphIndex + indexSubTable.additionalOffsetToIndexSubtable = additionalOffsetToIndexSubtable + indexSubTable.indexFormat = indexFormat + indexSubTable.imageFormat = imageFormat + indexSubTable.imageDataOffset = imageDataOffset + curStrike.indexSubTables.append(indexSubTable) + + def compile(self, ttFont): + + dataList = [] + self.numSizes = len(self.strikes) + dataList.append(sstruct.pack(eblcHeaderFormat, self)) + + # Data size of the header + bitmapSizeTable needs to be calculated + # in order to form offsets. This value will hold the size of the data + # in dataList after all the data is consolidated in dataList. + dataSize = len(dataList[0]) + + # The table will be structured in the following order: + # (0) header + # (1) Each bitmapSizeTable [1 ... self.numSizes] + # (2) Alternate between indexSubTableArray and indexSubTable + # for each bitmapSizeTable present. + # + # The issue is maintaining the proper offsets when table information + # gets moved around. All offsets and size information must be recalculated + # when building the table to allow editing within ttLib and also allow easy + # import/export to and from XML. All of this offset information is lost + # when exporting to XML so everything must be calculated fresh so importing + # from XML will work cleanly. Only byte offset and size information is + # calculated fresh. Count information like numberOfIndexSubTables is + # checked through assertions. If the information in this table was not + # touched or was changed properly then these types of values should match. + # + # The table will be rebuilt the following way: + # (0) Precompute the size of all the bitmapSizeTables. This is needed to + # compute the offsets properly. + # (1) For each bitmapSizeTable compute the indexSubTable and + # indexSubTableArray pair. The indexSubTable must be computed first + # so that the offset information in indexSubTableArray can be + # calculated. Update the data size after each pairing. + # (2) Build each bitmapSizeTable. + # (3) Consolidate all the data into the main dataList in the correct order. + + for curStrike in self.strikes: + dataSize += sstruct.calcsize(bitmapSizeTableFormatPart1) + dataSize += len(('hori', 'vert')) * sstruct.calcsize(sbitLineMetricsFormat) + dataSize += sstruct.calcsize(bitmapSizeTableFormatPart2) + + indexSubTablePairDataList = [] + for curStrike in self.strikes: + curTable = curStrike.bitmapSizeTable + curTable.numberOfIndexSubTables = len(curStrike.indexSubTables) + curTable.indexSubTableArrayOffset = dataSize + + # Precompute the size of the indexSubTableArray. This information + # is important for correctly calculating the new value for + # additionalOffsetToIndexSubtable. + sizeOfSubTableArray = curTable.numberOfIndexSubTables * indexSubTableArraySize + lowerBound = dataSize + dataSize += sizeOfSubTableArray + upperBound = dataSize + + indexSubTableDataList = [] + for indexSubTable in curStrike.indexSubTables: + indexSubTable.additionalOffsetToIndexSubtable = dataSize - curTable.indexSubTableArrayOffset + glyphIds = map(ttFont.getGlyphID, indexSubTable.names) + indexSubTable.firstGlyphIndex = min(glyphIds) + indexSubTable.lastGlyphIndex = max(glyphIds) + data = indexSubTable.compile(ttFont) + indexSubTableDataList.append(data) + dataSize += len(data) + curTable.startGlyphIndex = min(ist.firstGlyphIndex for ist in curStrike.indexSubTables) + curTable.endGlyphIndex = max(ist.lastGlyphIndex for ist in curStrike.indexSubTables) + + for i in curStrike.indexSubTables: + data = struct.pack(indexSubHeaderFormat, i.firstGlyphIndex, i.lastGlyphIndex, i.additionalOffsetToIndexSubtable) + indexSubTablePairDataList.append(data) + indexSubTablePairDataList.extend(indexSubTableDataList) + curTable.indexTablesSize = dataSize - curTable.indexSubTableArrayOffset + + for curStrike in self.strikes: + curTable = curStrike.bitmapSizeTable + data = sstruct.pack(bitmapSizeTableFormatPart1, curTable) + dataList.append(data) + for metric in ('hori', 'vert'): + metricObj = vars(curTable)[metric] + data = sstruct.pack(sbitLineMetricsFormat, metricObj) + dataList.append(data) + data = sstruct.pack(bitmapSizeTableFormatPart2, curTable) + dataList.append(data) + dataList.extend(indexSubTablePairDataList) + + return string.join(dataList, "") + + def toXML(self, writer, ttFont): + writer.simpletag('header', [('version', self.version)]) + writer.newline() + for curIndex, curStrike in enumerate(self.strikes): + curStrike.toXML(curIndex, writer, ttFont) + + def fromXML(self, (name, attrs, content), ttFont): + if name == 'header': + self.version = safeEval(attrs['version']) + elif name == 'strike': + if not hasattr(self, 'strikes'): + self.strikes = [] + strikeIndex = safeEval(attrs['index']) + curStrike = Strike() + curStrike.fromXML((name, attrs, content), ttFont, self) + + # Grow the strike array to the appropriate size. The XML format + # allows for the strike index value to be out of order. + if strikeIndex >= len(self.strikes): + self.strikes += [None] * (strikeIndex + 1 - len(self.strikes)) + assert self.strikes[strikeIndex] == None, "Duplicate strike EBLC indices." + self.strikes[strikeIndex] = curStrike + +class Strike: + + def __init__(self): + self.bitmapSizeTable = BitmapSizeTable() + self.indexSubTables = [] + + def toXML(self, strikeIndex, writer, ttFont): + writer.begintag('strike', [('index', strikeIndex)]) + writer.newline() + self.bitmapSizeTable.toXML(writer, ttFont) + writer.comment('GlyphIds are written but not read. The firstGlyphIndex and\nlastGlyphIndex values will be recalculated by the compiler.') + writer.newline() + for indexSubTable in self.indexSubTables: + indexSubTable.toXML(writer, ttFont) + writer.endtag('strike') + writer.newline() + + def fromXML(self, (name, attrs, content), ttFont, locator): + for element in content: + if type(element) != TupleType: + continue + name, attrs, content = element + if name == 'bitmapSizeTable': + self.bitmapSizeTable.fromXML(element, ttFont) + elif name.startswith(_indexSubTableSubclassPrefix): + indexFormat = safeEval(name[len(_indexSubTableSubclassPrefix):]) + indexFormatClass = locator.getIndexFormatClass(indexFormat) + indexSubTable = indexFormatClass(None, None) + indexSubTable.indexFormat = indexFormat + indexSubTable.fromXML(element, ttFont) + self.indexSubTables.append(indexSubTable) + + +class BitmapSizeTable: + + # Returns all the simple metric names that bitmap size table + # cares about in terms of XML creation. + def _getXMLMetricNames(self): + dataNames = sstruct.getformat(bitmapSizeTableFormatPart1)[1] + dataNames = dataNames + sstruct.getformat(bitmapSizeTableFormatPart2)[1] + # Skip the first 3 data names because they are byte offsets and counts. + return dataNames[3:] + + def toXML(self, writer, ttFont): + writer.begintag('bitmapSizeTable') + writer.newline() + for metric in ('hori', 'vert'): + getattr(self, metric).toXML(metric, writer, ttFont) + for metricName in self._getXMLMetricNames(): + writer.simpletag(metricName, value=getattr(self, metricName)) + writer.newline() + writer.endtag('bitmapSizeTable') + writer.newline() + + def fromXML(self, (name, attrs, content), ttFont): + # Create a lookup for all the simple names that make sense to + # bitmap size table. Only read the information from these names. + dataNames = set(self._getXMLMetricNames()) + for element in content: + if type(element) != TupleType: + continue + name, attrs, content = element + if name == 'sbitLineMetrics': + direction = attrs['direction'] + assert direction in ('hori', 'vert'), "SbitLineMetrics direction specified invalid." + metricObj = SbitLineMetrics() + metricObj.fromXML(element, ttFont) + vars(self)[direction] = metricObj + elif name in dataNames: + vars(self)[name] = safeEval(attrs['value']) + else: + print "Warning: unknown name '%s' being ignored in BitmapSizeTable." % name + + +class SbitLineMetrics: + + def toXML(self, name, writer, ttFont): + writer.begintag('sbitLineMetrics', [('direction', name)]) + writer.newline() + for metricName in sstruct.getformat(sbitLineMetricsFormat)[1]: + writer.simpletag(metricName, value=getattr(self, metricName)) + writer.newline() + writer.endtag('sbitLineMetrics') + writer.newline() + + def fromXML(self, (name, attrs, content), ttFont): + metricNames = set(sstruct.getformat(sbitLineMetricsFormat)[1]) + for element in content: + if type(element) != TupleType: + continue + name, attrs, content = element + if name in metricNames: + vars(self)[name] = safeEval(attrs['value']) + +# Important information about the naming scheme. Used for identifying subtables. +_indexSubTableSubclassPrefix = 'eblc_index_sub_table_' + +class EblcIndexSubTable: + + def __init__(self, data, ttFont): + self.data = data + self.ttFont = ttFont + + def __getattr__(self, attr): + # Allow lazy decompile. + if attr[:2] == '__': + raise AttributeError, attr + if self.data == None: + raise AttributeError, attr + self.decompile() + self.data = None + self.ttFont = None + return getattr(self, attr) + + # This method just takes care of the indexSubHeader. Implementing subclasses + # should call it to compile the indexSubHeader and then continue compiling + # the remainder of their unique format. + def compile(self, ttFont): + return struct.pack(indexSubHeaderFormat, self.indexFormat, self.imageFormat, self.imageDataOffset) + + # Creates the XML for bitmap glyphs. Each index sub table basically makes + # the same XML except for specific metric information that is written + # out via a method call that a subclass implements optionally. + def toXML(self, writer, ttFont): + writer.begintag(self.__class__.__name__, [ + ('imageFormat', self.imageFormat), + ('firstGlyphIndex', self.firstGlyphIndex), + ('lastGlyphIndex', self.lastGlyphIndex), + ]) + writer.newline() + self.writeMetrics(writer, ttFont) + # Write out the names as thats all thats needed to rebuild etc. + # For font debugging of consecutive formats the ids are also written. + # The ids are not read when moving from the XML format. + glyphIds = map(ttFont.getGlyphID, self.names) + for glyphName, glyphId in itertools.izip(self.names, glyphIds): + writer.simpletag('glyphLoc', name=glyphName, id=glyphId) + writer.newline() + writer.endtag(self.__class__.__name__) + writer.newline() + + def fromXML(self, (name, attrs, content), ttFont): + # Read all the attributes. Even though the glyph indices are + # recalculated, they are still read in case there needs to + # be an immediate export of the data. + self.imageFormat = safeEval(attrs['imageFormat']) + self.firstGlyphIndex = safeEval(attrs['firstGlyphIndex']) + self.lastGlyphIndex = safeEval(attrs['lastGlyphIndex']) + + self.readMetrics((name, attrs, content), ttFont) + + self.names = [] + for element in content: + if type(element) != TupleType: + continue + name, attrs, content = element + if name == 'glyphLoc': + self.names.append(attrs['name']) + + # A helper method that writes the metrics for the index sub table. It also + # is responsible for writing the image size for fixed size data since fixed + # size is not recalculated on compile. Default behavior is to do nothing. + def writeMetrics(self, writer, ttFont): + pass + + # A helper method that is the inverse of writeMetrics. + def readMetrics(self, (name, attrs, content), ttFont): + pass + + # This method is for fixed glyph data sizes. There are formats where + # the glyph data is fixed but are actually composite glyphs. To handle + # this the font spec in indexSubTable makes the data the size of the + # fixed size by padding the component arrays. This function abstracts + # out this padding process. Input is data unpadded. Output is data + # padded only in fixed formats. Default behavior is to return the data. + def padBitmapData(self, data): + return data + + # Remove any of the glyph locations and names that are flagged as skipped. + # This only occurs in formats {1,3}. + def removeSkipGlyphs(self): + # Determines if a name, location pair is a valid data location. + # Skip glyphs are marked when the size is equal to zero. + def isValidLocation((name, (startByte, endByte))): + return startByte < endByte + # Remove all skip glyphs. + dataPairs = filter(isValidLocation, zip(self.names, self.locations)) + self.names, self.locations = map(list, zip(*dataPairs)) + +# A closure for creating a custom mixin. This is done because formats 1 and 3 +# are very similar. The only difference between them is the size per offset +# value. Code put in here should handle both cases generally. +def _createOffsetArrayIndexSubTableMixin(formatStringForDataType): + + # Prep the data size for the offset array data format. + dataFormat = '>'+formatStringForDataType + offsetDataSize = struct.calcsize(dataFormat) + + class OffsetArrayIndexSubTableMixin: + + def decompile(self): + + numGlyphs = self.lastGlyphIndex - self.firstGlyphIndex + 1 + indexingOffsets = [glyphIndex * offsetDataSize for glyphIndex in xrange(numGlyphs+2)] + indexingLocations = zip(indexingOffsets, indexingOffsets[1:]) + offsetArray = [struct.unpack(dataFormat, self.data[slice(*loc)])[0] for loc in indexingLocations] + + glyphIds = range(self.firstGlyphIndex, self.lastGlyphIndex+1) + modifiedOffsets = [offset + self.imageDataOffset for offset in offsetArray] + self.locations = zip(modifiedOffsets, modifiedOffsets[1:]) + + self.names = map(self.ttFont.getGlyphName, glyphIds) + self.removeSkipGlyphs() + + def compile(self, ttFont): + # First make sure that all the data lines up properly. Formats 1 and 3 + # must have all its data lined up consecutively. If not this will fail. + for curLoc, nxtLoc in itertools.izip(self.locations, self.locations[1:]): + assert curLoc[1] == nxtLoc[0], "Data must be consecutive in indexSubTable offset formats" + + glyphIds = map(ttFont.getGlyphID, self.names) + # Make sure that all ids are sorted strictly increasing. + assert all(glyphIds[i] < glyphIds[i+1] for i in xrange(len(glyphIds)-1)) + + # Run a simple algorithm to add skip glyphs to the data locations at + # the places where an id is not present. + idQueue = deque(glyphIds) + locQueue = deque(self.locations) + allGlyphIds = range(self.firstGlyphIndex, self.lastGlyphIndex+1) + allLocations = [] + for curId in allGlyphIds: + if curId != idQueue[0]: + allLocations.append((locQueue[0][0], locQueue[0][0])) + else: + idQueue.popleft() + allLocations.append(locQueue.popleft()) + + # Now that all the locations are collected, pack them appropriately into + # offsets. This is the form where offset[i] is the location and + # offset[i+1]-offset[i] is the size of the data location. + offsets = list(allLocations[0]) + [loc[1] for loc in allLocations[1:]] + # Image data offset must be less than or equal to the minimum of locations. + # This offset may change the value for round tripping but is safer and + # allows imageDataOffset to not be required to be in the XML version. + self.imageDataOffset = min(offsets) + offsetArray = [offset - self.imageDataOffset for offset in offsets] + + dataList = [EblcIndexSubTable.compile(self, ttFont)] + dataList += [struct.pack(dataFormat, offsetValue) for offsetValue in offsetArray] + # Take care of any padding issues. Only occurs in format 3. + if offsetDataSize * len(dataList) % 4 != 0: + dataList.append(struct.pack(dataFormat, 0)) + return string.join(dataList, "") + + return OffsetArrayIndexSubTableMixin + +# A Mixin for functionality shared between the different kinds +# of fixed sized data handling. Both kinds have big metrics so +# that kind of special processing is also handled in this mixin. +class FixedSizeIndexSubTableMixin: + + def writeMetrics(self, writer, ttFont): + writer.simpletag('imageSize', value=self.imageSize) + writer.newline() + self.metrics.toXML(writer, ttFont) + + def readMetrics(self, (name, attrs, content), ttFont): + for element in content: + if type(element) != TupleType: + continue + name, attrs, content = element + if name == 'imageSize': + self.imageSize = safeEval(attrs['value']) + elif name == BigGlyphMetrics.__name__: + self.metrics = BigGlyphMetrics() + self.metrics.fromXML(element, ttFont) + elif name == SmallGlyphMetrics.__name__: + print "Warning: SmallGlyphMetrics being ignored in format %d." % self.indexFormat + + def padBitmapData(self, data): + # Make sure that the data isn't bigger than the fixed size. + assert len(data) <= self.imageSize, "Data in indexSubTable format %d must be less than the fixed size." % self.indexFormat + # Pad the data so that it matches the fixed size. + pad = (self.imageSize - len(data)) * '\0' + return data + pad + +class eblc_index_sub_table_1(_createOffsetArrayIndexSubTableMixin('L'), EblcIndexSubTable): + pass + +class eblc_index_sub_table_2(FixedSizeIndexSubTableMixin, EblcIndexSubTable): + + def decompile(self): + (self.imageSize,) = struct.unpack(">L", self.data[:4]) + self.metrics = BigGlyphMetrics() + sstruct.unpack2(bigGlyphMetricsFormat, self.data[4:], self.metrics) + glyphIds = range(self.firstGlyphIndex, self.lastGlyphIndex+1) + offsets = [self.imageSize * i + self.imageDataOffset for i in xrange(len(glyphIds)+1)] + self.locations = zip(offsets, offsets[1:]) + self.names = map(self.ttFont.getGlyphName, glyphIds) + + def compile(self, ttFont): + glyphIds = map(ttFont.getGlyphID, self.names) + # Make sure all the ids are consecutive. This is required by Format 2. + assert glyphIds == range(self.firstGlyphIndex, self.lastGlyphIndex+1), "Format 2 ids must be consecutive." + self.imageDataOffset = min(zip(*self.locations)[0]) + + dataList = [EblcIndexSubTable.compile(self, ttFont)] + dataList.append(struct.pack(">L", self.imageSize)) + dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics)) + return string.join(dataList, "") + +class eblc_index_sub_table_3(_createOffsetArrayIndexSubTableMixin('H'), EblcIndexSubTable): + pass + +class eblc_index_sub_table_4(EblcIndexSubTable): + + def decompile(self): + + (numGlyphs,) = struct.unpack(">L", self.data[:4]) + data = self.data[4:] + indexingOffsets = [glyphIndex * codeOffsetPairSize for glyphIndex in xrange(numGlyphs+2)] + indexingLocations = zip(indexingOffsets, indexingOffsets[1:]) + glyphArray = [struct.unpack(codeOffsetPairFormat, data[slice(*loc)]) for loc in indexingLocations] + glyphIds, offsets = map(list, zip(*glyphArray)) + # There are one too many glyph ids. Get rid of the last one. + glyphIds.pop() + + offsets = [offset + self.imageDataOffset for offset in offsets] + self.locations = zip(offsets, offsets[1:]) + self.names = map(self.ttFont.getGlyphName, glyphIds) + + def compile(self, ttFont): + # First make sure that all the data lines up properly. Format 4 + # must have all its data lined up consecutively. If not this will fail. + for curLoc, nxtLoc in itertools.izip(self.locations, self.locations[1:]): + assert curLoc[1] == nxtLoc[0], "Data must be consecutive in indexSubTable format 4" + + offsets = list(self.locations[0]) + [loc[1] for loc in self.locations[1:]] + # Image data offset must be less than or equal to the minimum of locations. + # Resetting this offset may change the value for round tripping but is safer + # and allows imageDataOffset to not be required to be in the XML version. + self.imageDataOffset = min(offsets) + offsets = [offset - self.imageDataOffset for offset in offsets] + glyphIds = map(ttFont.getGlyphID, self.names) + # Create an iterator over the ids plus a padding value. + idsPlusPad = list(itertools.chain(glyphIds, [0])) + + dataList = [EblcIndexSubTable.compile(self, ttFont)] + dataList.append(struct.pack(">L", len(glyphIds))) + tmp = [struct.pack(codeOffsetPairFormat, *cop) for cop in itertools.izip(idsPlusPad, offsets)] + dataList += tmp + data = string.join(dataList, "") + return data + +class eblc_index_sub_table_5(FixedSizeIndexSubTableMixin, EblcIndexSubTable): + + def decompile(self): + self.origDataLen = 0 + (self.imageSize,) = struct.unpack(">L", self.data[:4]) + data = self.data[4:] + self.metrics, data = sstruct.unpack2(bigGlyphMetricsFormat, data, BigGlyphMetrics()) + (numGlyphs,) = struct.unpack(">L", data[:4]) + data = data[4:] + glyphIds = [struct.unpack(">H", data[2*i:2*(i+1)])[0] for i in xrange(numGlyphs)] + + offsets = [self.imageSize * i + self.imageDataOffset for i in xrange(len(glyphIds)+1)] + self.locations = zip(offsets, offsets[1:]) + self.names = map(self.ttFont.getGlyphName, glyphIds) + + def compile(self, ttFont): + self.imageDataOffset = min(zip(*self.locations)[0]) + dataList = [EblcIndexSubTable.compile(self, ttFont)] + dataList.append(struct.pack(">L", self.imageSize)) + dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics)) + glyphIds = map(ttFont.getGlyphID, self.names) + dataList.append(struct.pack(">L", len(glyphIds))) + dataList += [struct.pack(">H", curId) for curId in glyphIds] + if len(glyphIds) % 2 == 1: + dataList.append(struct.pack(">H", 0)) + return string.join(dataList, "") + +# Dictionary of indexFormat to the class representing that format. +eblc_sub_table_classes = { + 1: eblc_index_sub_table_1, + 2: eblc_index_sub_table_2, + 3: eblc_index_sub_table_3, + 4: eblc_index_sub_table_4, + 5: eblc_index_sub_table_5, + } diff --git a/Lib/fontTools/ttLib/tables/__init__.py b/Lib/fontTools/ttLib/tables/__init__.py index 84cbf0404..76f363edf 100644 --- a/Lib/fontTools/ttLib/tables/__init__.py +++ b/Lib/fontTools/ttLib/tables/__init__.py @@ -6,6 +6,8 @@ def _moduleFinderHint(): import B_A_S_E_ import C_F_F_ import D_S_I_G_ + import E_B_D_T_ + import E_B_L_C_ import G_D_E_F_ import G_M_A_P_ import G_P_K_G_