Add support for standard EBLC/EBDT embedded bitmaps

This commit is contained in:
Matt Fontaine 2013-08-19 14:13:05 -04:00 committed by Behdad Esfahbod
parent c88536d1f4
commit c33b0a22ef
6 changed files with 1426 additions and 2 deletions

View File

@ -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:
<BLOCKQUOTE><TT>
<!-- begin table list -->
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
<!-- end table list -->
</TT></BLOCKQUOTE>
Other tables are dumped as hexadecimal data.

View File

@ -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:

View File

@ -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

View File

@ -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) & firstHalf
newByte = firstHalf
if firstByteLoc < secondByteLoc and secondByteLoc < len(self.imageData):
curByte = _reverseBytes(self.imageData[secondByteLoc])
secondHalf = ord(curByte) << numBitsCut
newByte = (firstHalf | secondHalf) & ((1<<numBits)-1)
dataList.append(chr(newByte))
# The way the data is kept is opposite the algorithm used.
data = string.join(dataList, "")
if not reverseBytes:
data = _reverseBytes(data)
return data
def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False):
if metrics == None:
metrics = self.metrics
if not reverseBytes:
dataRows = map(_reverseBytes, dataRows)
# Keep track of a list of ordinal values as they are easier to modify
# than a list of strings. Map to actual strings later.
numBytes = (self._getBitRange(len(dataRows), bitDepth, metrics)[0] + 7) / 8
ordDataList = [0] * numBytes
for row, data in enumerate(dataRows):
bitRange = self._getBitRange(row, bitDepth, metrics)
stepRange = bitRange + (8,)
for curBit, curByte in itertools.izip(xrange(*stepRange), data):
endBit = min(curBit+8, bitRange[1])
cutPoint = curBit % 8
firstByteLoc = curBit / 8
secondByteLoc = endBit / 8
if firstByteLoc < secondByteLoc:
numBitsCut = 8 - cutPoint
else:
numBitsCut = endBit - curBit
curByte = ord(curByte)
firstByte = curByte & ((1<<numBitsCut)-1)
ordDataList[firstByteLoc] |= (firstByte << cutPoint)
if firstByteLoc < secondByteLoc and secondByteLoc < numBytes:
secondByte = (curByte >> 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,
}

View File

@ -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,
}

View File

@ -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_