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.py3339
1 files changed, 1727 insertions, 1612 deletions
diff --git a/Lib/fontTools/ttLib/tables/otConverters.py b/Lib/fontTools/ttLib/tables/otConverters.py
index b08f1f19..390f1660 100644
--- a/Lib/fontTools/ttLib/tables/otConverters.py
+++ b/Lib/fontTools/ttLib/tables/otConverters.py
@@ -1,22 +1,34 @@
from fontTools.misc.fixedTools import (
- fixedToFloat as fi2fl,
- floatToFixed as fl2fi,
- floatToFixedToStr as fl2str,
- strToFixedToFloat as str2fl,
- ensureVersionIsLong as fi2ve,
- versionToFixed as ve2fi,
+ fixedToFloat as fi2fl,
+ floatToFixed as fl2fi,
+ floatToFixedToStr as fl2str,
+ strToFixedToFloat as str2fl,
+ ensureVersionIsLong as fi2ve,
+ versionToFixed as ve2fi,
)
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,
- ExtendMode as _ExtendMode,
- CompositeMode as _CompositeMode,
- NO_VARIATION_INDEX)
+from .otBase import (
+ CountReference,
+ FormatSwitchingBaseTable,
+ OTTableReader,
+ OTTableWriter,
+ ValueRecordFactory,
+)
+from .otTables import (
+ lookupTypes,
+ AATStateTable,
+ AATState,
+ AATAction,
+ ContextualMorphAction,
+ LigatureMorphAction,
+ InsertionMorphAction,
+ MorxSubtable,
+ ExtendMode as _ExtendMode,
+ CompositeMode as _CompositeMode,
+ NO_VARIATION_INDEX,
+)
from itertools import zip_longest
from functools import partial
import re
@@ -30,947 +42,1059 @@ istuple = lambda t: isinstance(t, tuple)
def buildConverters(tableSpec, tableNamespace):
- """Given a table spec from otData.py, build a converter object for each
- field of the table. This is called for each table in otData.py, and
- the results are assigned to the corresponding class in otTables.py."""
- converters = []
- convertersByName = {}
- for tp, name, repeat, aux, descr in tableSpec:
- tableName = name
- if name.startswith("ValueFormat"):
- assert tp == "uint16"
- converterClass = ValueFormat
- elif name.endswith("Count") or name in ("StructLength", "MorphType"):
- converterClass = {
- "uint8": ComputedUInt8,
- "uint16": ComputedUShort,
- "uint32": ComputedULong,
- }[tp]
- elif name == "SubTable":
- converterClass = SubTable
- elif name == "ExtSubTable":
- converterClass = ExtSubTable
- elif name == "SubStruct":
- converterClass = SubStruct
- elif name == "FeatureParams":
- converterClass = FeatureParams
- elif name in ("CIDGlyphMapping", "GlyphCIDMapping"):
- converterClass = StructWithLength
- else:
- if not tp in converterMapping and '(' not in tp:
- tableName = tp
- converterClass = Struct
- else:
- converterClass = eval(tp, tableNamespace, converterMapping)
-
- conv = converterClass(name, repeat, aux, description=descr)
-
- if conv.tableClass:
- # A "template" such as OffsetTo(AType) knowss the table class already
- tableClass = conv.tableClass
- elif tp in ('MortChain', 'MortSubtable', 'MorxChain'):
- tableClass = tableNamespace.get(tp)
- else:
- tableClass = tableNamespace.get(tableName)
-
- if not conv.tableClass:
- conv.tableClass = tableClass
-
- if name in ["SubTable", "ExtSubTable", "SubStruct"]:
- conv.lookupTypes = tableNamespace['lookupTypes']
- # also create reverse mapping
- for t in conv.lookupTypes.values():
- for cls in t.values():
- convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
- if name == "FeatureParams":
- conv.featureParamTypes = tableNamespace['featureParamTypes']
- conv.defaultFeatureParams = tableNamespace['FeatureParams']
- for cls in conv.featureParamTypes.values():
- convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
- converters.append(conv)
- assert name not in convertersByName, name
- convertersByName[name] = conv
- return converters, convertersByName
+ """Given a table spec from otData.py, build a converter object for each
+ field of the table. This is called for each table in otData.py, and
+ the results are assigned to the corresponding class in otTables.py."""
+ converters = []
+ convertersByName = {}
+ for tp, name, repeat, aux, descr in tableSpec:
+ tableName = name
+ if name.startswith("ValueFormat"):
+ assert tp == "uint16"
+ converterClass = ValueFormat
+ elif name.endswith("Count") or name in ("StructLength", "MorphType"):
+ converterClass = {
+ "uint8": ComputedUInt8,
+ "uint16": ComputedUShort,
+ "uint32": ComputedULong,
+ }[tp]
+ elif name == "SubTable":
+ converterClass = SubTable
+ elif name == "ExtSubTable":
+ converterClass = ExtSubTable
+ elif name == "SubStruct":
+ converterClass = SubStruct
+ elif name == "FeatureParams":
+ converterClass = FeatureParams
+ elif name in ("CIDGlyphMapping", "GlyphCIDMapping"):
+ converterClass = StructWithLength
+ else:
+ if not tp in converterMapping and "(" not in tp:
+ tableName = tp
+ converterClass = Struct
+ else:
+ converterClass = eval(tp, tableNamespace, converterMapping)
+
+ conv = converterClass(name, repeat, aux, description=descr)
+
+ if conv.tableClass:
+ # A "template" such as OffsetTo(AType) knowss the table class already
+ tableClass = conv.tableClass
+ elif tp in ("MortChain", "MortSubtable", "MorxChain"):
+ tableClass = tableNamespace.get(tp)
+ else:
+ tableClass = tableNamespace.get(tableName)
+
+ if not conv.tableClass:
+ conv.tableClass = tableClass
+
+ if name in ["SubTable", "ExtSubTable", "SubStruct"]:
+ conv.lookupTypes = tableNamespace["lookupTypes"]
+ # also create reverse mapping
+ for t in conv.lookupTypes.values():
+ for cls in t.values():
+ convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
+ if name == "FeatureParams":
+ conv.featureParamTypes = tableNamespace["featureParamTypes"]
+ conv.defaultFeatureParams = tableNamespace["FeatureParams"]
+ for cls in conv.featureParamTypes.values():
+ convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
+ converters.append(conv)
+ assert name not in convertersByName, name
+ convertersByName[name] = conv
+ return converters, convertersByName
class _MissingItem(tuple):
- __slots__ = ()
+ __slots__ = ()
try:
- from collections import UserList
+ from collections import UserList
except ImportError:
- from UserList import UserList
+ from UserList import UserList
class _LazyList(UserList):
-
- def __getslice__(self, i, j):
- return self.__getitem__(slice(i, j))
-
- def __getitem__(self, k):
- if isinstance(k, slice):
- indices = range(*k.indices(len(self)))
- return [self[i] for i in indices]
- item = self.data[k]
- if isinstance(item, _MissingItem):
- self.reader.seek(self.pos + item[0] * self.recordSize)
- item = self.conv.read(self.reader, self.font, {})
- self.data[k] = item
- return item
-
- def __add__(self, other):
- if isinstance(other, _LazyList):
- other = list(other)
- elif isinstance(other, list):
- pass
- else:
- return NotImplemented
- return list(self) + other
-
- def __radd__(self, other):
- if not isinstance(other, list):
- return NotImplemented
- return other + list(self)
+ def __getslice__(self, i, j):
+ return self.__getitem__(slice(i, j))
+
+ def __getitem__(self, k):
+ if isinstance(k, slice):
+ indices = range(*k.indices(len(self)))
+ return [self[i] for i in indices]
+ item = self.data[k]
+ if isinstance(item, _MissingItem):
+ self.reader.seek(self.pos + item[0] * self.recordSize)
+ item = self.conv.read(self.reader, self.font, {})
+ self.data[k] = item
+ return item
+
+ def __add__(self, other):
+ if isinstance(other, _LazyList):
+ other = list(other)
+ elif isinstance(other, list):
+ pass
+ else:
+ return NotImplemented
+ return list(self) + other
+
+ def __radd__(self, other):
+ if not isinstance(other, list):
+ return NotImplemented
+ return other + list(self)
class BaseConverter(object):
- """Base class for converter objects. Apart from the constructor, this
- is an abstract class."""
-
- def __init__(self, name, repeat, aux, tableClass=None, *, description=""):
- self.name = name
- self.repeat = repeat
- self.aux = aux
- self.tableClass = tableClass
- self.isCount = name.endswith("Count") or name in ['DesignAxisRecordSize', 'ValueRecordSize']
- self.isLookupType = name.endswith("LookupType") or name == "MorphType"
- self.isPropagated = name in [
- "ClassCount",
- "Class2Count",
- "FeatureTag",
- "SettingsCount",
- "VarRegionCount",
- "MappingCount",
- "RegionAxisCount",
- "DesignAxisCount",
- "DesignAxisRecordSize",
- "AxisValueCount",
- "ValueRecordSize",
- "AxisCount",
- "BaseGlyphRecordCount",
- "LayerRecordCount",
- ]
- self.description = description
-
- def readArray(self, reader, font, tableDict, count):
- """Read an array of values from the reader."""
- lazy = font.lazy and count > 8
- if lazy:
- recordSize = self.getRecordSize(reader)
- if recordSize is NotImplemented:
- lazy = False
- if not lazy:
- l = []
- for i in range(count):
- l.append(self.read(reader, font, tableDict))
- return l
- else:
- l = _LazyList()
- l.reader = reader.copy()
- l.pos = l.reader.pos
- l.font = font
- l.conv = self
- l.recordSize = recordSize
- l.extend(_MissingItem([i]) for i in range(count))
- reader.advance(count * recordSize)
- return l
-
- def getRecordSize(self, reader):
- if hasattr(self, 'staticSize'): return self.staticSize
- return NotImplemented
-
- def read(self, reader, font, tableDict):
- """Read a value from the reader."""
- raise NotImplementedError(self)
-
- def writeArray(self, writer, font, tableDict, values):
- 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."""
- raise NotImplementedError(self)
-
- def xmlRead(self, attrs, content, font):
- """Read a value from XML."""
- raise NotImplementedError(self)
-
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- """Write a value to XML."""
- raise NotImplementedError(self)
-
- varIndexBasePlusOffsetRE = re.compile(r"VarIndexBase\s*\+\s*(\d+)")
-
- def getVarIndexOffset(self) -> Optional[int]:
- """If description has `VarIndexBase + {offset}`, return the offset else None."""
- m = self.varIndexBasePlusOffsetRE.search(self.description)
- if not m:
- return None
- return int(m.group(1))
+ """Base class for converter objects. Apart from the constructor, this
+ is an abstract class."""
+
+ def __init__(self, name, repeat, aux, tableClass=None, *, description=""):
+ self.name = name
+ self.repeat = repeat
+ self.aux = aux
+ self.tableClass = tableClass
+ self.isCount = name.endswith("Count") or name in [
+ "DesignAxisRecordSize",
+ "ValueRecordSize",
+ ]
+ self.isLookupType = name.endswith("LookupType") or name == "MorphType"
+ self.isPropagated = name in [
+ "ClassCount",
+ "Class2Count",
+ "FeatureTag",
+ "SettingsCount",
+ "VarRegionCount",
+ "MappingCount",
+ "RegionAxisCount",
+ "DesignAxisCount",
+ "DesignAxisRecordSize",
+ "AxisValueCount",
+ "ValueRecordSize",
+ "AxisCount",
+ "BaseGlyphRecordCount",
+ "LayerRecordCount",
+ ]
+ self.description = description
+
+ def readArray(self, reader, font, tableDict, count):
+ """Read an array of values from the reader."""
+ lazy = font.lazy and count > 8
+ if lazy:
+ recordSize = self.getRecordSize(reader)
+ if recordSize is NotImplemented:
+ lazy = False
+ if not lazy:
+ l = []
+ for i in range(count):
+ l.append(self.read(reader, font, tableDict))
+ return l
+ else:
+ l = _LazyList()
+ l.reader = reader.copy()
+ l.pos = l.reader.pos
+ l.font = font
+ l.conv = self
+ l.recordSize = recordSize
+ l.extend(_MissingItem([i]) for i in range(count))
+ reader.advance(count * recordSize)
+ return l
+
+ def getRecordSize(self, reader):
+ if hasattr(self, "staticSize"):
+ return self.staticSize
+ return NotImplemented
+
+ def read(self, reader, font, tableDict):
+ """Read a value from the reader."""
+ raise NotImplementedError(self)
+
+ def writeArray(self, writer, font, tableDict, values):
+ 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."""
+ raise NotImplementedError(self)
+
+ def xmlRead(self, attrs, content, font):
+ """Read a value from XML."""
+ raise NotImplementedError(self)
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ """Write a value to XML."""
+ raise NotImplementedError(self)
+
+ varIndexBasePlusOffsetRE = re.compile(r"VarIndexBase\s*\+\s*(\d+)")
+
+ def getVarIndexOffset(self) -> Optional[int]:
+ """If description has `VarIndexBase + {offset}`, return the offset else None."""
+ m = self.varIndexBasePlusOffsetRE.search(self.description)
+ if not m:
+ return None
+ return int(m.group(1))
class SimpleValue(BaseConverter):
- @staticmethod
- def toString(value):
- return value
- @staticmethod
- def fromString(value):
- return value
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.simpletag(name, attrs + [("value", self.toString(value))])
- xmlWriter.newline()
- def xmlRead(self, attrs, content, font):
- return self.fromString(attrs["value"])
+ @staticmethod
+ def toString(value):
+ return value
+
+ @staticmethod
+ def fromString(value):
+ return value
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.simpletag(name, attrs + [("value", self.toString(value))])
+ xmlWriter.newline()
+
+ 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
+ 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):
- return int(value, 0)
+ @staticmethod
+ def fromString(value):
+ return int(value, 0)
+
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)
+ 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)
+ 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
+ @staticmethod
+ def toString(value):
+ return "0x%08X" % value
+
class VarIndex(OptionalValue, ULong):
- DEFAULT = NO_VARIATION_INDEX
+ DEFAULT = NO_VARIATION_INDEX
+
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)
+ 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)
+ 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)
+ 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)
+ 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
- def read(self, reader, font, tableDict):
- return reader.readUInt24()
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- writer.writeUInt24(value)
+ staticSize = 3
+
+ def read(self, reader, font, tableDict):
+ return reader.readUInt24()
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ writer.writeUInt24(value)
+
class ComputedInt(IntValue):
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- if value is not None:
- xmlWriter.comment("%s=%s" % (name, value))
- xmlWriter.newline()
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ if value is not None:
+ xmlWriter.comment("%s=%s" % (name, value))
+ xmlWriter.newline()
+
class ComputedUInt8(ComputedInt, UInt8):
- pass
+ pass
+
+
class ComputedUShort(ComputedInt, UShort):
- pass
+ pass
+
+
class ComputedULong(ComputedInt, ULong):
- pass
+ pass
+
class Tag(SimpleValue):
- staticSize = 4
- def read(self, reader, font, tableDict):
- return reader.readTag()
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- writer.writeTag(value)
+ staticSize = 4
+
+ def read(self, reader, font, tableDict):
+ return reader.readTag()
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ writer.writeTag(value)
+
class GlyphID(SimpleValue):
- staticSize = 2
- typecode = "H"
- def readArray(self, reader, font, tableDict, count):
- 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))
+ staticSize = 2
+ typecode = "H"
+
+ def readArray(self, reader, font, tableDict, count):
+ 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))
class GlyphID32(GlyphID):
- staticSize = 4
- typecode = "L"
+ staticSize = 4
+ typecode = "L"
class NameID(UShort):
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.simpletag(name, attrs + [("value", value)])
- if font and value:
- nameTable = font.get("name")
- if nameTable:
- name = nameTable.getDebugName(value)
- xmlWriter.write(" ")
- if name:
- xmlWriter.comment(name)
- else:
- xmlWriter.comment("missing from name table")
- log.warning("name id %d missing from name table" % value)
- xmlWriter.newline()
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.simpletag(name, attrs + [("value", value)])
+ if font and value:
+ nameTable = font.get("name")
+ if nameTable:
+ name = nameTable.getDebugName(value)
+ xmlWriter.write(" ")
+ if name:
+ xmlWriter.comment(name)
+ else:
+ xmlWriter.comment("missing from name table")
+ log.warning("name id %d missing from name table" % value)
+ xmlWriter.newline()
+
class STATFlags(UShort):
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.simpletag(name, attrs + [("value", value)])
- flags = []
- if value & 0x01:
- flags.append("OlderSiblingFontAttribute")
- if value & 0x02:
- flags.append("ElidableAxisValueName")
- if flags:
- xmlWriter.write(" ")
- xmlWriter.comment(" ".join(flags))
- xmlWriter.newline()
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.simpletag(name, attrs + [("value", value)])
+ flags = []
+ if value & 0x01:
+ flags.append("OlderSiblingFontAttribute")
+ if value & 0x02:
+ flags.append("ElidableAxisValueName")
+ if flags:
+ xmlWriter.write(" ")
+ xmlWriter.comment(" ".join(flags))
+ xmlWriter.newline()
+
class FloatValue(SimpleValue):
- @staticmethod
- def fromString(value):
- return float(value)
+ @staticmethod
+ def fromString(value):
+ return float(value)
+
class DeciPoints(FloatValue):
- staticSize = 2
- def read(self, reader, font, tableDict):
- return reader.readUShort() / 10
+ staticSize = 2
+
+ def read(self, reader, font, tableDict):
+ return reader.readUShort() / 10
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ writer.writeUShort(round(value * 10))
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- writer.writeUShort(round(value * 10))
class BaseFixedValue(FloatValue):
- staticSize = NotImplemented
- precisionBits = NotImplemented
- readerMethod = NotImplemented
- writerMethod = NotImplemented
- def read(self, reader, font, tableDict):
- return self.fromInt(getattr(reader, self.readerMethod)())
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- getattr(writer, self.writerMethod)(self.toInt(value))
- @classmethod
- def fromInt(cls, value):
- return fi2fl(value, cls.precisionBits)
- @classmethod
- def toInt(cls, value):
- return fl2fi(value, cls.precisionBits)
- @classmethod
- def fromString(cls, value):
- return str2fl(value, cls.precisionBits)
- @classmethod
- def toString(cls, value):
- return fl2str(value, cls.precisionBits)
+ staticSize = NotImplemented
+ precisionBits = NotImplemented
+ readerMethod = NotImplemented
+ writerMethod = NotImplemented
+
+ def read(self, reader, font, tableDict):
+ return self.fromInt(getattr(reader, self.readerMethod)())
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ getattr(writer, self.writerMethod)(self.toInt(value))
+
+ @classmethod
+ def fromInt(cls, value):
+ return fi2fl(value, cls.precisionBits)
+
+ @classmethod
+ def toInt(cls, value):
+ return fl2fi(value, cls.precisionBits)
+
+ @classmethod
+ def fromString(cls, value):
+ return str2fl(value, cls.precisionBits)
+
+ @classmethod
+ def toString(cls, value):
+ return fl2str(value, cls.precisionBits)
+
class Fixed(BaseFixedValue):
- staticSize = 4
- precisionBits = 16
- readerMethod = "readLong"
- writerMethod = "writeLong"
+ staticSize = 4
+ precisionBits = 16
+ readerMethod = "readLong"
+ writerMethod = "writeLong"
+
class F2Dot14(BaseFixedValue):
- staticSize = 2
- precisionBits = 14
- readerMethod = "readShort"
- writerMethod = "writeShort"
+ staticSize = 2
+ precisionBits = 14
+ readerMethod = "readShort"
+ writerMethod = "writeShort"
+
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.
- bias = 0.0
- factor = 1.0/(1<<14) * 180 # 0.010986328125
- @classmethod
- def fromInt(cls, value):
- return (super().fromInt(value) + cls.bias) * 180
- @classmethod
- def toInt(cls, value):
- return super().toInt((value / 180) - cls.bias)
- @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)
+ # 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.
+ bias = 0.0
+ factor = 1.0 / (1 << 14) * 180 # 0.010986328125
+
+ @classmethod
+ def fromInt(cls, value):
+ return (super().fromInt(value) + cls.bias) * 180
+
+ @classmethod
+ def toInt(cls, value):
+ return super().toInt((value / 180) - cls.bias)
+
+ @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 BiasedAngle(Angle):
- # A bias of 1.0 is used in the representation of start and end angles
- # of COLRv1 PaintSweepGradients to allow for encoding +360deg
- bias = 1.0
+ # A bias of 1.0 is used in the representation of start and end angles
+ # of COLRv1 PaintSweepGradients to allow for encoding +360deg
+ bias = 1.0
+
class Version(SimpleValue):
- staticSize = 4
- def read(self, reader, font, tableDict):
- value = reader.readLong()
- assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
- return value
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- value = fi2ve(value)
- assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
- writer.writeLong(value)
- @staticmethod
- def fromString(value):
- return ve2fi(value)
- @staticmethod
- def toString(value):
- return "0x%08x" % value
- @staticmethod
- def fromFloat(v):
- return fl2fi(v, 16)
+ staticSize = 4
+
+ def read(self, reader, font, tableDict):
+ value = reader.readLong()
+ return value
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ value = fi2ve(value)
+ writer.writeLong(value)
+
+ @staticmethod
+ def fromString(value):
+ return ve2fi(value)
+
+ @staticmethod
+ def toString(value):
+ return "0x%08x" % value
+
+ @staticmethod
+ def fromFloat(v):
+ return fl2fi(v, 16)
class Char64(SimpleValue):
- """An ASCII string with up to 64 characters.
-
- Unused character positions are filled with 0x00 bytes.
- Used in Apple AAT fonts in the `gcid` table.
- """
- staticSize = 64
-
- def read(self, reader, font, tableDict):
- data = reader.readData(self.staticSize)
- zeroPos = data.find(b"\0")
- if zeroPos >= 0:
- data = data[:zeroPos]
- s = tostr(data, encoding="ascii", errors="replace")
- if s != tostr(data, encoding="ascii", errors="ignore"):
- log.warning('replaced non-ASCII characters in "%s"' %
- s)
- return s
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- data = tobytes(value, encoding="ascii", errors="replace")
- if data != tobytes(value, encoding="ascii", errors="ignore"):
- log.warning('replacing non-ASCII characters in "%s"' %
- value)
- if len(data) > self.staticSize:
- log.warning('truncating overlong "%s" to %d bytes' %
- (value, self.staticSize))
- data = (data + b"\0" * self.staticSize)[:self.staticSize]
- writer.writeData(data)
+ """An ASCII string with up to 64 characters.
+
+ Unused character positions are filled with 0x00 bytes.
+ Used in Apple AAT fonts in the `gcid` table.
+ """
+
+ staticSize = 64
+
+ def read(self, reader, font, tableDict):
+ data = reader.readData(self.staticSize)
+ zeroPos = data.find(b"\0")
+ if zeroPos >= 0:
+ data = data[:zeroPos]
+ s = tostr(data, encoding="ascii", errors="replace")
+ if s != tostr(data, encoding="ascii", errors="ignore"):
+ log.warning('replaced non-ASCII characters in "%s"' % s)
+ return s
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ data = tobytes(value, encoding="ascii", errors="replace")
+ if data != tobytes(value, encoding="ascii", errors="ignore"):
+ log.warning('replacing non-ASCII characters in "%s"' % value)
+ if len(data) > self.staticSize:
+ log.warning(
+ 'truncating overlong "%s" to %d bytes' % (value, self.staticSize)
+ )
+ data = (data + b"\0" * self.staticSize)[: self.staticSize]
+ writer.writeData(data)
class Struct(BaseConverter):
-
- def getRecordSize(self, reader):
- return self.tableClass and self.tableClass.getRecordSize(reader)
-
- def read(self, reader, font, tableDict):
- table = self.tableClass()
- table.decompile(reader, font)
- return table
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- value.compile(writer, font)
-
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- if value is None:
- if attrs:
- # If there are attributes (probably index), then
- # don't drop this even if it's NULL. It will mess
- # up the array indices of the containing element.
- xmlWriter.simpletag(name, attrs + [("empty", 1)])
- xmlWriter.newline()
- else:
- pass # NULL table, ignore
- else:
- value.toXML(xmlWriter, font, attrs, name=name)
-
- def xmlRead(self, attrs, content, font):
- if "empty" in attrs and safeEval(attrs["empty"]):
- return None
- table = self.tableClass()
- Format = attrs.get("Format")
- if Format is not None:
- table.Format = int(Format)
-
- noPostRead = not hasattr(table, 'postRead')
- if noPostRead:
- # TODO Cache table.hasPropagated.
- cleanPropagation = False
- for conv in table.getConverters():
- if conv.isPropagated:
- cleanPropagation = True
- if not hasattr(font, '_propagator'):
- font._propagator = {}
- propagator = font._propagator
- assert conv.name not in propagator, (conv.name, propagator)
- setattr(table, conv.name, None)
- propagator[conv.name] = CountReference(table.__dict__, conv.name)
-
- for element in content:
- if isinstance(element, tuple):
- name, attrs, content = element
- table.fromXML(name, attrs, content, font)
- else:
- pass
-
- table.populateDefaults(propagator=getattr(font, '_propagator', None))
-
- if noPostRead:
- if cleanPropagation:
- for conv in table.getConverters():
- if conv.isPropagated:
- propagator = font._propagator
- del propagator[conv.name]
- if not propagator:
- del font._propagator
-
- return table
-
- def __repr__(self):
- return "Struct of " + repr(self.tableClass)
+ def getRecordSize(self, reader):
+ return self.tableClass and self.tableClass.getRecordSize(reader)
+
+ def read(self, reader, font, tableDict):
+ table = self.tableClass()
+ table.decompile(reader, font)
+ return table
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ value.compile(writer, font)
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ if value is None:
+ if attrs:
+ # If there are attributes (probably index), then
+ # don't drop this even if it's NULL. It will mess
+ # up the array indices of the containing element.
+ xmlWriter.simpletag(name, attrs + [("empty", 1)])
+ xmlWriter.newline()
+ else:
+ pass # NULL table, ignore
+ else:
+ value.toXML(xmlWriter, font, attrs, name=name)
+
+ def xmlRead(self, attrs, content, font):
+ if "empty" in attrs and safeEval(attrs["empty"]):
+ return None
+ table = self.tableClass()
+ Format = attrs.get("Format")
+ if Format is not None:
+ table.Format = int(Format)
+
+ noPostRead = not hasattr(table, "postRead")
+ if noPostRead:
+ # TODO Cache table.hasPropagated.
+ cleanPropagation = False
+ for conv in table.getConverters():
+ if conv.isPropagated:
+ cleanPropagation = True
+ if not hasattr(font, "_propagator"):
+ font._propagator = {}
+ propagator = font._propagator
+ assert conv.name not in propagator, (conv.name, propagator)
+ setattr(table, conv.name, None)
+ propagator[conv.name] = CountReference(table.__dict__, conv.name)
+
+ for element in content:
+ if isinstance(element, tuple):
+ name, attrs, content = element
+ table.fromXML(name, attrs, content, font)
+ else:
+ pass
+
+ table.populateDefaults(propagator=getattr(font, "_propagator", None))
+
+ if noPostRead:
+ if cleanPropagation:
+ for conv in table.getConverters():
+ if conv.isPropagated:
+ propagator = font._propagator
+ del propagator[conv.name]
+ if not propagator:
+ del font._propagator
+
+ return table
+
+ def __repr__(self):
+ return "Struct of " + repr(self.tableClass)
class StructWithLength(Struct):
- def read(self, reader, font, tableDict):
- pos = reader.pos
- table = self.tableClass()
- table.decompile(reader, font)
- reader.seek(pos + table.StructLength)
- return table
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- for convIndex, conv in enumerate(value.getConverters()):
- if conv.name == "StructLength":
- break
- lengthIndex = len(writer.items) + convIndex
- if isinstance(value, FormatSwitchingBaseTable):
- lengthIndex += 1 # implicit Format field
- deadbeef = {1:0xDE, 2:0xDEAD, 4:0xDEADBEEF}[conv.staticSize]
-
- before = writer.getDataLength()
- value.StructLength = deadbeef
- value.compile(writer, font)
- length = writer.getDataLength() - before
- lengthWriter = writer.getSubWriter()
- conv.write(lengthWriter, font, tableDict, length)
- assert(writer.items[lengthIndex] ==
- b"\xde\xad\xbe\xef"[:conv.staticSize])
- writer.items[lengthIndex] = lengthWriter.getAllData()
+ def read(self, reader, font, tableDict):
+ pos = reader.pos
+ table = self.tableClass()
+ table.decompile(reader, font)
+ reader.seek(pos + table.StructLength)
+ return table
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ for convIndex, conv in enumerate(value.getConverters()):
+ if conv.name == "StructLength":
+ break
+ lengthIndex = len(writer.items) + convIndex
+ if isinstance(value, FormatSwitchingBaseTable):
+ lengthIndex += 1 # implicit Format field
+ deadbeef = {1: 0xDE, 2: 0xDEAD, 4: 0xDEADBEEF}[conv.staticSize]
+
+ before = writer.getDataLength()
+ value.StructLength = deadbeef
+ value.compile(writer, font)
+ length = writer.getDataLength() - before
+ lengthWriter = writer.getSubWriter()
+ conv.write(lengthWriter, font, tableDict, length)
+ assert writer.items[lengthIndex] == b"\xde\xad\xbe\xef"[: conv.staticSize]
+ writer.items[lengthIndex] = lengthWriter.getAllData()
class Table(Struct):
+ staticSize = 2
+
+ def readOffset(self, reader):
+ return reader.readUShort()
+
+ def writeNullOffset(self, writer):
+ writer.writeUShort(0)
+
+ def read(self, reader, font, tableDict):
+ offset = self.readOffset(reader)
+ if offset == 0:
+ return None
+ table = self.tableClass()
+ reader = reader.getSubReader(offset)
+ if font.lazy:
+ table.reader = reader
+ table.font = font
+ else:
+ table.decompile(reader, font)
+ return table
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ if value is None:
+ self.writeNullOffset(writer)
+ else:
+ subWriter = writer.getSubWriter()
+ subWriter.name = self.name
+ if repeatIndex is not None:
+ subWriter.repeatIndex = repeatIndex
+ writer.writeSubTable(subWriter, offsetSize=self.staticSize)
+ value.compile(subWriter, font)
- staticSize = 2
-
- def readOffset(self, reader):
- return reader.readUShort()
-
- def writeNullOffset(self, writer):
- writer.writeUShort(0)
-
- def read(self, reader, font, tableDict):
- offset = self.readOffset(reader)
- if offset == 0:
- return None
- table = self.tableClass()
- reader = reader.getSubReader(offset)
- if font.lazy:
- table.reader = reader
- table.font = font
- else:
- table.decompile(reader, font)
- return table
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- if value is None:
- self.writeNullOffset(writer)
- else:
- subWriter = writer.getSubWriter(offsetSize=self.staticSize)
- subWriter.name = self.name
- if repeatIndex is not None:
- subWriter.repeatIndex = repeatIndex
- writer.writeSubTable(subWriter)
- value.compile(subWriter, font)
class LTable(Table):
+ staticSize = 4
- staticSize = 4
+ def readOffset(self, reader):
+ return reader.readULong()
- def readOffset(self, reader):
- return reader.readULong()
-
- def writeNullOffset(self, writer):
- writer.writeULong(0)
+ def writeNullOffset(self, writer):
+ writer.writeULong(0)
# Table pointed to by a 24-bit, 3-byte long offset
class Table24(Table):
+ staticSize = 3
- staticSize = 3
-
- def readOffset(self, reader):
- return reader.readUInt24()
+ def readOffset(self, reader):
+ return reader.readUInt24()
- def writeNullOffset(self, writer):
- writer.writeUInt24(0)
+ def writeNullOffset(self, writer):
+ writer.writeUInt24(0)
# TODO Clean / merge the SubTable and SubStruct
+
class SubStruct(Struct):
- def getConverter(self, tableType, lookupType):
- tableClass = self.lookupTypes[tableType][lookupType]
- return self.__class__(self.name, self.repeat, self.aux, tableClass)
+ def getConverter(self, tableType, lookupType):
+ tableClass = self.lookupTypes[tableType][lookupType]
+ return self.__class__(self.name, self.repeat, self.aux, tableClass)
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ super(SubStruct, self).xmlWrite(xmlWriter, font, value, None, attrs)
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- super(SubStruct, self).xmlWrite(xmlWriter, font, value, None, attrs)
class SubTable(Table):
- def getConverter(self, tableType, lookupType):
- tableClass = self.lookupTypes[tableType][lookupType]
- return self.__class__(self.name, self.repeat, self.aux, tableClass)
+ def getConverter(self, tableType, lookupType):
+ tableClass = self.lookupTypes[tableType][lookupType]
+ return self.__class__(self.name, self.repeat, self.aux, tableClass)
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- super(SubTable, self).xmlWrite(xmlWriter, font, value, None, attrs)
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ super(SubTable, self).xmlWrite(xmlWriter, font, value, None, attrs)
-class ExtSubTable(LTable, SubTable):
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- writer.Extension = True # actually, mere presence of the field flags it as an Ext Subtable writer.
- Table.write(self, writer, font, tableDict, value, repeatIndex)
+class ExtSubTable(LTable, SubTable):
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ writer.Extension = True # actually, mere presence of the field flags it as an Ext Subtable writer.
+ Table.write(self, writer, font, tableDict, value, repeatIndex)
class FeatureParams(Table):
- def getConverter(self, featureTag):
- tableClass = self.featureParamTypes.get(featureTag, self.defaultFeatureParams)
- return self.__class__(self.name, self.repeat, self.aux, tableClass)
+ def getConverter(self, featureTag):
+ tableClass = self.featureParamTypes.get(featureTag, self.defaultFeatureParams)
+ return self.__class__(self.name, self.repeat, self.aux, tableClass)
class ValueFormat(IntValue):
- staticSize = 2
- def __init__(self, name, repeat, aux, tableClass=None, *, description=""):
- BaseConverter.__init__(
- self, name, repeat, aux, tableClass, description=description
- )
- self.which = "ValueFormat" + ("2" if name[-1] == "2" else "1")
- def read(self, reader, font, tableDict):
- format = reader.readUShort()
- reader[self.which] = ValueRecordFactory(format)
- return format
- def write(self, writer, font, tableDict, format, repeatIndex=None):
- writer.writeUShort(format)
- writer[self.which] = ValueRecordFactory(format)
+ staticSize = 2
+
+ def __init__(self, name, repeat, aux, tableClass=None, *, description=""):
+ BaseConverter.__init__(
+ self, name, repeat, aux, tableClass, description=description
+ )
+ self.which = "ValueFormat" + ("2" if name[-1] == "2" else "1")
+
+ def read(self, reader, font, tableDict):
+ format = reader.readUShort()
+ reader[self.which] = ValueRecordFactory(format)
+ return format
+
+ def write(self, writer, font, tableDict, format, repeatIndex=None):
+ writer.writeUShort(format)
+ writer[self.which] = ValueRecordFactory(format)
class ValueRecord(ValueFormat):
- def getRecordSize(self, reader):
- return 2 * len(reader[self.which])
- def read(self, reader, font, tableDict):
- return reader[self.which].readValueRecord(reader, font)
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- writer[self.which].writeValueRecord(writer, font, value)
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- if value is None:
- pass # NULL table, ignore
- else:
- value.toXML(xmlWriter, font, self.name, attrs)
- def xmlRead(self, attrs, content, font):
- from .otBase import ValueRecord
- value = ValueRecord()
- value.fromXML(None, attrs, content, font)
- return value
+ def getRecordSize(self, reader):
+ return 2 * len(reader[self.which])
+
+ def read(self, reader, font, tableDict):
+ return reader[self.which].readValueRecord(reader, font)
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ writer[self.which].writeValueRecord(writer, font, value)
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ if value is None:
+ pass # NULL table, ignore
+ else:
+ value.toXML(xmlWriter, font, self.name, attrs)
+
+ def xmlRead(self, attrs, content, font):
+ from .otBase import ValueRecord
+
+ value = ValueRecord()
+ value.fromXML(None, attrs, content, font)
+ return value
class AATLookup(BaseConverter):
- BIN_SEARCH_HEADER_SIZE = 10
-
- def __init__(self, name, repeat, aux, tableClass, *, description=""):
- BaseConverter.__init__(
- self, name, repeat, aux, tableClass, description=description
- )
- if issubclass(self.tableClass, SimpleValue):
- self.converter = self.tableClass(name='Value', repeat=None, aux=None)
- else:
- self.converter = Table(name='Value', repeat=None, aux=None, tableClass=self.tableClass)
-
- def read(self, reader, font, tableDict):
- format = reader.readUShort()
- if format == 0:
- return self.readFormat0(reader, font)
- elif format == 2:
- return self.readFormat2(reader, font)
- elif format == 4:
- return self.readFormat4(reader, font)
- elif format == 6:
- return self.readFormat6(reader, font)
- elif format == 8:
- return self.readFormat8(reader, font)
- else:
- assert False, "unsupported lookup format: %d" % format
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- values = list(sorted([(font.getGlyphID(glyph), val)
- for glyph, val in value.items()]))
- # TODO: Also implement format 4.
- formats = list(sorted(filter(None, [
- self.buildFormat0(writer, font, values),
- self.buildFormat2(writer, font, values),
- self.buildFormat6(writer, font, values),
- self.buildFormat8(writer, font, values),
- ])))
- # We use the format ID as secondary sort key to make the output
- # deterministic when multiple formats have same encoded size.
- dataSize, lookupFormat, writeMethod = formats[0]
- pos = writer.getDataLength()
- writeMethod()
- actualSize = writer.getDataLength() - pos
- assert actualSize == dataSize, (
- "AATLookup format %d claimed to write %d bytes, but wrote %d" %
- (lookupFormat, dataSize, actualSize))
-
- @staticmethod
- def writeBinSearchHeader(writer, numUnits, unitSize):
- writer.writeUShort(unitSize)
- writer.writeUShort(numUnits)
- searchRange, entrySelector, rangeShift = \
- getSearchRange(n=numUnits, itemSize=unitSize)
- writer.writeUShort(searchRange)
- writer.writeUShort(entrySelector)
- writer.writeUShort(rangeShift)
-
- def buildFormat0(self, writer, font, values):
- numGlyphs = len(font.getGlyphOrder())
- if len(values) != numGlyphs:
- return None
- valueSize = self.converter.staticSize
- return (2 + numGlyphs * valueSize, 0,
- lambda: self.writeFormat0(writer, font, values))
-
- def writeFormat0(self, writer, font, values):
- writer.writeUShort(0)
- for glyphID_, value in values:
- self.converter.write(
- writer, font, tableDict=None,
- value=value, repeatIndex=None)
-
- def buildFormat2(self, writer, font, values):
- segStart, segValue = values[0]
- segEnd = segStart
- segments = []
- for glyphID, curValue in values[1:]:
- if glyphID != segEnd + 1 or curValue != segValue:
- segments.append((segStart, segEnd, segValue))
- segStart = segEnd = glyphID
- segValue = curValue
- else:
- segEnd = glyphID
- segments.append((segStart, segEnd, segValue))
- valueSize = self.converter.staticSize
- numUnits, unitSize = len(segments) + 1, valueSize + 4
- return (2 + self.BIN_SEARCH_HEADER_SIZE + numUnits * unitSize, 2,
- lambda: self.writeFormat2(writer, font, segments))
-
- def writeFormat2(self, writer, font, segments):
- writer.writeUShort(2)
- valueSize = self.converter.staticSize
- numUnits, unitSize = len(segments), valueSize + 4
- self.writeBinSearchHeader(writer, numUnits, unitSize)
- for firstGlyph, lastGlyph, value in segments:
- writer.writeUShort(lastGlyph)
- writer.writeUShort(firstGlyph)
- self.converter.write(
- writer, font, tableDict=None,
- value=value, repeatIndex=None)
- writer.writeUShort(0xFFFF)
- writer.writeUShort(0xFFFF)
- writer.writeData(b'\x00' * valueSize)
-
- def buildFormat6(self, writer, font, values):
- valueSize = self.converter.staticSize
- numUnits, unitSize = len(values), valueSize + 2
- return (2 + self.BIN_SEARCH_HEADER_SIZE + (numUnits + 1) * unitSize, 6,
- lambda: self.writeFormat6(writer, font, values))
-
- def writeFormat6(self, writer, font, values):
- writer.writeUShort(6)
- valueSize = self.converter.staticSize
- numUnits, unitSize = len(values), valueSize + 2
- self.writeBinSearchHeader(writer, numUnits, unitSize)
- for glyphID, value in values:
- writer.writeUShort(glyphID)
- self.converter.write(
- writer, font, tableDict=None,
- value=value, repeatIndex=None)
- writer.writeUShort(0xFFFF)
- writer.writeData(b'\x00' * valueSize)
-
- def buildFormat8(self, writer, font, values):
- minGlyphID, maxGlyphID = values[0][0], values[-1][0]
- if len(values) != maxGlyphID - minGlyphID + 1:
- return None
- valueSize = self.converter.staticSize
- return (6 + len(values) * valueSize, 8,
- lambda: self.writeFormat8(writer, font, values))
-
- def writeFormat8(self, writer, font, values):
- firstGlyphID = values[0][0]
- writer.writeUShort(8)
- writer.writeUShort(firstGlyphID)
- writer.writeUShort(len(values))
- for _, value in values:
- self.converter.write(
- writer, font, tableDict=None,
- value=value, repeatIndex=None)
-
- def readFormat0(self, reader, font):
- numGlyphs = len(font.getGlyphOrder())
- data = self.converter.readArray(
- reader, font, tableDict=None, count=numGlyphs)
- return {font.getGlyphName(k): value
- for k, value in enumerate(data)}
-
- def readFormat2(self, reader, font):
- mapping = {}
- pos = reader.pos - 2 # start of table is at UShort for format
- unitSize, numUnits = reader.readUShort(), reader.readUShort()
- assert unitSize >= 4 + self.converter.staticSize, unitSize
- for i in range(numUnits):
- reader.seek(pos + i * unitSize + 12)
- last = reader.readUShort()
- first = reader.readUShort()
- value = self.converter.read(reader, font, tableDict=None)
- if last != 0xFFFF:
- for k in range(first, last + 1):
- mapping[font.getGlyphName(k)] = value
- return mapping
-
- def readFormat4(self, reader, font):
- mapping = {}
- pos = reader.pos - 2 # start of table is at UShort for format
- unitSize = reader.readUShort()
- assert unitSize >= 6, unitSize
- for i in range(reader.readUShort()):
- reader.seek(pos + i * unitSize + 12)
- last = reader.readUShort()
- first = reader.readUShort()
- offset = reader.readUShort()
- if last != 0xFFFF:
- dataReader = reader.getSubReader(0) # relative to current position
- dataReader.seek(pos + offset) # relative to start of table
- data = self.converter.readArray(
- dataReader, font, tableDict=None,
- count=last - first + 1)
- for k, v in enumerate(data):
- mapping[font.getGlyphName(first + k)] = v
- return mapping
-
- def readFormat6(self, reader, font):
- mapping = {}
- pos = reader.pos - 2 # start of table is at UShort for format
- unitSize = reader.readUShort()
- assert unitSize >= 2 + self.converter.staticSize, unitSize
- for i in range(reader.readUShort()):
- reader.seek(pos + i * unitSize + 12)
- glyphID = reader.readUShort()
- value = self.converter.read(
- reader, font, tableDict=None)
- if glyphID != 0xFFFF:
- mapping[font.getGlyphName(glyphID)] = value
- return mapping
-
- def readFormat8(self, reader, font):
- first = reader.readUShort()
- count = reader.readUShort()
- data = self.converter.readArray(
- reader, font, tableDict=None, count=count)
- return {font.getGlyphName(first + k): value
- for (k, value) in enumerate(data)}
-
- def xmlRead(self, attrs, content, font):
- value = {}
- for element in content:
- if isinstance(element, tuple):
- name, a, eltContent = element
- if name == "Lookup":
- value[a["glyph"]] = self.converter.xmlRead(a, eltContent, font)
- return value
-
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.begintag(name, attrs)
- xmlWriter.newline()
- for glyph, value in sorted(value.items()):
- self.converter.xmlWrite(
- xmlWriter, font, value=value,
- name="Lookup", attrs=[("glyph", glyph)])
- xmlWriter.endtag(name)
- xmlWriter.newline()
+ BIN_SEARCH_HEADER_SIZE = 10
+
+ def __init__(self, name, repeat, aux, tableClass, *, description=""):
+ BaseConverter.__init__(
+ self, name, repeat, aux, tableClass, description=description
+ )
+ if issubclass(self.tableClass, SimpleValue):
+ self.converter = self.tableClass(name="Value", repeat=None, aux=None)
+ else:
+ self.converter = Table(
+ name="Value", repeat=None, aux=None, tableClass=self.tableClass
+ )
+
+ def read(self, reader, font, tableDict):
+ format = reader.readUShort()
+ if format == 0:
+ return self.readFormat0(reader, font)
+ elif format == 2:
+ return self.readFormat2(reader, font)
+ elif format == 4:
+ return self.readFormat4(reader, font)
+ elif format == 6:
+ return self.readFormat6(reader, font)
+ elif format == 8:
+ return self.readFormat8(reader, font)
+ else:
+ assert False, "unsupported lookup format: %d" % format
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ values = list(
+ sorted([(font.getGlyphID(glyph), val) for glyph, val in value.items()])
+ )
+ # TODO: Also implement format 4.
+ formats = list(
+ sorted(
+ filter(
+ None,
+ [
+ self.buildFormat0(writer, font, values),
+ self.buildFormat2(writer, font, values),
+ self.buildFormat6(writer, font, values),
+ self.buildFormat8(writer, font, values),
+ ],
+ )
+ )
+ )
+ # We use the format ID as secondary sort key to make the output
+ # deterministic when multiple formats have same encoded size.
+ dataSize, lookupFormat, writeMethod = formats[0]
+ pos = writer.getDataLength()
+ writeMethod()
+ actualSize = writer.getDataLength() - pos
+ assert (
+ actualSize == dataSize
+ ), "AATLookup format %d claimed to write %d bytes, but wrote %d" % (
+ lookupFormat,
+ dataSize,
+ actualSize,
+ )
+
+ @staticmethod
+ def writeBinSearchHeader(writer, numUnits, unitSize):
+ writer.writeUShort(unitSize)
+ writer.writeUShort(numUnits)
+ searchRange, entrySelector, rangeShift = getSearchRange(
+ n=numUnits, itemSize=unitSize
+ )
+ writer.writeUShort(searchRange)
+ writer.writeUShort(entrySelector)
+ writer.writeUShort(rangeShift)
+
+ def buildFormat0(self, writer, font, values):
+ numGlyphs = len(font.getGlyphOrder())
+ if len(values) != numGlyphs:
+ return None
+ valueSize = self.converter.staticSize
+ return (
+ 2 + numGlyphs * valueSize,
+ 0,
+ lambda: self.writeFormat0(writer, font, values),
+ )
+
+ def writeFormat0(self, writer, font, values):
+ writer.writeUShort(0)
+ for glyphID_, value in values:
+ self.converter.write(
+ writer, font, tableDict=None, value=value, repeatIndex=None
+ )
+
+ def buildFormat2(self, writer, font, values):
+ segStart, segValue = values[0]
+ segEnd = segStart
+ segments = []
+ for glyphID, curValue in values[1:]:
+ if glyphID != segEnd + 1 or curValue != segValue:
+ segments.append((segStart, segEnd, segValue))
+ segStart = segEnd = glyphID
+ segValue = curValue
+ else:
+ segEnd = glyphID
+ segments.append((segStart, segEnd, segValue))
+ valueSize = self.converter.staticSize
+ numUnits, unitSize = len(segments) + 1, valueSize + 4
+ return (
+ 2 + self.BIN_SEARCH_HEADER_SIZE + numUnits * unitSize,
+ 2,
+ lambda: self.writeFormat2(writer, font, segments),
+ )
+
+ def writeFormat2(self, writer, font, segments):
+ writer.writeUShort(2)
+ valueSize = self.converter.staticSize
+ numUnits, unitSize = len(segments), valueSize + 4
+ self.writeBinSearchHeader(writer, numUnits, unitSize)
+ for firstGlyph, lastGlyph, value in segments:
+ writer.writeUShort(lastGlyph)
+ writer.writeUShort(firstGlyph)
+ self.converter.write(
+ writer, font, tableDict=None, value=value, repeatIndex=None
+ )
+ writer.writeUShort(0xFFFF)
+ writer.writeUShort(0xFFFF)
+ writer.writeData(b"\x00" * valueSize)
+
+ def buildFormat6(self, writer, font, values):
+ valueSize = self.converter.staticSize
+ numUnits, unitSize = len(values), valueSize + 2
+ return (
+ 2 + self.BIN_SEARCH_HEADER_SIZE + (numUnits + 1) * unitSize,
+ 6,
+ lambda: self.writeFormat6(writer, font, values),
+ )
+
+ def writeFormat6(self, writer, font, values):
+ writer.writeUShort(6)
+ valueSize = self.converter.staticSize
+ numUnits, unitSize = len(values), valueSize + 2
+ self.writeBinSearchHeader(writer, numUnits, unitSize)
+ for glyphID, value in values:
+ writer.writeUShort(glyphID)
+ self.converter.write(
+ writer, font, tableDict=None, value=value, repeatIndex=None
+ )
+ writer.writeUShort(0xFFFF)
+ writer.writeData(b"\x00" * valueSize)
+
+ def buildFormat8(self, writer, font, values):
+ minGlyphID, maxGlyphID = values[0][0], values[-1][0]
+ if len(values) != maxGlyphID - minGlyphID + 1:
+ return None
+ valueSize = self.converter.staticSize
+ return (
+ 6 + len(values) * valueSize,
+ 8,
+ lambda: self.writeFormat8(writer, font, values),
+ )
+
+ def writeFormat8(self, writer, font, values):
+ firstGlyphID = values[0][0]
+ writer.writeUShort(8)
+ writer.writeUShort(firstGlyphID)
+ writer.writeUShort(len(values))
+ for _, value in values:
+ self.converter.write(
+ writer, font, tableDict=None, value=value, repeatIndex=None
+ )
+
+ def readFormat0(self, reader, font):
+ numGlyphs = len(font.getGlyphOrder())
+ data = self.converter.readArray(reader, font, tableDict=None, count=numGlyphs)
+ return {font.getGlyphName(k): value for k, value in enumerate(data)}
+
+ def readFormat2(self, reader, font):
+ mapping = {}
+ pos = reader.pos - 2 # start of table is at UShort for format
+ unitSize, numUnits = reader.readUShort(), reader.readUShort()
+ assert unitSize >= 4 + self.converter.staticSize, unitSize
+ for i in range(numUnits):
+ reader.seek(pos + i * unitSize + 12)
+ last = reader.readUShort()
+ first = reader.readUShort()
+ value = self.converter.read(reader, font, tableDict=None)
+ if last != 0xFFFF:
+ for k in range(first, last + 1):
+ mapping[font.getGlyphName(k)] = value
+ return mapping
+
+ def readFormat4(self, reader, font):
+ mapping = {}
+ pos = reader.pos - 2 # start of table is at UShort for format
+ unitSize = reader.readUShort()
+ assert unitSize >= 6, unitSize
+ for i in range(reader.readUShort()):
+ reader.seek(pos + i * unitSize + 12)
+ last = reader.readUShort()
+ first = reader.readUShort()
+ offset = reader.readUShort()
+ if last != 0xFFFF:
+ dataReader = reader.getSubReader(0) # relative to current position
+ dataReader.seek(pos + offset) # relative to start of table
+ data = self.converter.readArray(
+ dataReader, font, tableDict=None, count=last - first + 1
+ )
+ for k, v in enumerate(data):
+ mapping[font.getGlyphName(first + k)] = v
+ return mapping
+
+ def readFormat6(self, reader, font):
+ mapping = {}
+ pos = reader.pos - 2 # start of table is at UShort for format
+ unitSize = reader.readUShort()
+ assert unitSize >= 2 + self.converter.staticSize, unitSize
+ for i in range(reader.readUShort()):
+ reader.seek(pos + i * unitSize + 12)
+ glyphID = reader.readUShort()
+ value = self.converter.read(reader, font, tableDict=None)
+ if glyphID != 0xFFFF:
+ mapping[font.getGlyphName(glyphID)] = value
+ return mapping
+
+ def readFormat8(self, reader, font):
+ first = reader.readUShort()
+ count = reader.readUShort()
+ data = self.converter.readArray(reader, font, tableDict=None, count=count)
+ return {font.getGlyphName(first + k): value for (k, value) in enumerate(data)}
+
+ def xmlRead(self, attrs, content, font):
+ value = {}
+ for element in content:
+ if isinstance(element, tuple):
+ name, a, eltContent = element
+ if name == "Lookup":
+ value[a["glyph"]] = self.converter.xmlRead(a, eltContent, font)
+ return value
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.begintag(name, attrs)
+ xmlWriter.newline()
+ for glyph, value in sorted(value.items()):
+ self.converter.xmlWrite(
+ xmlWriter, font, value=value, name="Lookup", attrs=[("glyph", glyph)]
+ )
+ xmlWriter.endtag(name)
+ xmlWriter.newline()
# The AAT 'ankr' table has an unusual structure: An offset to an AATLookup
@@ -981,831 +1105,822 @@ class AATLookup(BaseConverter):
# to the data table to the offset found in the AATLookup, and then use
# the sum of these two offsets to find the actual data.
class AATLookupWithDataOffset(BaseConverter):
- def read(self, reader, font, tableDict):
- lookupOffset = reader.readULong()
- dataOffset = reader.readULong()
- lookupReader = reader.getSubReader(lookupOffset)
- lookup = AATLookup('DataOffsets', None, None, UShort)
- offsets = lookup.read(lookupReader, font, tableDict)
- result = {}
- for glyph, offset in offsets.items():
- dataReader = reader.getSubReader(offset + dataOffset)
- item = self.tableClass()
- item.decompile(dataReader, font)
- result[glyph] = item
- return result
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- # We do not work with OTTableWriter sub-writers because
- # the offsets in our AATLookup are relative to our data
- # table, for which we need to provide an offset value itself.
- # It might have been possible to somehow make a kludge for
- # performing this indirect offset computation directly inside
- # OTTableWriter. But this would have made the internal logic
- # of OTTableWriter even more complex than it already is,
- # so we decided to roll our own offset computation for the
- # contents of the AATLookup and associated data table.
- offsetByGlyph, offsetByData, dataLen = {}, {}, 0
- compiledData = []
- for glyph in sorted(value, key=font.getGlyphID):
- subWriter = OTTableWriter()
- value[glyph].compile(subWriter, font)
- data = subWriter.getAllData()
- offset = offsetByData.get(data, None)
- if offset == None:
- offset = dataLen
- dataLen = dataLen + len(data)
- offsetByData[data] = offset
- compiledData.append(data)
- offsetByGlyph[glyph] = offset
- # For calculating the offsets to our AATLookup and data table,
- # we can use the regular OTTableWriter infrastructure.
- lookupWriter = writer.getSubWriter(offsetSize=4)
- lookup = AATLookup('DataOffsets', None, None, UShort)
- lookup.write(lookupWriter, font, tableDict, offsetByGlyph, None)
-
- dataWriter = writer.getSubWriter(offsetSize=4)
- writer.writeSubTable(lookupWriter)
- writer.writeSubTable(dataWriter)
- for d in compiledData:
- dataWriter.writeData(d)
-
- def xmlRead(self, attrs, content, font):
- lookup = AATLookup('DataOffsets', None, None, self.tableClass)
- return lookup.xmlRead(attrs, content, font)
-
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- lookup = AATLookup('DataOffsets', None, None, self.tableClass)
- lookup.xmlWrite(xmlWriter, font, value, name, attrs)
+ def read(self, reader, font, tableDict):
+ lookupOffset = reader.readULong()
+ dataOffset = reader.readULong()
+ lookupReader = reader.getSubReader(lookupOffset)
+ lookup = AATLookup("DataOffsets", None, None, UShort)
+ offsets = lookup.read(lookupReader, font, tableDict)
+ result = {}
+ for glyph, offset in offsets.items():
+ dataReader = reader.getSubReader(offset + dataOffset)
+ item = self.tableClass()
+ item.decompile(dataReader, font)
+ result[glyph] = item
+ return result
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ # We do not work with OTTableWriter sub-writers because
+ # the offsets in our AATLookup are relative to our data
+ # table, for which we need to provide an offset value itself.
+ # It might have been possible to somehow make a kludge for
+ # performing this indirect offset computation directly inside
+ # OTTableWriter. But this would have made the internal logic
+ # of OTTableWriter even more complex than it already is,
+ # so we decided to roll our own offset computation for the
+ # contents of the AATLookup and associated data table.
+ offsetByGlyph, offsetByData, dataLen = {}, {}, 0
+ compiledData = []
+ for glyph in sorted(value, key=font.getGlyphID):
+ subWriter = OTTableWriter()
+ value[glyph].compile(subWriter, font)
+ data = subWriter.getAllData()
+ offset = offsetByData.get(data, None)
+ if offset == None:
+ offset = dataLen
+ dataLen = dataLen + len(data)
+ offsetByData[data] = offset
+ compiledData.append(data)
+ offsetByGlyph[glyph] = offset
+ # For calculating the offsets to our AATLookup and data table,
+ # we can use the regular OTTableWriter infrastructure.
+ lookupWriter = writer.getSubWriter()
+ lookup = AATLookup("DataOffsets", None, None, UShort)
+ lookup.write(lookupWriter, font, tableDict, offsetByGlyph, None)
+
+ dataWriter = writer.getSubWriter()
+ writer.writeSubTable(lookupWriter, offsetSize=4)
+ writer.writeSubTable(dataWriter, offsetSize=4)
+ for d in compiledData:
+ dataWriter.writeData(d)
+
+ def xmlRead(self, attrs, content, font):
+ lookup = AATLookup("DataOffsets", None, None, self.tableClass)
+ return lookup.xmlRead(attrs, content, font)
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ lookup = AATLookup("DataOffsets", None, None, self.tableClass)
+ lookup.xmlWrite(xmlWriter, font, value, name, attrs)
class MorxSubtableConverter(BaseConverter):
- _PROCESSING_ORDERS = {
- # bits 30 and 28 of morx.CoverageFlags; see morx spec
- (False, False): "LayoutOrder",
- (True, False): "ReversedLayoutOrder",
- (False, True): "LogicalOrder",
- (True, True): "ReversedLogicalOrder",
- }
-
- _PROCESSING_ORDERS_REVERSED = {
- val: key for key, val in _PROCESSING_ORDERS.items()
- }
-
- def __init__(self, name, repeat, aux, tableClass=None, *, description=""):
- BaseConverter.__init__(
- self, name, repeat, aux, tableClass, description=description
- )
-
- def _setTextDirectionFromCoverageFlags(self, flags, subtable):
- if (flags & 0x20) != 0:
- subtable.TextDirection = "Any"
- elif (flags & 0x80) != 0:
- subtable.TextDirection = "Vertical"
- else:
- subtable.TextDirection = "Horizontal"
-
- def read(self, reader, font, tableDict):
- pos = reader.pos
- m = MorxSubtable()
- m.StructLength = reader.readULong()
- flags = reader.readUInt8()
- orderKey = ((flags & 0x40) != 0, (flags & 0x10) != 0)
- m.ProcessingOrder = self._PROCESSING_ORDERS[orderKey]
- self._setTextDirectionFromCoverageFlags(flags, m)
- m.Reserved = reader.readUShort()
- m.Reserved |= (flags & 0xF) << 16
- m.MorphType = reader.readUInt8()
- m.SubFeatureFlags = reader.readULong()
- tableClass = lookupTypes["morx"].get(m.MorphType)
- if tableClass is None:
- assert False, ("unsupported 'morx' lookup type %s" %
- m.MorphType)
- # To decode AAT ligatures, we need to know the subtable size.
- # The easiest way to pass this along is to create a new reader
- # that works on just the subtable as its data.
- headerLength = reader.pos - pos
- data = reader.data[
- reader.pos
- : reader.pos + m.StructLength - headerLength]
- assert len(data) == m.StructLength - headerLength
- subReader = OTTableReader(data=data, tableTag=reader.tableTag)
- m.SubStruct = tableClass()
- m.SubStruct.decompile(subReader, font)
- reader.seek(pos + m.StructLength)
- return m
-
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.begintag(name, attrs)
- xmlWriter.newline()
- xmlWriter.comment("StructLength=%d" % value.StructLength)
- xmlWriter.newline()
- xmlWriter.simpletag("TextDirection", value=value.TextDirection)
- xmlWriter.newline()
- xmlWriter.simpletag("ProcessingOrder",
- value=value.ProcessingOrder)
- xmlWriter.newline()
- if value.Reserved != 0:
- xmlWriter.simpletag("Reserved",
- value="0x%04x" % value.Reserved)
- xmlWriter.newline()
- xmlWriter.comment("MorphType=%d" % value.MorphType)
- xmlWriter.newline()
- xmlWriter.simpletag("SubFeatureFlags",
- value="0x%08x" % value.SubFeatureFlags)
- xmlWriter.newline()
- value.SubStruct.toXML(xmlWriter, font)
- xmlWriter.endtag(name)
- xmlWriter.newline()
-
- def xmlRead(self, attrs, content, font):
- m = MorxSubtable()
- covFlags = 0
- m.Reserved = 0
- for eltName, eltAttrs, eltContent in filter(istuple, content):
- if eltName == "CoverageFlags":
- # Only in XML from old versions of fonttools.
- covFlags = safeEval(eltAttrs["value"])
- orderKey = ((covFlags & 0x40) != 0,
- (covFlags & 0x10) != 0)
- m.ProcessingOrder = self._PROCESSING_ORDERS[
- orderKey]
- self._setTextDirectionFromCoverageFlags(
- covFlags, m)
- elif eltName == "ProcessingOrder":
- m.ProcessingOrder = eltAttrs["value"]
- assert m.ProcessingOrder in self._PROCESSING_ORDERS_REVERSED, "unknown ProcessingOrder: %s" % m.ProcessingOrder
- elif eltName == "TextDirection":
- m.TextDirection = eltAttrs["value"]
- assert m.TextDirection in {"Horizontal", "Vertical", "Any"}, "unknown TextDirection %s" % m.TextDirection
- elif eltName == "Reserved":
- m.Reserved = safeEval(eltAttrs["value"])
- elif eltName == "SubFeatureFlags":
- m.SubFeatureFlags = safeEval(eltAttrs["value"])
- elif eltName.endswith("Morph"):
- m.fromXML(eltName, eltAttrs, eltContent, font)
- else:
- assert False, eltName
- m.Reserved = (covFlags & 0xF) << 16 | m.Reserved
- return m
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- covFlags = (value.Reserved & 0x000F0000) >> 16
- reverseOrder, logicalOrder = self._PROCESSING_ORDERS_REVERSED[
- value.ProcessingOrder]
- covFlags |= 0x80 if value.TextDirection == "Vertical" else 0
- covFlags |= 0x40 if reverseOrder else 0
- covFlags |= 0x20 if value.TextDirection == "Any" else 0
- covFlags |= 0x10 if logicalOrder else 0
- value.CoverageFlags = covFlags
- lengthIndex = len(writer.items)
- before = writer.getDataLength()
- value.StructLength = 0xdeadbeef
- # The high nibble of value.Reserved is actuallly encoded
- # into coverageFlags, so we need to clear it here.
- origReserved = value.Reserved # including high nibble
- value.Reserved = value.Reserved & 0xFFFF # without high nibble
- value.compile(writer, font)
- value.Reserved = origReserved # restore original value
- assert writer.items[lengthIndex] == b"\xde\xad\xbe\xef"
- length = writer.getDataLength() - before
- writer.items[lengthIndex] = struct.pack(">L", length)
+ _PROCESSING_ORDERS = {
+ # bits 30 and 28 of morx.CoverageFlags; see morx spec
+ (False, False): "LayoutOrder",
+ (True, False): "ReversedLayoutOrder",
+ (False, True): "LogicalOrder",
+ (True, True): "ReversedLogicalOrder",
+ }
+
+ _PROCESSING_ORDERS_REVERSED = {val: key for key, val in _PROCESSING_ORDERS.items()}
+
+ def __init__(self, name, repeat, aux, tableClass=None, *, description=""):
+ BaseConverter.__init__(
+ self, name, repeat, aux, tableClass, description=description
+ )
+
+ def _setTextDirectionFromCoverageFlags(self, flags, subtable):
+ if (flags & 0x20) != 0:
+ subtable.TextDirection = "Any"
+ elif (flags & 0x80) != 0:
+ subtable.TextDirection = "Vertical"
+ else:
+ subtable.TextDirection = "Horizontal"
+
+ def read(self, reader, font, tableDict):
+ pos = reader.pos
+ m = MorxSubtable()
+ m.StructLength = reader.readULong()
+ flags = reader.readUInt8()
+ orderKey = ((flags & 0x40) != 0, (flags & 0x10) != 0)
+ m.ProcessingOrder = self._PROCESSING_ORDERS[orderKey]
+ self._setTextDirectionFromCoverageFlags(flags, m)
+ m.Reserved = reader.readUShort()
+ m.Reserved |= (flags & 0xF) << 16
+ m.MorphType = reader.readUInt8()
+ m.SubFeatureFlags = reader.readULong()
+ tableClass = lookupTypes["morx"].get(m.MorphType)
+ if tableClass is None:
+ assert False, "unsupported 'morx' lookup type %s" % m.MorphType
+ # To decode AAT ligatures, we need to know the subtable size.
+ # The easiest way to pass this along is to create a new reader
+ # that works on just the subtable as its data.
+ headerLength = reader.pos - pos
+ data = reader.data[reader.pos : reader.pos + m.StructLength - headerLength]
+ assert len(data) == m.StructLength - headerLength
+ subReader = OTTableReader(data=data, tableTag=reader.tableTag)
+ m.SubStruct = tableClass()
+ m.SubStruct.decompile(subReader, font)
+ reader.seek(pos + m.StructLength)
+ return m
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.begintag(name, attrs)
+ xmlWriter.newline()
+ xmlWriter.comment("StructLength=%d" % value.StructLength)
+ xmlWriter.newline()
+ xmlWriter.simpletag("TextDirection", value=value.TextDirection)
+ xmlWriter.newline()
+ xmlWriter.simpletag("ProcessingOrder", value=value.ProcessingOrder)
+ xmlWriter.newline()
+ if value.Reserved != 0:
+ xmlWriter.simpletag("Reserved", value="0x%04x" % value.Reserved)
+ xmlWriter.newline()
+ xmlWriter.comment("MorphType=%d" % value.MorphType)
+ xmlWriter.newline()
+ xmlWriter.simpletag("SubFeatureFlags", value="0x%08x" % value.SubFeatureFlags)
+ xmlWriter.newline()
+ value.SubStruct.toXML(xmlWriter, font)
+ xmlWriter.endtag(name)
+ xmlWriter.newline()
+
+ def xmlRead(self, attrs, content, font):
+ m = MorxSubtable()
+ covFlags = 0
+ m.Reserved = 0
+ for eltName, eltAttrs, eltContent in filter(istuple, content):
+ if eltName == "CoverageFlags":
+ # Only in XML from old versions of fonttools.
+ covFlags = safeEval(eltAttrs["value"])
+ orderKey = ((covFlags & 0x40) != 0, (covFlags & 0x10) != 0)
+ m.ProcessingOrder = self._PROCESSING_ORDERS[orderKey]
+ self._setTextDirectionFromCoverageFlags(covFlags, m)
+ elif eltName == "ProcessingOrder":
+ m.ProcessingOrder = eltAttrs["value"]
+ assert m.ProcessingOrder in self._PROCESSING_ORDERS_REVERSED, (
+ "unknown ProcessingOrder: %s" % m.ProcessingOrder
+ )
+ elif eltName == "TextDirection":
+ m.TextDirection = eltAttrs["value"]
+ assert m.TextDirection in {"Horizontal", "Vertical", "Any"}, (
+ "unknown TextDirection %s" % m.TextDirection
+ )
+ elif eltName == "Reserved":
+ m.Reserved = safeEval(eltAttrs["value"])
+ elif eltName == "SubFeatureFlags":
+ m.SubFeatureFlags = safeEval(eltAttrs["value"])
+ elif eltName.endswith("Morph"):
+ m.fromXML(eltName, eltAttrs, eltContent, font)
+ else:
+ assert False, eltName
+ m.Reserved = (covFlags & 0xF) << 16 | m.Reserved
+ return m
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ covFlags = (value.Reserved & 0x000F0000) >> 16
+ reverseOrder, logicalOrder = self._PROCESSING_ORDERS_REVERSED[
+ value.ProcessingOrder
+ ]
+ covFlags |= 0x80 if value.TextDirection == "Vertical" else 0
+ covFlags |= 0x40 if reverseOrder else 0
+ covFlags |= 0x20 if value.TextDirection == "Any" else 0
+ covFlags |= 0x10 if logicalOrder else 0
+ value.CoverageFlags = covFlags
+ lengthIndex = len(writer.items)
+ before = writer.getDataLength()
+ value.StructLength = 0xDEADBEEF
+ # The high nibble of value.Reserved is actuallly encoded
+ # into coverageFlags, so we need to clear it here.
+ origReserved = value.Reserved # including high nibble
+ value.Reserved = value.Reserved & 0xFFFF # without high nibble
+ value.compile(writer, font)
+ value.Reserved = origReserved # restore original value
+ assert writer.items[lengthIndex] == b"\xde\xad\xbe\xef"
+ length = writer.getDataLength() - before
+ writer.items[lengthIndex] = struct.pack(">L", length)
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#ExtendedStateHeader
# TODO: Untangle the implementation of the various lookup-specific formats.
class STXHeader(BaseConverter):
- def __init__(self, name, repeat, aux, tableClass, *, description=""):
- BaseConverter.__init__(
- self, name, repeat, aux, tableClass, description=description
- )
- assert issubclass(self.tableClass, AATAction)
- self.classLookup = AATLookup("GlyphClasses", None, None, UShort)
- if issubclass(self.tableClass, ContextualMorphAction):
- self.perGlyphLookup = AATLookup("PerGlyphLookup",
- None, None, GlyphID)
- else:
- self.perGlyphLookup = None
-
- def read(self, reader, font, tableDict):
- table = AATStateTable()
- pos = reader.pos
- classTableReader = reader.getSubReader(0)
- stateArrayReader = reader.getSubReader(0)
- entryTableReader = reader.getSubReader(0)
- actionReader = None
- ligaturesReader = None
- table.GlyphClassCount = reader.readULong()
- classTableReader.seek(pos + reader.readULong())
- stateArrayReader.seek(pos + reader.readULong())
- entryTableReader.seek(pos + reader.readULong())
- if self.perGlyphLookup is not None:
- perGlyphTableReader = reader.getSubReader(0)
- perGlyphTableReader.seek(pos + reader.readULong())
- if issubclass(self.tableClass, LigatureMorphAction):
- actionReader = reader.getSubReader(0)
- actionReader.seek(pos + reader.readULong())
- ligComponentReader = reader.getSubReader(0)
- ligComponentReader.seek(pos + reader.readULong())
- ligaturesReader = reader.getSubReader(0)
- ligaturesReader.seek(pos + reader.readULong())
- numLigComponents = (ligaturesReader.pos
- - ligComponentReader.pos) // 2
- assert numLigComponents >= 0
- table.LigComponents = \
- ligComponentReader.readUShortArray(numLigComponents)
- table.Ligatures = self._readLigatures(ligaturesReader, font)
- elif issubclass(self.tableClass, InsertionMorphAction):
- actionReader = reader.getSubReader(0)
- actionReader.seek(pos + reader.readULong())
- table.GlyphClasses = self.classLookup.read(classTableReader,
- font, tableDict)
- numStates = int((entryTableReader.pos - stateArrayReader.pos)
- / (table.GlyphClassCount * 2))
- for stateIndex in range(numStates):
- state = AATState()
- table.States.append(state)
- for glyphClass in range(table.GlyphClassCount):
- entryIndex = stateArrayReader.readUShort()
- state.Transitions[glyphClass] = \
- self._readTransition(entryTableReader,
- entryIndex, font,
- actionReader)
- if self.perGlyphLookup is not None:
- table.PerGlyphLookups = self._readPerGlyphLookups(
- table, perGlyphTableReader, font)
- return table
-
- def _readTransition(self, reader, entryIndex, font, actionReader):
- transition = self.tableClass()
- entryReader = reader.getSubReader(
- reader.pos + entryIndex * transition.staticSize)
- transition.decompile(entryReader, font, actionReader)
- return transition
-
- def _readLigatures(self, reader, font):
- limit = len(reader.data)
- numLigatureGlyphs = (limit - reader.pos) // 2
- return font.getGlyphNameMany(reader.readUShortArray(numLigatureGlyphs))
-
- def _countPerGlyphLookups(self, table):
- # Somewhat annoyingly, the morx table does not encode
- # the size of the per-glyph table. So we need to find
- # the maximum value that MorphActions use as index
- # into this table.
- numLookups = 0
- for state in table.States:
- for t in state.Transitions.values():
- if isinstance(t, ContextualMorphAction):
- if t.MarkIndex != 0xFFFF:
- numLookups = max(
- numLookups,
- t.MarkIndex + 1)
- if t.CurrentIndex != 0xFFFF:
- numLookups = max(
- numLookups,
- t.CurrentIndex + 1)
- return numLookups
-
- def _readPerGlyphLookups(self, table, reader, font):
- pos = reader.pos
- lookups = []
- for _ in range(self._countPerGlyphLookups(table)):
- lookupReader = reader.getSubReader(0)
- lookupReader.seek(pos + reader.readULong())
- lookups.append(
- self.perGlyphLookup.read(lookupReader, font, {}))
- return lookups
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- glyphClassWriter = OTTableWriter()
- self.classLookup.write(glyphClassWriter, font, tableDict,
- value.GlyphClasses, repeatIndex=None)
- glyphClassData = pad(glyphClassWriter.getAllData(), 2)
- glyphClassCount = max(value.GlyphClasses.values()) + 1
- glyphClassTableOffset = 16 # size of STXHeader
- if self.perGlyphLookup is not None:
- glyphClassTableOffset += 4
-
- glyphClassTableOffset += self.tableClass.actionHeaderSize
- actionData, actionIndex = \
- self.tableClass.compileActions(font, value.States)
- stateArrayData, entryTableData = self._compileStates(
- font, value.States, glyphClassCount, actionIndex)
- stateArrayOffset = glyphClassTableOffset + len(glyphClassData)
- entryTableOffset = stateArrayOffset + len(stateArrayData)
- perGlyphOffset = entryTableOffset + len(entryTableData)
- perGlyphData = \
- pad(self._compilePerGlyphLookups(value, font), 4)
- if actionData is not None:
- actionOffset = entryTableOffset + len(entryTableData)
- else:
- actionOffset = None
-
- ligaturesOffset, ligComponentsOffset = None, None
- ligComponentsData = self._compileLigComponents(value, font)
- ligaturesData = self._compileLigatures(value, font)
- if ligComponentsData is not None:
- assert len(perGlyphData) == 0
- ligComponentsOffset = actionOffset + len(actionData)
- ligaturesOffset = ligComponentsOffset + len(ligComponentsData)
-
- writer.writeULong(glyphClassCount)
- writer.writeULong(glyphClassTableOffset)
- writer.writeULong(stateArrayOffset)
- writer.writeULong(entryTableOffset)
- if self.perGlyphLookup is not None:
- writer.writeULong(perGlyphOffset)
- if actionOffset is not None:
- writer.writeULong(actionOffset)
- if ligComponentsOffset is not None:
- writer.writeULong(ligComponentsOffset)
- writer.writeULong(ligaturesOffset)
- writer.writeData(glyphClassData)
- writer.writeData(stateArrayData)
- writer.writeData(entryTableData)
- writer.writeData(perGlyphData)
- if actionData is not None:
- writer.writeData(actionData)
- if ligComponentsData is not None:
- writer.writeData(ligComponentsData)
- if ligaturesData is not None:
- writer.writeData(ligaturesData)
-
- def _compileStates(self, font, states, glyphClassCount, actionIndex):
- stateArrayWriter = OTTableWriter()
- entries, entryIDs = [], {}
- for state in states:
- for glyphClass in range(glyphClassCount):
- transition = state.Transitions[glyphClass]
- entryWriter = OTTableWriter()
- transition.compile(entryWriter, font,
- actionIndex)
- entryData = entryWriter.getAllData()
- assert len(entryData) == transition.staticSize, ( \
- "%s has staticSize %d, "
- "but actually wrote %d bytes" % (
- repr(transition),
- transition.staticSize,
- len(entryData)))
- entryIndex = entryIDs.get(entryData)
- if entryIndex is None:
- entryIndex = len(entries)
- entryIDs[entryData] = entryIndex
- entries.append(entryData)
- stateArrayWriter.writeUShort(entryIndex)
- stateArrayData = pad(stateArrayWriter.getAllData(), 4)
- entryTableData = pad(bytesjoin(entries), 4)
- return stateArrayData, entryTableData
-
- def _compilePerGlyphLookups(self, table, font):
- if self.perGlyphLookup is None:
- return b""
- numLookups = self._countPerGlyphLookups(table)
- assert len(table.PerGlyphLookups) == numLookups, (
- "len(AATStateTable.PerGlyphLookups) is %d, "
- "but the actions inside the table refer to %d" %
- (len(table.PerGlyphLookups), numLookups))
- writer = OTTableWriter()
- for lookup in table.PerGlyphLookups:
- lookupWriter = writer.getSubWriter(offsetSize=4)
- self.perGlyphLookup.write(lookupWriter, font,
- {}, lookup, None)
- writer.writeSubTable(lookupWriter)
- return writer.getAllData()
-
- def _compileLigComponents(self, table, font):
- if not hasattr(table, "LigComponents"):
- return None
- writer = OTTableWriter()
- for component in table.LigComponents:
- writer.writeUShort(component)
- return writer.getAllData()
-
- def _compileLigatures(self, table, font):
- if not hasattr(table, "Ligatures"):
- return None
- writer = OTTableWriter()
- for glyphName in table.Ligatures:
- writer.writeUShort(font.getGlyphID(glyphName))
- return writer.getAllData()
-
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.begintag(name, attrs)
- xmlWriter.newline()
- xmlWriter.comment("GlyphClassCount=%s" %value.GlyphClassCount)
- xmlWriter.newline()
- for g, klass in sorted(value.GlyphClasses.items()):
- xmlWriter.simpletag("GlyphClass", glyph=g, value=klass)
- xmlWriter.newline()
- for stateIndex, state in enumerate(value.States):
- xmlWriter.begintag("State", index=stateIndex)
- xmlWriter.newline()
- for glyphClass, trans in sorted(state.Transitions.items()):
- trans.toXML(xmlWriter, font=font,
- attrs={"onGlyphClass": glyphClass},
- name="Transition")
- xmlWriter.endtag("State")
- xmlWriter.newline()
- for i, lookup in enumerate(value.PerGlyphLookups):
- xmlWriter.begintag("PerGlyphLookup", index=i)
- xmlWriter.newline()
- for glyph, val in sorted(lookup.items()):
- xmlWriter.simpletag("Lookup", glyph=glyph,
- value=val)
- xmlWriter.newline()
- xmlWriter.endtag("PerGlyphLookup")
- xmlWriter.newline()
- if hasattr(value, "LigComponents"):
- xmlWriter.begintag("LigComponents")
- xmlWriter.newline()
- for i, val in enumerate(getattr(value, "LigComponents")):
- xmlWriter.simpletag("LigComponent", index=i,
- value=val)
- xmlWriter.newline()
- xmlWriter.endtag("LigComponents")
- xmlWriter.newline()
- self._xmlWriteLigatures(xmlWriter, font, value, name, attrs)
- xmlWriter.endtag(name)
- xmlWriter.newline()
-
- def _xmlWriteLigatures(self, xmlWriter, font, value, name, attrs):
- if not hasattr(value, "Ligatures"):
- return
- xmlWriter.begintag("Ligatures")
- xmlWriter.newline()
- for i, g in enumerate(getattr(value, "Ligatures")):
- xmlWriter.simpletag("Ligature", index=i, glyph=g)
- xmlWriter.newline()
- xmlWriter.endtag("Ligatures")
- xmlWriter.newline()
-
- def xmlRead(self, attrs, content, font):
- table = AATStateTable()
- for eltName, eltAttrs, eltContent in filter(istuple, content):
- if eltName == "GlyphClass":
- glyph = eltAttrs["glyph"]
- value = eltAttrs["value"]
- table.GlyphClasses[glyph] = safeEval(value)
- elif eltName == "State":
- state = self._xmlReadState(eltAttrs, eltContent, font)
- table.States.append(state)
- elif eltName == "PerGlyphLookup":
- lookup = self.perGlyphLookup.xmlRead(
- eltAttrs, eltContent, font)
- table.PerGlyphLookups.append(lookup)
- elif eltName == "LigComponents":
- table.LigComponents = \
- self._xmlReadLigComponents(
- eltAttrs, eltContent, font)
- elif eltName == "Ligatures":
- table.Ligatures = \
- self._xmlReadLigatures(
- eltAttrs, eltContent, font)
- table.GlyphClassCount = max(table.GlyphClasses.values()) + 1
- return table
-
- def _xmlReadState(self, attrs, content, font):
- state = AATState()
- for eltName, eltAttrs, eltContent in filter(istuple, content):
- if eltName == "Transition":
- glyphClass = safeEval(eltAttrs["onGlyphClass"])
- transition = self.tableClass()
- transition.fromXML(eltName, eltAttrs,
- eltContent, font)
- state.Transitions[glyphClass] = transition
- return state
-
- def _xmlReadLigComponents(self, attrs, content, font):
- ligComponents = []
- for eltName, eltAttrs, _eltContent in filter(istuple, content):
- if eltName == "LigComponent":
- ligComponents.append(
- safeEval(eltAttrs["value"]))
- return ligComponents
-
- def _xmlReadLigatures(self, attrs, content, font):
- ligs = []
- for eltName, eltAttrs, _eltContent in filter(istuple, content):
- if eltName == "Ligature":
- ligs.append(eltAttrs["glyph"])
- return ligs
+ def __init__(self, name, repeat, aux, tableClass, *, description=""):
+ BaseConverter.__init__(
+ self, name, repeat, aux, tableClass, description=description
+ )
+ assert issubclass(self.tableClass, AATAction)
+ self.classLookup = AATLookup("GlyphClasses", None, None, UShort)
+ if issubclass(self.tableClass, ContextualMorphAction):
+ self.perGlyphLookup = AATLookup("PerGlyphLookup", None, None, GlyphID)
+ else:
+ self.perGlyphLookup = None
+
+ def read(self, reader, font, tableDict):
+ table = AATStateTable()
+ pos = reader.pos
+ classTableReader = reader.getSubReader(0)
+ stateArrayReader = reader.getSubReader(0)
+ entryTableReader = reader.getSubReader(0)
+ actionReader = None
+ ligaturesReader = None
+ table.GlyphClassCount = reader.readULong()
+ classTableReader.seek(pos + reader.readULong())
+ stateArrayReader.seek(pos + reader.readULong())
+ entryTableReader.seek(pos + reader.readULong())
+ if self.perGlyphLookup is not None:
+ perGlyphTableReader = reader.getSubReader(0)
+ perGlyphTableReader.seek(pos + reader.readULong())
+ if issubclass(self.tableClass, LigatureMorphAction):
+ actionReader = reader.getSubReader(0)
+ actionReader.seek(pos + reader.readULong())
+ ligComponentReader = reader.getSubReader(0)
+ ligComponentReader.seek(pos + reader.readULong())
+ ligaturesReader = reader.getSubReader(0)
+ ligaturesReader.seek(pos + reader.readULong())
+ numLigComponents = (ligaturesReader.pos - ligComponentReader.pos) // 2
+ assert numLigComponents >= 0
+ table.LigComponents = ligComponentReader.readUShortArray(numLigComponents)
+ table.Ligatures = self._readLigatures(ligaturesReader, font)
+ elif issubclass(self.tableClass, InsertionMorphAction):
+ actionReader = reader.getSubReader(0)
+ actionReader.seek(pos + reader.readULong())
+ table.GlyphClasses = self.classLookup.read(classTableReader, font, tableDict)
+ numStates = int(
+ (entryTableReader.pos - stateArrayReader.pos) / (table.GlyphClassCount * 2)
+ )
+ for stateIndex in range(numStates):
+ state = AATState()
+ table.States.append(state)
+ for glyphClass in range(table.GlyphClassCount):
+ entryIndex = stateArrayReader.readUShort()
+ state.Transitions[glyphClass] = self._readTransition(
+ entryTableReader, entryIndex, font, actionReader
+ )
+ if self.perGlyphLookup is not None:
+ table.PerGlyphLookups = self._readPerGlyphLookups(
+ table, perGlyphTableReader, font
+ )
+ return table
+
+ def _readTransition(self, reader, entryIndex, font, actionReader):
+ transition = self.tableClass()
+ entryReader = reader.getSubReader(
+ reader.pos + entryIndex * transition.staticSize
+ )
+ transition.decompile(entryReader, font, actionReader)
+ return transition
+
+ def _readLigatures(self, reader, font):
+ limit = len(reader.data)
+ numLigatureGlyphs = (limit - reader.pos) // 2
+ return font.getGlyphNameMany(reader.readUShortArray(numLigatureGlyphs))
+
+ def _countPerGlyphLookups(self, table):
+ # Somewhat annoyingly, the morx table does not encode
+ # the size of the per-glyph table. So we need to find
+ # the maximum value that MorphActions use as index
+ # into this table.
+ numLookups = 0
+ for state in table.States:
+ for t in state.Transitions.values():
+ if isinstance(t, ContextualMorphAction):
+ if t.MarkIndex != 0xFFFF:
+ numLookups = max(numLookups, t.MarkIndex + 1)
+ if t.CurrentIndex != 0xFFFF:
+ numLookups = max(numLookups, t.CurrentIndex + 1)
+ return numLookups
+
+ def _readPerGlyphLookups(self, table, reader, font):
+ pos = reader.pos
+ lookups = []
+ for _ in range(self._countPerGlyphLookups(table)):
+ lookupReader = reader.getSubReader(0)
+ lookupReader.seek(pos + reader.readULong())
+ lookups.append(self.perGlyphLookup.read(lookupReader, font, {}))
+ return lookups
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ glyphClassWriter = OTTableWriter()
+ self.classLookup.write(
+ glyphClassWriter, font, tableDict, value.GlyphClasses, repeatIndex=None
+ )
+ glyphClassData = pad(glyphClassWriter.getAllData(), 2)
+ glyphClassCount = max(value.GlyphClasses.values()) + 1
+ glyphClassTableOffset = 16 # size of STXHeader
+ if self.perGlyphLookup is not None:
+ glyphClassTableOffset += 4
+
+ glyphClassTableOffset += self.tableClass.actionHeaderSize
+ actionData, actionIndex = self.tableClass.compileActions(font, value.States)
+ stateArrayData, entryTableData = self._compileStates(
+ font, value.States, glyphClassCount, actionIndex
+ )
+ stateArrayOffset = glyphClassTableOffset + len(glyphClassData)
+ entryTableOffset = stateArrayOffset + len(stateArrayData)
+ perGlyphOffset = entryTableOffset + len(entryTableData)
+ perGlyphData = pad(self._compilePerGlyphLookups(value, font), 4)
+ if actionData is not None:
+ actionOffset = entryTableOffset + len(entryTableData)
+ else:
+ actionOffset = None
+
+ ligaturesOffset, ligComponentsOffset = None, None
+ ligComponentsData = self._compileLigComponents(value, font)
+ ligaturesData = self._compileLigatures(value, font)
+ if ligComponentsData is not None:
+ assert len(perGlyphData) == 0
+ ligComponentsOffset = actionOffset + len(actionData)
+ ligaturesOffset = ligComponentsOffset + len(ligComponentsData)
+
+ writer.writeULong(glyphClassCount)
+ writer.writeULong(glyphClassTableOffset)
+ writer.writeULong(stateArrayOffset)
+ writer.writeULong(entryTableOffset)
+ if self.perGlyphLookup is not None:
+ writer.writeULong(perGlyphOffset)
+ if actionOffset is not None:
+ writer.writeULong(actionOffset)
+ if ligComponentsOffset is not None:
+ writer.writeULong(ligComponentsOffset)
+ writer.writeULong(ligaturesOffset)
+ writer.writeData(glyphClassData)
+ writer.writeData(stateArrayData)
+ writer.writeData(entryTableData)
+ writer.writeData(perGlyphData)
+ if actionData is not None:
+ writer.writeData(actionData)
+ if ligComponentsData is not None:
+ writer.writeData(ligComponentsData)
+ if ligaturesData is not None:
+ writer.writeData(ligaturesData)
+
+ def _compileStates(self, font, states, glyphClassCount, actionIndex):
+ stateArrayWriter = OTTableWriter()
+ entries, entryIDs = [], {}
+ for state in states:
+ for glyphClass in range(glyphClassCount):
+ transition = state.Transitions[glyphClass]
+ entryWriter = OTTableWriter()
+ transition.compile(entryWriter, font, actionIndex)
+ entryData = entryWriter.getAllData()
+ assert (
+ len(entryData) == transition.staticSize
+ ), "%s has staticSize %d, " "but actually wrote %d bytes" % (
+ repr(transition),
+ transition.staticSize,
+ len(entryData),
+ )
+ entryIndex = entryIDs.get(entryData)
+ if entryIndex is None:
+ entryIndex = len(entries)
+ entryIDs[entryData] = entryIndex
+ entries.append(entryData)
+ stateArrayWriter.writeUShort(entryIndex)
+ stateArrayData = pad(stateArrayWriter.getAllData(), 4)
+ entryTableData = pad(bytesjoin(entries), 4)
+ return stateArrayData, entryTableData
+
+ def _compilePerGlyphLookups(self, table, font):
+ if self.perGlyphLookup is None:
+ return b""
+ numLookups = self._countPerGlyphLookups(table)
+ assert len(table.PerGlyphLookups) == numLookups, (
+ "len(AATStateTable.PerGlyphLookups) is %d, "
+ "but the actions inside the table refer to %d"
+ % (len(table.PerGlyphLookups), numLookups)
+ )
+ writer = OTTableWriter()
+ for lookup in table.PerGlyphLookups:
+ lookupWriter = writer.getSubWriter()
+ self.perGlyphLookup.write(lookupWriter, font, {}, lookup, None)
+ writer.writeSubTable(lookupWriter, offsetSize=4)
+ return writer.getAllData()
+
+ def _compileLigComponents(self, table, font):
+ if not hasattr(table, "LigComponents"):
+ return None
+ writer = OTTableWriter()
+ for component in table.LigComponents:
+ writer.writeUShort(component)
+ return writer.getAllData()
+
+ def _compileLigatures(self, table, font):
+ if not hasattr(table, "Ligatures"):
+ return None
+ writer = OTTableWriter()
+ for glyphName in table.Ligatures:
+ writer.writeUShort(font.getGlyphID(glyphName))
+ return writer.getAllData()
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.begintag(name, attrs)
+ xmlWriter.newline()
+ xmlWriter.comment("GlyphClassCount=%s" % value.GlyphClassCount)
+ xmlWriter.newline()
+ for g, klass in sorted(value.GlyphClasses.items()):
+ xmlWriter.simpletag("GlyphClass", glyph=g, value=klass)
+ xmlWriter.newline()
+ for stateIndex, state in enumerate(value.States):
+ xmlWriter.begintag("State", index=stateIndex)
+ xmlWriter.newline()
+ for glyphClass, trans in sorted(state.Transitions.items()):
+ trans.toXML(
+ xmlWriter,
+ font=font,
+ attrs={"onGlyphClass": glyphClass},
+ name="Transition",
+ )
+ xmlWriter.endtag("State")
+ xmlWriter.newline()
+ for i, lookup in enumerate(value.PerGlyphLookups):
+ xmlWriter.begintag("PerGlyphLookup", index=i)
+ xmlWriter.newline()
+ for glyph, val in sorted(lookup.items()):
+ xmlWriter.simpletag("Lookup", glyph=glyph, value=val)
+ xmlWriter.newline()
+ xmlWriter.endtag("PerGlyphLookup")
+ xmlWriter.newline()
+ if hasattr(value, "LigComponents"):
+ xmlWriter.begintag("LigComponents")
+ xmlWriter.newline()
+ for i, val in enumerate(getattr(value, "LigComponents")):
+ xmlWriter.simpletag("LigComponent", index=i, value=val)
+ xmlWriter.newline()
+ xmlWriter.endtag("LigComponents")
+ xmlWriter.newline()
+ self._xmlWriteLigatures(xmlWriter, font, value, name, attrs)
+ xmlWriter.endtag(name)
+ xmlWriter.newline()
+
+ def _xmlWriteLigatures(self, xmlWriter, font, value, name, attrs):
+ if not hasattr(value, "Ligatures"):
+ return
+ xmlWriter.begintag("Ligatures")
+ xmlWriter.newline()
+ for i, g in enumerate(getattr(value, "Ligatures")):
+ xmlWriter.simpletag("Ligature", index=i, glyph=g)
+ xmlWriter.newline()
+ xmlWriter.endtag("Ligatures")
+ xmlWriter.newline()
+
+ def xmlRead(self, attrs, content, font):
+ table = AATStateTable()
+ for eltName, eltAttrs, eltContent in filter(istuple, content):
+ if eltName == "GlyphClass":
+ glyph = eltAttrs["glyph"]
+ value = eltAttrs["value"]
+ table.GlyphClasses[glyph] = safeEval(value)
+ elif eltName == "State":
+ state = self._xmlReadState(eltAttrs, eltContent, font)
+ table.States.append(state)
+ elif eltName == "PerGlyphLookup":
+ lookup = self.perGlyphLookup.xmlRead(eltAttrs, eltContent, font)
+ table.PerGlyphLookups.append(lookup)
+ elif eltName == "LigComponents":
+ table.LigComponents = self._xmlReadLigComponents(
+ eltAttrs, eltContent, font
+ )
+ elif eltName == "Ligatures":
+ table.Ligatures = self._xmlReadLigatures(eltAttrs, eltContent, font)
+ table.GlyphClassCount = max(table.GlyphClasses.values()) + 1
+ return table
+
+ def _xmlReadState(self, attrs, content, font):
+ state = AATState()
+ for eltName, eltAttrs, eltContent in filter(istuple, content):
+ if eltName == "Transition":
+ glyphClass = safeEval(eltAttrs["onGlyphClass"])
+ transition = self.tableClass()
+ transition.fromXML(eltName, eltAttrs, eltContent, font)
+ state.Transitions[glyphClass] = transition
+ return state
+
+ def _xmlReadLigComponents(self, attrs, content, font):
+ ligComponents = []
+ for eltName, eltAttrs, _eltContent in filter(istuple, content):
+ if eltName == "LigComponent":
+ ligComponents.append(safeEval(eltAttrs["value"]))
+ return ligComponents
+
+ def _xmlReadLigatures(self, attrs, content, font):
+ ligs = []
+ for eltName, eltAttrs, _eltContent in filter(istuple, content):
+ if eltName == "Ligature":
+ ligs.append(eltAttrs["glyph"])
+ return ligs
class CIDGlyphMap(BaseConverter):
- def read(self, reader, font, tableDict):
- numCIDs = reader.readUShort()
- result = {}
- for cid, glyphID in enumerate(reader.readUShortArray(numCIDs)):
- if glyphID != 0xFFFF:
- result[cid] = font.getGlyphName(glyphID)
- return result
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- items = {cid: font.getGlyphID(glyph)
- for cid, glyph in value.items()}
- count = max(items) + 1 if items else 0
- writer.writeUShort(count)
- for cid in range(count):
- writer.writeUShort(items.get(cid, 0xFFFF))
-
- def xmlRead(self, attrs, content, font):
- result = {}
- for eName, eAttrs, _eContent in filter(istuple, content):
- if eName == "CID":
- result[safeEval(eAttrs["cid"])] = \
- eAttrs["glyph"].strip()
- return result
-
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.begintag(name, attrs)
- xmlWriter.newline()
- for cid, glyph in sorted(value.items()):
- if glyph is not None and glyph != 0xFFFF:
- xmlWriter.simpletag(
- "CID", cid=cid, glyph=glyph)
- xmlWriter.newline()
- xmlWriter.endtag(name)
- xmlWriter.newline()
+ def read(self, reader, font, tableDict):
+ numCIDs = reader.readUShort()
+ result = {}
+ for cid, glyphID in enumerate(reader.readUShortArray(numCIDs)):
+ if glyphID != 0xFFFF:
+ result[cid] = font.getGlyphName(glyphID)
+ return result
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ items = {cid: font.getGlyphID(glyph) for cid, glyph in value.items()}
+ count = max(items) + 1 if items else 0
+ writer.writeUShort(count)
+ for cid in range(count):
+ writer.writeUShort(items.get(cid, 0xFFFF))
+
+ def xmlRead(self, attrs, content, font):
+ result = {}
+ for eName, eAttrs, _eContent in filter(istuple, content):
+ if eName == "CID":
+ result[safeEval(eAttrs["cid"])] = eAttrs["glyph"].strip()
+ return result
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.begintag(name, attrs)
+ xmlWriter.newline()
+ for cid, glyph in sorted(value.items()):
+ if glyph is not None and glyph != 0xFFFF:
+ xmlWriter.simpletag("CID", cid=cid, glyph=glyph)
+ xmlWriter.newline()
+ xmlWriter.endtag(name)
+ xmlWriter.newline()
class GlyphCIDMap(BaseConverter):
- def read(self, reader, font, tableDict):
- glyphOrder = font.getGlyphOrder()
- count = reader.readUShort()
- cids = reader.readUShortArray(count)
- if count > len(glyphOrder):
- log.warning("GlyphCIDMap has %d elements, "
- "but the font has only %d glyphs; "
- "ignoring the rest" %
- (count, len(glyphOrder)))
- result = {}
- for glyphID in range(min(len(cids), len(glyphOrder))):
- cid = cids[glyphID]
- if cid != 0xFFFF:
- result[glyphOrder[glyphID]] = cid
- return result
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- items = {font.getGlyphID(g): cid
- for g, cid in value.items()
- if cid is not None and cid != 0xFFFF}
- count = max(items) + 1 if items else 0
- writer.writeUShort(count)
- for glyphID in range(count):
- writer.writeUShort(items.get(glyphID, 0xFFFF))
-
- def xmlRead(self, attrs, content, font):
- result = {}
- for eName, eAttrs, _eContent in filter(istuple, content):
- if eName == "CID":
- result[eAttrs["glyph"]] = \
- safeEval(eAttrs["value"])
- return result
-
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.begintag(name, attrs)
- xmlWriter.newline()
- for glyph, cid in sorted(value.items()):
- if cid is not None and cid != 0xFFFF:
- xmlWriter.simpletag(
- "CID", glyph=glyph, value=cid)
- xmlWriter.newline()
- xmlWriter.endtag(name)
- xmlWriter.newline()
+ def read(self, reader, font, tableDict):
+ glyphOrder = font.getGlyphOrder()
+ count = reader.readUShort()
+ cids = reader.readUShortArray(count)
+ if count > len(glyphOrder):
+ log.warning(
+ "GlyphCIDMap has %d elements, "
+ "but the font has only %d glyphs; "
+ "ignoring the rest" % (count, len(glyphOrder))
+ )
+ result = {}
+ for glyphID in range(min(len(cids), len(glyphOrder))):
+ cid = cids[glyphID]
+ if cid != 0xFFFF:
+ result[glyphOrder[glyphID]] = cid
+ return result
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ items = {
+ font.getGlyphID(g): cid
+ for g, cid in value.items()
+ if cid is not None and cid != 0xFFFF
+ }
+ count = max(items) + 1 if items else 0
+ writer.writeUShort(count)
+ for glyphID in range(count):
+ writer.writeUShort(items.get(glyphID, 0xFFFF))
+
+ def xmlRead(self, attrs, content, font):
+ result = {}
+ for eName, eAttrs, _eContent in filter(istuple, content):
+ if eName == "CID":
+ result[eAttrs["glyph"]] = safeEval(eAttrs["value"])
+ return result
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.begintag(name, attrs)
+ xmlWriter.newline()
+ for glyph, cid in sorted(value.items()):
+ if cid is not None and cid != 0xFFFF:
+ xmlWriter.simpletag("CID", glyph=glyph, value=cid)
+ xmlWriter.newline()
+ xmlWriter.endtag(name)
+ xmlWriter.newline()
class DeltaValue(BaseConverter):
-
- def read(self, reader, font, tableDict):
- StartSize = tableDict["StartSize"]
- EndSize = tableDict["EndSize"]
- DeltaFormat = tableDict["DeltaFormat"]
- assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
- nItems = EndSize - StartSize + 1
- nBits = 1 << DeltaFormat
- minusOffset = 1 << nBits
- mask = (1 << nBits) - 1
- signMask = 1 << (nBits - 1)
-
- DeltaValue = []
- tmp, shift = 0, 0
- for i in range(nItems):
- if shift == 0:
- tmp, shift = reader.readUShort(), 16
- shift = shift - nBits
- value = (tmp >> shift) & mask
- if value & signMask:
- value = value - minusOffset
- DeltaValue.append(value)
- return DeltaValue
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- StartSize = tableDict["StartSize"]
- EndSize = tableDict["EndSize"]
- DeltaFormat = tableDict["DeltaFormat"]
- DeltaValue = value
- assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
- nItems = EndSize - StartSize + 1
- nBits = 1 << DeltaFormat
- assert len(DeltaValue) == nItems
- mask = (1 << nBits) - 1
-
- tmp, shift = 0, 16
- for value in DeltaValue:
- shift = shift - nBits
- tmp = tmp | ((value & mask) << shift)
- if shift == 0:
- writer.writeUShort(tmp)
- tmp, shift = 0, 16
- if shift != 16:
- writer.writeUShort(tmp)
-
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.simpletag(name, attrs + [("value", value)])
- xmlWriter.newline()
-
- def xmlRead(self, attrs, content, font):
- return safeEval(attrs["value"])
+ def read(self, reader, font, tableDict):
+ StartSize = tableDict["StartSize"]
+ EndSize = tableDict["EndSize"]
+ DeltaFormat = tableDict["DeltaFormat"]
+ assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
+ nItems = EndSize - StartSize + 1
+ nBits = 1 << DeltaFormat
+ minusOffset = 1 << nBits
+ mask = (1 << nBits) - 1
+ signMask = 1 << (nBits - 1)
+
+ DeltaValue = []
+ tmp, shift = 0, 0
+ for i in range(nItems):
+ if shift == 0:
+ tmp, shift = reader.readUShort(), 16
+ shift = shift - nBits
+ value = (tmp >> shift) & mask
+ if value & signMask:
+ value = value - minusOffset
+ DeltaValue.append(value)
+ return DeltaValue
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ StartSize = tableDict["StartSize"]
+ EndSize = tableDict["EndSize"]
+ DeltaFormat = tableDict["DeltaFormat"]
+ DeltaValue = value
+ assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
+ nItems = EndSize - StartSize + 1
+ nBits = 1 << DeltaFormat
+ assert len(DeltaValue) == nItems
+ mask = (1 << nBits) - 1
+
+ tmp, shift = 0, 16
+ for value in DeltaValue:
+ shift = shift - nBits
+ tmp = tmp | ((value & mask) << shift)
+ if shift == 0:
+ writer.writeUShort(tmp)
+ tmp, shift = 0, 16
+ if shift != 16:
+ writer.writeUShort(tmp)
+
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.simpletag(name, attrs + [("value", value)])
+ xmlWriter.newline()
+
+ def xmlRead(self, attrs, content, font):
+ return safeEval(attrs["value"])
class VarIdxMapValue(BaseConverter):
-
- def read(self, reader, font, tableDict):
- fmt = tableDict['EntryFormat']
- nItems = tableDict['MappingCount']
-
- innerBits = 1 + (fmt & 0x000F)
- innerMask = (1<<innerBits) - 1
- outerMask = 0xFFFFFFFF - innerMask
- outerShift = 16 - innerBits
-
- entrySize = 1 + ((fmt & 0x0030) >> 4)
- readArray = {
- 1: reader.readUInt8Array,
- 2: reader.readUShortArray,
- 3: reader.readUInt24Array,
- 4: reader.readULongArray,
- }[entrySize]
-
- return [(((raw & outerMask) << outerShift) | (raw & innerMask))
- for raw in readArray(nItems)]
-
- def write(self, writer, font, tableDict, value, repeatIndex=None):
- fmt = tableDict['EntryFormat']
- mapping = value
- writer['MappingCount'].setValue(len(mapping))
-
- innerBits = 1 + (fmt & 0x000F)
- innerMask = (1<<innerBits) - 1
- outerShift = 16 - innerBits
-
- entrySize = 1 + ((fmt & 0x0030) >> 4)
- writeArray = {
- 1: writer.writeUInt8Array,
- 2: writer.writeUShortArray,
- 3: writer.writeUInt24Array,
- 4: writer.writeULongArray,
- }[entrySize]
-
- writeArray([(((idx & 0xFFFF0000) >> outerShift) | (idx & innerMask))
- for idx in mapping])
+ def read(self, reader, font, tableDict):
+ fmt = tableDict["EntryFormat"]
+ nItems = tableDict["MappingCount"]
+
+ innerBits = 1 + (fmt & 0x000F)
+ innerMask = (1 << innerBits) - 1
+ outerMask = 0xFFFFFFFF - innerMask
+ outerShift = 16 - innerBits
+
+ entrySize = 1 + ((fmt & 0x0030) >> 4)
+ readArray = {
+ 1: reader.readUInt8Array,
+ 2: reader.readUShortArray,
+ 3: reader.readUInt24Array,
+ 4: reader.readULongArray,
+ }[entrySize]
+
+ return [
+ (((raw & outerMask) << outerShift) | (raw & innerMask))
+ for raw in readArray(nItems)
+ ]
+
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ fmt = tableDict["EntryFormat"]
+ mapping = value
+ writer["MappingCount"].setValue(len(mapping))
+
+ innerBits = 1 + (fmt & 0x000F)
+ innerMask = (1 << innerBits) - 1
+ outerShift = 16 - innerBits
+
+ entrySize = 1 + ((fmt & 0x0030) >> 4)
+ writeArray = {
+ 1: writer.writeUInt8Array,
+ 2: writer.writeUShortArray,
+ 3: writer.writeUInt24Array,
+ 4: writer.writeULongArray,
+ }[entrySize]
+
+ writeArray(
+ [
+ (((idx & 0xFFFF0000) >> outerShift) | (idx & innerMask))
+ for idx in mapping
+ ]
+ )
class VarDataValue(BaseConverter):
+ def read(self, reader, font, tableDict):
+ values = []
- def read(self, reader, font, tableDict):
- values = []
+ regionCount = tableDict["VarRegionCount"]
+ wordCount = tableDict["NumShorts"]
- regionCount = tableDict["VarRegionCount"]
- wordCount = tableDict["NumShorts"]
+ # https://github.com/fonttools/fonttools/issues/2279
+ longWords = bool(wordCount & 0x8000)
+ wordCount = wordCount & 0x7FFF
- # 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
- 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:]
- 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
- return values
+ def write(self, writer, font, tableDict, values, repeatIndex=None):
+ regionCount = tableDict["VarRegionCount"]
+ wordCount = tableDict["NumShorts"]
- def write(self, writer, font, tableDict, values, repeatIndex=None):
- regionCount = tableDict["VarRegionCount"]
- wordCount = tableDict["NumShorts"]
+ # https://github.com/fonttools/fonttools/issues/2279
+ longWords = bool(wordCount & 0x8000)
+ wordCount = wordCount & 0x7FFF
- # 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]
- (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))
- 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)])
+ xmlWriter.newline()
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.simpletag(name, attrs + [("value", value)])
- xmlWriter.newline()
+ def xmlRead(self, attrs, content, font):
+ return safeEval(attrs["value"])
- def xmlRead(self, attrs, content, font):
- return safeEval(attrs["value"])
class LookupFlag(UShort):
- def xmlWrite(self, xmlWriter, font, value, name, attrs):
- xmlWriter.simpletag(name, attrs + [("value", value)])
- flags = []
- if value & 0x01: flags.append("rightToLeft")
- if value & 0x02: flags.append("ignoreBaseGlyphs")
- if value & 0x04: flags.append("ignoreLigatures")
- if value & 0x08: flags.append("ignoreMarks")
- if value & 0x10: flags.append("useMarkFilteringSet")
- if value & 0xff00: flags.append("markAttachmentType[%i]" % (value >> 8))
- if flags:
- xmlWriter.comment(" ".join(flags))
- xmlWriter.newline()
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.simpletag(name, attrs + [("value", value)])
+ flags = []
+ if value & 0x01:
+ flags.append("rightToLeft")
+ if value & 0x02:
+ flags.append("ignoreBaseGlyphs")
+ if value & 0x04:
+ flags.append("ignoreLigatures")
+ if value & 0x08:
+ flags.append("ignoreMarks")
+ if value & 0x10:
+ flags.append("useMarkFilteringSet")
+ if value & 0xFF00:
+ flags.append("markAttachmentType[%i]" % (value >> 8))
+ if flags:
+ xmlWriter.comment(" ".join(flags))
+ xmlWriter.newline()
class _UInt8Enum(UInt8):
- enumClass = NotImplemented
+ enumClass = NotImplemented
+
+ def read(self, reader, font, tableDict):
+ return self.enumClass(super().read(reader, font, tableDict))
+
+ @classmethod
+ def fromString(cls, value):
+ return getattr(cls.enumClass, value.upper())
- def read(self, reader, font, tableDict):
- return self.enumClass(super().read(reader, font, tableDict))
- @classmethod
- def fromString(cls, value):
- return getattr(cls.enumClass, value.upper())
- @classmethod
- def toString(cls, value):
- return cls.enumClass(value).name.lower()
+ @classmethod
+ def toString(cls, value):
+ return cls.enumClass(value).name.lower()
class ExtendMode(_UInt8Enum):
- enumClass = _ExtendMode
+ enumClass = _ExtendMode
class CompositeMode(_UInt8Enum):
- enumClass = _CompositeMode
+ enumClass = _CompositeMode
converterMapping = {
- # type class
- "int8": Int8,
- "int16": Short,
- "uint8": UInt8,
- "uint16": UShort,
- "uint24": UInt24,
- "uint32": ULong,
- "char64": Char64,
- "Flags32": Flags32,
- "VarIndex": VarIndex,
- "Version": Version,
- "Tag": Tag,
- "GlyphID": GlyphID,
- "GlyphID32": GlyphID32,
- "NameID": NameID,
- "DeciPoints": DeciPoints,
- "Fixed": Fixed,
- "F2Dot14": F2Dot14,
- "Angle": Angle,
- "BiasedAngle": BiasedAngle,
- "struct": Struct,
- "Offset": Table,
- "LOffset": LTable,
- "Offset24": Table24,
- "ValueRecord": ValueRecord,
- "DeltaValue": DeltaValue,
- "VarIdxMapValue": VarIdxMapValue,
- "VarDataValue": VarDataValue,
- "LookupFlag": LookupFlag,
- "ExtendMode": ExtendMode,
- "CompositeMode": CompositeMode,
- "STATFlags": STATFlags,
-
- # AAT
- "CIDGlyphMap": CIDGlyphMap,
- "GlyphCIDMap": GlyphCIDMap,
- "MortChain": StructWithLength,
- "MortSubtable": StructWithLength,
- "MorxChain": StructWithLength,
- "MorxSubtable": MorxSubtableConverter,
-
- # "Template" types
- "AATLookup": lambda C: partial(AATLookup, tableClass=C),
- "AATLookupWithDataOffset": lambda C: partial(AATLookupWithDataOffset, tableClass=C),
- "STXHeader": lambda C: partial(STXHeader, tableClass=C),
- "OffsetTo": lambda C: partial(Table, tableClass=C),
- "LOffsetTo": lambda C: partial(LTable, tableClass=C),
- "LOffset24To": lambda C: partial(Table24, tableClass=C),
+ # type class
+ "int8": Int8,
+ "int16": Short,
+ "uint8": UInt8,
+ "uint16": UShort,
+ "uint24": UInt24,
+ "uint32": ULong,
+ "char64": Char64,
+ "Flags32": Flags32,
+ "VarIndex": VarIndex,
+ "Version": Version,
+ "Tag": Tag,
+ "GlyphID": GlyphID,
+ "GlyphID32": GlyphID32,
+ "NameID": NameID,
+ "DeciPoints": DeciPoints,
+ "Fixed": Fixed,
+ "F2Dot14": F2Dot14,
+ "Angle": Angle,
+ "BiasedAngle": BiasedAngle,
+ "struct": Struct,
+ "Offset": Table,
+ "LOffset": LTable,
+ "Offset24": Table24,
+ "ValueRecord": ValueRecord,
+ "DeltaValue": DeltaValue,
+ "VarIdxMapValue": VarIdxMapValue,
+ "VarDataValue": VarDataValue,
+ "LookupFlag": LookupFlag,
+ "ExtendMode": ExtendMode,
+ "CompositeMode": CompositeMode,
+ "STATFlags": STATFlags,
+ # AAT
+ "CIDGlyphMap": CIDGlyphMap,
+ "GlyphCIDMap": GlyphCIDMap,
+ "MortChain": StructWithLength,
+ "MortSubtable": StructWithLength,
+ "MorxChain": StructWithLength,
+ "MorxSubtable": MorxSubtableConverter,
+ # "Template" types
+ "AATLookup": lambda C: partial(AATLookup, tableClass=C),
+ "AATLookupWithDataOffset": lambda C: partial(AATLookupWithDataOffset, tableClass=C),
+ "STXHeader": lambda C: partial(STXHeader, tableClass=C),
+ "OffsetTo": lambda C: partial(Table, tableClass=C),
+ "LOffsetTo": lambda C: partial(LTable, tableClass=C),
+ "LOffset24To": lambda C: partial(Table24, tableClass=C),
}