diff options
Diffstat (limited to 'Lib/fontTools/ttLib/tables/E_B_L_C_.py')
-rw-r--r-- | Lib/fontTools/ttLib/tables/E_B_L_C_.py | 1139 |
1 files changed, 610 insertions, 529 deletions
diff --git a/Lib/fontTools/ttLib/tables/E_B_L_C_.py b/Lib/fontTools/ttLib/tables/E_B_L_C_.py index bb3d2140..6046d910 100644 --- a/Lib/fontTools/ttLib/tables/E_B_L_C_.py +++ b/Lib/fontTools/ttLib/tables/E_B_L_C_.py @@ -1,7 +1,12 @@ from fontTools.misc import sstruct from . import DefaultTable from fontTools.misc.textTools import bytesjoin, safeEval -from .BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGlyphMetrics, smallGlyphMetricsFormat +from .BitmapGlyphMetrics import ( + BigGlyphMetrics, + bigGlyphMetricsFormat, + SmallGlyphMetrics, + smallGlyphMetricsFormat, +) import struct import itertools from collections import deque @@ -59,571 +64,647 @@ 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 + i = 0 + + dummy = sstruct.unpack(eblcHeaderFormat, data[:8], self) + i += 8 + + self.strikes = [] + for curStrikeIndex in range(self.numSizes): + curStrike = Strike() + self.strikes.append(curStrike) + curTable = curStrike.bitmapSizeTable + dummy = sstruct.unpack2( + bitmapSizeTableFormatPart1, data[i : i + 16], curTable + ) + i += 16 + for metric in ("hori", "vert"): + metricObj = SbitLineMetrics() + vars(curTable)[metric] = metricObj + dummy = sstruct.unpack2( + sbitLineMetricsFormat, data[i : i + 12], metricObj + ) + i += 12 + dummy = sstruct.unpack( + bitmapSizeTableFormatPart2, data[i : i + 8], curTable + ) + i += 8 + + for curStrike in self.strikes: + curTable = curStrike.bitmapSizeTable + for subtableIndex in range(curTable.numberOfIndexSubTables): + i = ( + curTable.indexSubTableArrayOffset + + subtableIndex * indexSubTableArraySize + ) + + tup = struct.unpack( + indexSubTableArrayFormat, data[i : i + indexSubTableArraySize] + ) + (firstGlyphIndex, lastGlyphIndex, additionalOffsetToIndexSubtable) = tup + i = curTable.indexSubTableArrayOffset + additionalOffsetToIndexSubtable + + tup = struct.unpack( + indexSubHeaderFormat, data[i : i + indexSubHeaderSize] + ) + (indexFormat, imageFormat, imageDataOffset) = tup + + indexFormatClass = self.getIndexFormatClass(indexFormat) + indexSubTable = indexFormatClass(data[i + indexSubHeaderSize :], ttFont) + indexSubTable.firstGlyphIndex = firstGlyphIndex + indexSubTable.lastGlyphIndex = lastGlyphIndex + indexSubTable.additionalOffsetToIndexSubtable = ( + additionalOffsetToIndexSubtable + ) + indexSubTable.indexFormat = indexFormat + indexSubTable.imageFormat = imageFormat + indexSubTable.imageDataOffset = imageDataOffset + indexSubTable.decompile() # https://github.com/fonttools/fonttools/issues/317 + 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 _ 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 = list(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 bytesjoin(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] is None, "Duplicate strike EBLC indices." + self.strikes[strikeIndex] = curStrike - 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 - i = 0; - - dummy = sstruct.unpack(eblcHeaderFormat, data[:8], self) - i += 8; - - self.strikes = [] - for curStrikeIndex in range(self.numSizes): - curStrike = Strike() - self.strikes.append(curStrike) - curTable = curStrike.bitmapSizeTable - dummy = sstruct.unpack2(bitmapSizeTableFormatPart1, data[i:i+16], curTable) - i += 16 - for metric in ('hori', 'vert'): - metricObj = SbitLineMetrics() - vars(curTable)[metric] = metricObj - dummy = sstruct.unpack2(sbitLineMetricsFormat, data[i:i+12], metricObj) - i += 12 - dummy = sstruct.unpack(bitmapSizeTableFormatPart2, data[i:i+8], curTable) - i += 8 - - for curStrike in self.strikes: - curTable = curStrike.bitmapSizeTable - for subtableIndex in range(curTable.numberOfIndexSubTables): - i = curTable.indexSubTableArrayOffset + subtableIndex * indexSubTableArraySize - - tup = struct.unpack(indexSubTableArrayFormat, data[i:i+indexSubTableArraySize]) - (firstGlyphIndex, lastGlyphIndex, additionalOffsetToIndexSubtable) = tup - i = curTable.indexSubTableArrayOffset + additionalOffsetToIndexSubtable - - tup = struct.unpack(indexSubHeaderFormat, data[i:i+indexSubHeaderSize]) - (indexFormat, imageFormat, imageDataOffset) = tup - - indexFormatClass = self.getIndexFormatClass(indexFormat) - indexSubTable = indexFormatClass(data[i+indexSubHeaderSize:], ttFont) - indexSubTable.firstGlyphIndex = firstGlyphIndex - indexSubTable.lastGlyphIndex = lastGlyphIndex - indexSubTable.additionalOffsetToIndexSubtable = additionalOffsetToIndexSubtable - indexSubTable.indexFormat = indexFormat - indexSubTable.imageFormat = imageFormat - indexSubTable.imageDataOffset = imageDataOffset - indexSubTable.decompile() # https://github.com/fonttools/fonttools/issues/317 - 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 _ 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 = list(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 bytesjoin(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] is None, "Duplicate strike EBLC indices." - self.strikes[strikeIndex] = curStrike class Strike(object): - - 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 not isinstance(element, tuple): - continue - name, attrs, content = element - if name == 'bitmapSizeTable': - self.bitmapSizeTable.fromXML(name, attrs, content, ttFont) - elif name.startswith(_indexSubTableSubclassPrefix): - indexFormat = safeEval(name[len(_indexSubTableSubclassPrefix):]) - indexFormatClass = locator.getIndexFormatClass(indexFormat) - indexSubTable = indexFormatClass(None, None) - indexSubTable.indexFormat = indexFormat - indexSubTable.fromXML(name, attrs, content, ttFont) - self.indexSubTables.append(indexSubTable) + 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 not isinstance(element, tuple): + continue + name, attrs, content = element + if name == "bitmapSizeTable": + self.bitmapSizeTable.fromXML(name, attrs, content, ttFont) + elif name.startswith(_indexSubTableSubclassPrefix): + indexFormat = safeEval(name[len(_indexSubTableSubclassPrefix) :]) + indexFormatClass = locator.getIndexFormatClass(indexFormat) + indexSubTable = indexFormatClass(None, None) + indexSubTable.indexFormat = indexFormat + indexSubTable.fromXML(name, attrs, content, ttFont) + self.indexSubTables.append(indexSubTable) class BitmapSizeTable(object): - - # 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 not isinstance(element, tuple): - continue - name, attrs, content = element - if name == 'sbitLineMetrics': - direction = attrs['direction'] - assert direction in ('hori', 'vert'), "SbitLineMetrics direction specified invalid." - metricObj = SbitLineMetrics() - metricObj.fromXML(name, attrs, content, ttFont) - vars(self)[direction] = metricObj - elif name in dataNames: - vars(self)[name] = safeEval(attrs['value']) - else: - log.warning("unknown name '%s' being ignored in BitmapSizeTable.", name) + # 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 not isinstance(element, tuple): + continue + name, attrs, content = element + if name == "sbitLineMetrics": + direction = attrs["direction"] + assert direction in ( + "hori", + "vert", + ), "SbitLineMetrics direction specified invalid." + metricObj = SbitLineMetrics() + metricObj.fromXML(name, attrs, content, ttFont) + vars(self)[direction] = metricObj + elif name in dataNames: + vars(self)[name] = safeEval(attrs["value"]) + else: + log.warning("unknown name '%s' being ignored in BitmapSizeTable.", name) class SbitLineMetrics(object): + 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 not isinstance(element, tuple): + continue + name, attrs, content = element + if name in metricNames: + vars(self)[name] = safeEval(attrs["value"]) - 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 not isinstance(element, tuple): - 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_' +_indexSubTableSubclassPrefix = "eblc_index_sub_table_" + class EblcIndexSubTable(object): + def __init__(self, data, ttFont): + self.data = data + self.ttFont = ttFont + # TODO Currently non-lazy decompiling doesn't work for this class... + # if not ttFont.lazy: + # self.decompile() + # del self.data, self.ttFont + + def __getattr__(self, attr): + # Allow lazy decompile. + if attr[:2] == "__": + raise AttributeError(attr) + if attr == "data": + raise AttributeError(attr) + self.decompile() + return getattr(self, attr) + + def ensureDecompiled(self, recurse=False): + if hasattr(self, "data"): + self.decompile() + + # 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 zip(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 not isinstance(element, tuple): + 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(args): + (name, (startByte, endByte)) = args + return startByte < endByte + + # Remove all skip glyphs. + dataPairs = list(filter(isValidLocation, zip(self.names, self.locations))) + self.names, self.locations = list(map(list, zip(*dataPairs))) - def __init__(self, data, ttFont): - self.data = data - self.ttFont = ttFont - # TODO Currently non-lazy decompiling doesn't work for this class... - #if not ttFont.lazy: - # self.decompile() - # del self.data, self.ttFont - - def __getattr__(self, attr): - # Allow lazy decompile. - if attr[:2] == '__': - raise AttributeError(attr) - if attr == "data": - raise AttributeError(attr) - self.decompile() - return getattr(self, attr) - - def ensureDecompiled(self, recurse=False): - if hasattr(self, "data"): - self.decompile() - - # 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 zip(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 not isinstance(element, tuple): - 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(args): - (name, (startByte, endByte)) = args - return startByte < endByte - # Remove all skip glyphs. - dataPairs = list(filter(isValidLocation, zip(self.names, self.locations))) - self.names, self.locations = list(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(object): + def decompile(self): + numGlyphs = self.lastGlyphIndex - self.firstGlyphIndex + 1 + indexingOffsets = [ + glyphIndex * offsetDataSize for glyphIndex in range(numGlyphs + 2) + ] + indexingLocations = zip(indexingOffsets, indexingOffsets[1:]) + offsetArray = [ + struct.unpack(dataFormat, self.data[slice(*loc)])[0] + for loc in indexingLocations + ] + + glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1)) + modifiedOffsets = [offset + self.imageDataOffset for offset in offsetArray] + self.locations = list(zip(modifiedOffsets, modifiedOffsets[1:])) + + self.names = list(map(self.ttFont.getGlyphName, glyphIds)) + self.removeSkipGlyphs() + del self.data, self.ttFont + + 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 zip(self.locations, self.locations[1:]): + assert ( + curLoc[1] == nxtLoc[0] + ), "Data must be consecutive in indexSubTable offset formats" + + glyphIds = list(map(ttFont.getGlyphID, self.names)) + # Make sure that all ids are sorted strictly increasing. + assert all(glyphIds[i] < glyphIds[i + 1] for i in range(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 = list(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(offsetArray) % 4 != 0: + dataList.append(struct.pack(dataFormat, 0)) + return bytesjoin(dataList) + + return OffsetArrayIndexSubTableMixin - # Prep the data size for the offset array data format. - dataFormat = '>'+formatStringForDataType - offsetDataSize = struct.calcsize(dataFormat) - - class OffsetArrayIndexSubTableMixin(object): - - def decompile(self): - - numGlyphs = self.lastGlyphIndex - self.firstGlyphIndex + 1 - indexingOffsets = [glyphIndex * offsetDataSize for glyphIndex in range(numGlyphs+2)] - indexingLocations = zip(indexingOffsets, indexingOffsets[1:]) - offsetArray = [struct.unpack(dataFormat, self.data[slice(*loc)])[0] for loc in indexingLocations] - - glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex+1)) - modifiedOffsets = [offset + self.imageDataOffset for offset in offsetArray] - self.locations = list(zip(modifiedOffsets, modifiedOffsets[1:])) - - self.names = list(map(self.ttFont.getGlyphName, glyphIds)) - self.removeSkipGlyphs() - del self.data, self.ttFont - - 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 zip(self.locations, self.locations[1:]): - assert curLoc[1] == nxtLoc[0], "Data must be consecutive in indexSubTable offset formats" - - glyphIds = list(map(ttFont.getGlyphID, self.names)) - # Make sure that all ids are sorted strictly increasing. - assert all(glyphIds[i] < glyphIds[i+1] for i in range(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 = list(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(offsetArray) % 4 != 0: - dataList.append(struct.pack(dataFormat, 0)) - return bytesjoin(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(object): + 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 not isinstance(element, tuple): + continue + name, attrs, content = element + if name == "imageSize": + self.imageSize = safeEval(attrs["value"]) + elif name == BigGlyphMetrics.__name__: + self.metrics = BigGlyphMetrics() + self.metrics.fromXML(name, attrs, content, ttFont) + elif name == SmallGlyphMetrics.__name__: + log.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)) * b"\0" + return data + pad + + +class eblc_index_sub_table_1( + _createOffsetArrayIndexSubTableMixin("L"), EblcIndexSubTable +): + pass - 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 not isinstance(element, tuple): - continue - name, attrs, content = element - if name == 'imageSize': - self.imageSize = safeEval(attrs['value']) - elif name == BigGlyphMetrics.__name__: - self.metrics = BigGlyphMetrics() - self.metrics.fromXML(name, attrs, content, ttFont) - elif name == SmallGlyphMetrics.__name__: - log.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)) * b'\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 = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1)) + offsets = [ + self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds) + 1) + ] + self.locations = list(zip(offsets, offsets[1:])) + self.names = list(map(self.ttFont.getGlyphName, glyphIds)) + del self.data, self.ttFont + + def compile(self, ttFont): + glyphIds = list(map(ttFont.getGlyphID, self.names)) + # Make sure all the ids are consecutive. This is required by Format 2. + assert glyphIds == list( + range(self.firstGlyphIndex, self.lastGlyphIndex + 1) + ), "Format 2 ids must be consecutive." + self.imageDataOffset = min(next(iter(zip(*self.locations)))) + + dataList = [EblcIndexSubTable.compile(self, ttFont)] + dataList.append(struct.pack(">L", self.imageSize)) + dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics)) + return bytesjoin(dataList) + + +class eblc_index_sub_table_3( + _createOffsetArrayIndexSubTableMixin("H"), EblcIndexSubTable +): + pass - def decompile(self): - (self.imageSize,) = struct.unpack(">L", self.data[:4]) - self.metrics = BigGlyphMetrics() - sstruct.unpack2(bigGlyphMetricsFormat, self.data[4:], self.metrics) - glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex+1)) - offsets = [self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds)+1)] - self.locations = list(zip(offsets, offsets[1:])) - self.names = list(map(self.ttFont.getGlyphName, glyphIds)) - del self.data, self.ttFont - - def compile(self, ttFont): - glyphIds = list(map(ttFont.getGlyphID, self.names)) - # Make sure all the ids are consecutive. This is required by Format 2. - assert glyphIds == list(range(self.firstGlyphIndex, self.lastGlyphIndex+1)), "Format 2 ids must be consecutive." - self.imageDataOffset = min(next(iter(zip(*self.locations)))) - - dataList = [EblcIndexSubTable.compile(self, ttFont)] - dataList.append(struct.pack(">L", self.imageSize)) - dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics)) - return bytesjoin(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 range(numGlyphs + 2) + ] + indexingLocations = zip(indexingOffsets, indexingOffsets[1:]) + glyphArray = [ + struct.unpack(codeOffsetPairFormat, data[slice(*loc)]) + for loc in indexingLocations + ] + glyphIds, offsets = list(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 = list(zip(offsets, offsets[1:])) + self.names = list(map(self.ttFont.getGlyphName, glyphIds)) + del self.data, self.ttFont + + 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 zip(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 = list(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 zip(idsPlusPad, offsets) + ] + dataList += tmp + data = bytesjoin(dataList) + return data - def decompile(self): - - (numGlyphs,) = struct.unpack(">L", self.data[:4]) - data = self.data[4:] - indexingOffsets = [glyphIndex * codeOffsetPairSize for glyphIndex in range(numGlyphs+2)] - indexingLocations = zip(indexingOffsets, indexingOffsets[1:]) - glyphArray = [struct.unpack(codeOffsetPairFormat, data[slice(*loc)]) for loc in indexingLocations] - glyphIds, offsets = list(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 = list(zip(offsets, offsets[1:])) - self.names = list(map(self.ttFont.getGlyphName, glyphIds)) - del self.data, self.ttFont - - 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 zip(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 = list(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 zip(idsPlusPad, offsets)] - dataList += tmp - data = bytesjoin(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 range(numGlyphs) + ] + + offsets = [ + self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds) + 1) + ] + self.locations = list(zip(offsets, offsets[1:])) + self.names = list(map(self.ttFont.getGlyphName, glyphIds)) + del self.data, self.ttFont + + def compile(self, ttFont): + self.imageDataOffset = min(next(iter(zip(*self.locations)))) + dataList = [EblcIndexSubTable.compile(self, ttFont)] + dataList.append(struct.pack(">L", self.imageSize)) + dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics)) + glyphIds = list(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 bytesjoin(dataList) - 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 range(numGlyphs)] - - offsets = [self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds)+1)] - self.locations = list(zip(offsets, offsets[1:])) - self.names = list(map(self.ttFont.getGlyphName, glyphIds)) - del self.data, self.ttFont - - def compile(self, ttFont): - self.imageDataOffset = min(next(iter(zip(*self.locations)))) - dataList = [EblcIndexSubTable.compile(self, ttFont)] - dataList.append(struct.pack(">L", self.imageSize)) - dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics)) - glyphIds = list(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 bytesjoin(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, - } + 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, +} |