719 lines
29 KiB
Python
719 lines
29 KiB
Python
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)
|
|
|
|
|
|
class table_E_B_L_C_(DefaultTable.DefaultTable):
|
|
"""Embedded Bitmap Location table
|
|
|
|
The ``EBLC`` table contains the locations of monochrome or grayscale
|
|
bitmaps for glyphs. It must be used in concert with the ``EBDT`` table.
|
|
|
|
See also https://learn.microsoft.com/en-us/typography/opentype/spec/eblc
|
|
"""
|
|
|
|
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)
|
|
|
|
|
|
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:]
|
|
|
|
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"])
|
|
|
|
|
|
# Important information about the naming scheme. Used for identifying subtables.
|
|
_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)))
|
|
|
|
|
|
# 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.
|
|
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
|
|
|
|
|
|
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
|
|
|
|
|
|
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
|
|
|
|
|
|
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)
|
|
|
|
|
|
# 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,
|
|
}
|