Add support for standard EBLC/EBDT embedded bitmaps
This commit is contained in:
parent
c88536d1f4
commit
c33b0a22ef
@ -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.
|
||||
|
@ -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:
|
||||
|
57
Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py
Normal file
57
Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py
Normal 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
|
749
Lib/fontTools/ttLib/tables/E_B_D_T_.py
Normal file
749
Lib/fontTools/ttLib/tables/E_B_D_T_.py
Normal 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,
|
||||
}
|
614
Lib/fontTools/ttLib/tables/E_B_L_C_.py
Normal file
614
Lib/fontTools/ttLib/tables/E_B_L_C_.py
Normal 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,
|
||||
}
|
@ -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_
|
||||
|
Loading…
x
Reference in New Issue
Block a user