from fontTools.misc import sstruct
from . import DefaultTable
from fontTools.misc.textTools import bytesjoin, safeEval
from .BitmapGlyphMetrics import (
BigGlyphMetrics,
bigGlyphMetricsFormat,
SmallGlyphMetrics,
smallGlyphMetricsFormat,
)
import struct
import itertools
from collections import deque
import logging
log = logging.getLogger(__name__)
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)
[docs]
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.
[docs]
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)
[docs]
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)
[docs]
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)
[docs]
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
[docs]
class Strike(object):
def __init__(self):
self.bitmapSizeTable = BitmapSizeTable()
self.indexSubTables = []
[docs]
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()
[docs]
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)
[docs]
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 list(dataNames.keys())[3:]
[docs]
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()
[docs]
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)
[docs]
class SbitLineMetrics(object):
[docs]
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()
[docs]
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_"
[docs]
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)
[docs]
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.
[docs]
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.
[docs]
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()
[docs]
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.
[docs]
def writeMetrics(self, writer, ttFont):
pass
# A helper method that is the inverse of writeMetrics.
[docs]
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.
[docs]
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}.
[docs]
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
# 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.
[docs]
class FixedSizeIndexSubTableMixin(object):
[docs]
def writeMetrics(self, writer, ttFont):
writer.simpletag("imageSize", value=self.imageSize)
writer.newline()
self.metrics.toXML(writer, ttFont)
[docs]
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
)
[docs]
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
[docs]
class eblc_index_sub_table_1(
_createOffsetArrayIndexSubTableMixin("L"), EblcIndexSubTable
):
pass
[docs]
class eblc_index_sub_table_2(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
[docs]
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
[docs]
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)
[docs]
class eblc_index_sub_table_3(
_createOffsetArrayIndexSubTableMixin("H"), EblcIndexSubTable
):
pass
[docs]
class eblc_index_sub_table_4(EblcIndexSubTable):
[docs]
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
[docs]
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
[docs]
class eblc_index_sub_table_5(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
[docs]
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
[docs]
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,
}