aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/ttLib/tables/otConverters.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools/ttLib/tables/otConverters.py')
-rw-r--r--Lib/fontTools/ttLib/tables/otConverters.py266
1 files changed, 116 insertions, 150 deletions
diff --git a/Lib/fontTools/ttLib/tables/otConverters.py b/Lib/fontTools/ttLib/tables/otConverters.py
index 4af38acd..44fcd0ab 100644
--- a/Lib/fontTools/ttLib/tables/otConverters.py
+++ b/Lib/fontTools/ttLib/tables/otConverters.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import bytesjoin, tobytes, tostr
from fontTools.misc.fixedTools import (
fixedToFloat as fi2fl,
floatToFixed as fl2fi,
@@ -7,14 +6,15 @@ from fontTools.misc.fixedTools import (
ensureVersionIsLong as fi2ve,
versionToFixed as ve2fi,
)
-from fontTools.misc.textTools import pad, safeEval
+from fontTools.misc.roundTools import nearestMultipleShortestRepr, otRound
+from fontTools.misc.textTools import bytesjoin, tobytes, tostr, pad, safeEval
from fontTools.ttLib import getSearchRange
from .otBase import (CountReference, FormatSwitchingBaseTable,
OTTableReader, OTTableWriter, ValueRecordFactory)
from .otTables import (lookupTypes, AATStateTable, AATState, AATAction,
ContextualMorphAction, LigatureMorphAction,
- InsertionMorphAction, MorxSubtable, VariableFloat,
- VariableInt, ExtendMode as _ExtendMode,
+ InsertionMorphAction, MorxSubtable,
+ ExtendMode as _ExtendMode,
CompositeMode as _CompositeMode)
from itertools import zip_longest
from functools import partial
@@ -192,8 +192,12 @@ class BaseConverter(object):
raise NotImplementedError(self)
def writeArray(self, writer, font, tableDict, values):
- for i, value in enumerate(values):
- self.write(writer, font, tableDict, value, i)
+ try:
+ for i, value in enumerate(values):
+ self.write(writer, font, tableDict, value, i)
+ except Exception as e:
+ e.args = e.args + (i,)
+ raise
def write(self, writer, font, tableDict, value, repeatIndex=None):
"""Write a value to the writer."""
@@ -221,6 +225,18 @@ class SimpleValue(BaseConverter):
def xmlRead(self, attrs, content, font):
return self.fromString(attrs["value"])
+class OptionalValue(SimpleValue):
+ DEFAULT = None
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ if value != self.DEFAULT:
+ attrs.append(("value", self.toString(value)))
+ xmlWriter.simpletag(name, attrs)
+ xmlWriter.newline()
+ def xmlRead(self, attrs, content, font):
+ if "value" in attrs:
+ return self.fromString(attrs["value"])
+ return self.DEFAULT
+
class IntValue(SimpleValue):
@staticmethod
def fromString(value):
@@ -230,48 +246,75 @@ class Long(IntValue):
staticSize = 4
def read(self, reader, font, tableDict):
return reader.readLong()
+ def readArray(self, reader, font, tableDict, count):
+ return reader.readLongArray(count)
def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeLong(value)
+ def writeArray(self, writer, font, tableDict, values):
+ writer.writeLongArray(values)
class ULong(IntValue):
staticSize = 4
def read(self, reader, font, tableDict):
return reader.readULong()
+ def readArray(self, reader, font, tableDict, count):
+ return reader.readULongArray(count)
def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeULong(value)
+ def writeArray(self, writer, font, tableDict, values):
+ writer.writeULongArray(values)
class Flags32(ULong):
@staticmethod
def toString(value):
return "0x%08X" % value
+class VarIndex(OptionalValue, ULong):
+ DEFAULT = 0xFFFFFFFF
+
class Short(IntValue):
staticSize = 2
def read(self, reader, font, tableDict):
return reader.readShort()
+ def readArray(self, reader, font, tableDict, count):
+ return reader.readShortArray(count)
def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeShort(value)
+ def writeArray(self, writer, font, tableDict, values):
+ writer.writeShortArray(values)
class UShort(IntValue):
staticSize = 2
def read(self, reader, font, tableDict):
return reader.readUShort()
+ def readArray(self, reader, font, tableDict, count):
+ return reader.readUShortArray(count)
def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeUShort(value)
+ def writeArray(self, writer, font, tableDict, values):
+ writer.writeUShortArray(values)
class Int8(IntValue):
staticSize = 1
def read(self, reader, font, tableDict):
return reader.readInt8()
+ def readArray(self, reader, font, tableDict, count):
+ return reader.readInt8Array(count)
def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeInt8(value)
+ def writeArray(self, writer, font, tableDict, values):
+ writer.writeInt8Array(values)
class UInt8(IntValue):
staticSize = 1
def read(self, reader, font, tableDict):
return reader.readUInt8()
+ def readArray(self, reader, font, tableDict, count):
+ return reader.readUInt8Array(count)
def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeUInt8(value)
+ def writeArray(self, writer, font, tableDict, values):
+ writer.writeUInt8Array(values)
class UInt24(IntValue):
staticSize = 3
@@ -304,16 +347,11 @@ class GlyphID(SimpleValue):
staticSize = 2
typecode = "H"
def readArray(self, reader, font, tableDict, count):
- glyphOrder = font.getGlyphOrder()
- gids = reader.readArray(self.typecode, self.staticSize, count)
- try:
- l = [glyphOrder[gid] for gid in gids]
- except IndexError:
- # Slower, but will not throw an IndexError on an invalid glyph id.
- l = [font.getGlyphName(gid) for gid in gids]
- return l
+ return font.getGlyphNameMany(reader.readArray(self.typecode, self.staticSize, count))
def read(self, reader, font, tableDict):
return font.getGlyphName(reader.readValue(self.typecode, self.staticSize))
+ def writeArray(self, writer, font, tableDict, values):
+ writer.writeArray(self.typecode, font.getGlyphIDMany(values))
def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeValue(self.typecode, font.getGlyphID(value))
@@ -390,6 +428,22 @@ class F2Dot14(FloatValue):
def toString(value):
return fl2str(value, 14)
+class Angle(F2Dot14):
+ # angles are specified in degrees, and encoded as F2Dot14 fractions of half
+ # circle: e.g. 1.0 => 180, -0.5 => -90, -2.0 => -360, etc.
+ factor = 1.0/(1<<14) * 180 # 0.010986328125
+ def read(self, reader, font, tableDict):
+ return super().read(reader, font, tableDict) * 180
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ super().write(writer, font, tableDict, value / 180, repeatIndex=repeatIndex)
+ @classmethod
+ def fromString(cls, value):
+ # quantize to nearest multiples of minimum fixed-precision angle
+ return otRound(float(value) / cls.factor) * cls.factor
+ @classmethod
+ def toString(cls, value):
+ return nearestMultipleShortestRepr(value, cls.factor)
+
class Version(SimpleValue):
staticSize = 4
def read(self, reader, font, tableDict):
@@ -1155,8 +1209,7 @@ class STXHeader(BaseConverter):
def _readLigatures(self, reader, font):
limit = len(reader.data)
numLigatureGlyphs = (limit - reader.pos) // 2
- return [font.getGlyphName(g)
- for g in reader.readUShortArray(numLigatureGlyphs)]
+ return font.getGlyphNameMany(reader.readUShortArray(numLigatureGlyphs))
def _countPerGlyphLookups(self, table):
# Somewhat annoyingly, the morx table does not encode
@@ -1551,20 +1604,15 @@ class VarIdxMapValue(BaseConverter):
outerShift = 16 - innerBits
entrySize = 1 + ((fmt & 0x0030) >> 4)
- read = {
- 1: reader.readUInt8,
- 2: reader.readUShort,
- 3: reader.readUInt24,
- 4: reader.readULong,
+ readArray = {
+ 1: reader.readUInt8Array,
+ 2: reader.readUShortArray,
+ 3: reader.readUInt24Array,
+ 4: reader.readULongArray,
}[entrySize]
- mapping = []
- for i in range(nItems):
- raw = read()
- idx = ((raw & outerMask) << outerShift) | (raw & innerMask)
- mapping.append(idx)
-
- return mapping
+ return [(((raw & outerMask) << outerShift) | (raw & innerMask))
+ for raw in readArray(nItems)]
def write(self, writer, font, tableDict, value, repeatIndex=None):
fmt = tableDict['EntryFormat']
@@ -1576,16 +1624,15 @@ class VarIdxMapValue(BaseConverter):
outerShift = 16 - innerBits
entrySize = 1 + ((fmt & 0x0030) >> 4)
- write = {
- 1: writer.writeUInt8,
- 2: writer.writeUShort,
- 3: writer.writeUInt24,
- 4: writer.writeULong,
+ writeArray = {
+ 1: writer.writeUInt8Array,
+ 2: writer.writeUShortArray,
+ 3: writer.writeUInt24Array,
+ 4: writer.writeULongArray,
}[entrySize]
- for idx in mapping:
- raw = ((idx & 0xFFFF0000) >> outerShift) | (idx & innerMask)
- write(raw)
+ writeArray([(((idx & 0xFFFF0000) >> outerShift) | (idx & innerMask))
+ for idx in mapping])
class VarDataValue(BaseConverter):
@@ -1594,27 +1641,43 @@ class VarDataValue(BaseConverter):
values = []
regionCount = tableDict["VarRegionCount"]
- shortCount = tableDict["NumShorts"]
+ wordCount = tableDict["NumShorts"]
- for i in range(min(regionCount, shortCount)):
- values.append(reader.readShort())
- for i in range(min(regionCount, shortCount), regionCount):
- values.append(reader.readInt8())
- for i in range(regionCount, shortCount):
- reader.readInt8()
+ # https://github.com/fonttools/fonttools/issues/2279
+ longWords = bool(wordCount & 0x8000)
+ wordCount = wordCount & 0x7FFF
+
+ if longWords:
+ readBigArray, readSmallArray = reader.readLongArray, reader.readShortArray
+ else:
+ readBigArray, readSmallArray = reader.readShortArray, reader.readInt8Array
+
+ n1, n2 = min(regionCount, wordCount), max(regionCount, wordCount)
+ values.extend(readBigArray(n1))
+ values.extend(readSmallArray(n2 - n1))
+ if n2 > regionCount: # Padding
+ del values[regionCount:]
return values
- def write(self, writer, font, tableDict, value, repeatIndex=None):
+ def write(self, writer, font, tableDict, values, repeatIndex=None):
regionCount = tableDict["VarRegionCount"]
- shortCount = tableDict["NumShorts"]
+ wordCount = tableDict["NumShorts"]
- for i in range(min(regionCount, shortCount)):
- writer.writeShort(value[i])
- for i in range(min(regionCount, shortCount), regionCount):
- writer.writeInt8(value[i])
- for i in range(regionCount, shortCount):
- writer.writeInt8(0)
+ # https://github.com/fonttools/fonttools/issues/2279
+ longWords = bool(wordCount & 0x8000)
+ wordCount = wordCount & 0x7FFF
+
+ (writeBigArray, writeSmallArray) = {
+ False: (writer.writeShortArray, writer.writeInt8Array),
+ True: (writer.writeLongArray, writer.writeShortArray),
+ }[longWords]
+
+ n1, n2 = min(regionCount, wordCount), max(regionCount, wordCount)
+ writeBigArray(values[:n1])
+ writeSmallArray(values[n1:regionCount])
+ if n2 > regionCount: # Padding
+ writer.writeSmallArray([0] * (n2 - regionCount))
def xmlWrite(self, xmlWriter, font, value, name, attrs):
xmlWriter.simpletag(name, attrs + [("value", value)])
@@ -1637,99 +1700,6 @@ class LookupFlag(UShort):
xmlWriter.comment(" ".join(flags))
xmlWriter.newline()
-def _issubclass_namedtuple(x):
- return (
- issubclass(x, tuple)
- and getattr(x, "_fields", None) is not None
- )
-
-
-class _NamedTupleConverter(BaseConverter):
- # subclasses must override this
- tupleClass = NotImplemented
- # List[SimpleValue]
- converterClasses = NotImplemented
-
- def __init__(self, name, repeat, aux, tableClass=None):
- # we expect all converters to be subclasses of SimpleValue
- assert all(issubclass(klass, SimpleValue) for klass in self.converterClasses)
- assert _issubclass_namedtuple(self.tupleClass), repr(self.tupleClass)
- assert len(self.tupleClass._fields) == len(self.converterClasses)
- assert tableClass is None # tableClass is unused by SimplValues
- BaseConverter.__init__(self, name, repeat, aux)
- self.converters = [
- klass(name=name, repeat=None, aux=None)
- for name, klass in zip(self.tupleClass._fields, self.converterClasses)
- ]
- self.convertersByName = {conv.name: conv for conv in self.converters}
- # returned by getRecordSize method
- self.staticSize = sum(c.staticSize for c in self.converters)
-
- def read(self, reader, font, tableDict):
- kwargs = {
- conv.name: conv.read(reader, font, tableDict)
- for conv in self.converters
- }
- return self.tupleClass(**kwargs)
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- for conv in self.converters:
- v = getattr(value, conv.name)
- # repeatIndex is unused for SimpleValues
- conv.write(writer, font, tableDict, v, repeatIndex=None)
-
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- assert value is not None
- defaults = value.__new__.__defaults__ or ()
- assert len(self.converters) >= len(defaults)
- values = {}
- required = object()
- for conv, default in zip_longest(
- reversed(self.converters),
- reversed(defaults),
- fillvalue=required,
- ):
- v = getattr(value, conv.name)
- if default is required or v != default:
- values[conv.name] = conv.toString(v)
- if attrs is None:
- attrs = []
- attrs.extend(
- (conv.name, values[conv.name])
- for conv in self.converters
- if conv.name in values
- )
- xmlWriter.simpletag(name, attrs)
- xmlWriter.newline()
-
- def xmlRead(self, attrs, content, font):
- converters = self.convertersByName
- kwargs = {
- k: converters[k].fromString(v)
- for k, v in attrs.items()
- }
- return self.tupleClass(**kwargs)
-
-
-class VarFixed(_NamedTupleConverter):
- tupleClass = VariableFloat
- converterClasses = [Fixed, ULong]
-
-
-class VarF2Dot14(_NamedTupleConverter):
- tupleClass = VariableFloat
- converterClasses = [F2Dot14, ULong]
-
-
-class VarInt16(_NamedTupleConverter):
- tupleClass = VariableInt
- converterClasses = [Short, ULong]
-
-
-class VarUInt16(_NamedTupleConverter):
- tupleClass = VariableInt
- converterClasses = [UShort, ULong]
-
class _UInt8Enum(UInt8):
enumClass = NotImplemented
@@ -1762,6 +1732,7 @@ converterMapping = {
"uint32": ULong,
"char64": Char64,
"Flags32": Flags32,
+ "VarIndex": VarIndex,
"Version": Version,
"Tag": Tag,
"GlyphID": GlyphID,
@@ -1770,6 +1741,7 @@ converterMapping = {
"DeciPoints": DeciPoints,
"Fixed": Fixed,
"F2Dot14": F2Dot14,
+ "Angle": Angle,
"struct": Struct,
"Offset": Table,
"LOffset": LTable,
@@ -1798,10 +1770,4 @@ converterMapping = {
"OffsetTo": lambda C: partial(Table, tableClass=C),
"LOffsetTo": lambda C: partial(LTable, tableClass=C),
"LOffset24To": lambda C: partial(Table24, tableClass=C),
-
- # Variable types
- "VarFixed": VarFixed,
- "VarF2Dot14": VarF2Dot14,
- "VarInt16": VarInt16,
- "VarUInt16": VarUInt16,
}