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_