From 8e013d940601fda3c056ab8365447c105853c14e Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 17 Jun 2022 12:09:13 -0600 Subject: [PATCH 01/18] [avar] WIP Move avar to otData --- Lib/fontTools/ttLib/tables/_a_v_a_r.py | 19 +++++-------------- Lib/fontTools/ttLib/tables/otData.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py index 16f2a219a..7a6bd81e5 100644 --- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py +++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py @@ -14,20 +14,9 @@ import logging log = logging.getLogger(__name__) -# Apple's documentation of 'avar': -# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6avar.html +from .otBase import BaseTTXConverter -AVAR_HEADER_FORMAT = """ - > # big endian - majorVersion: H - minorVersion: H - reserved: H - axisCount: H -""" -assert sstruct.calcsize(AVAR_HEADER_FORMAT) == 8, sstruct.calcsize(AVAR_HEADER_FORMAT) - - -class table__a_v_a_r(DefaultTable.DefaultTable): +class table__a_v_a_r(BaseTTXConverter): """Axis Variations Table This class represents the ``avar`` table of a variable font. The object has one @@ -55,8 +44,9 @@ class table__a_v_a_r(DefaultTable.DefaultTable): def __init__(self, tag=None): DefaultTable.DefaultTable.__init__(self, tag) - self.segments = {} + #self.segments = {} + """ def compile(self, ttFont): axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] header = { @@ -120,3 +110,4 @@ class table__a_v_a_r(DefaultTable.DefaultTable): log.warning("duplicate entry for %s in axis '%s'", fromValue, axis) segment[fromValue] = toValue + """ diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py index 8357ecd2f..a08e6ca0b 100755 --- a/Lib/fontTools/ttLib/tables/otData.py +++ b/Lib/fontTools/ttLib/tables/otData.py @@ -1954,4 +1954,25 @@ otData = [ ('CompositeMode', 'CompositeMode', None, None, 'A CompositeMode enumeration value.'), ('LOffset24To(Paint)', 'BackdropPaint', None, None, 'Offset (from beginning of PaintComposite table) to backdrop Paint subtable.'), ]), + + # + # avar + # + + ('AxisValueMap', [ + ('F2Dot14', 'FromCoordinate', None, None, 'A normalized coordinate value obtained using default normalization'), + ('F2Dot14', 'ToCoordinate', None, None, 'The modified, normalized coordinate value'), + ]), + + ('AxisSegmentMap', [ + ('uint16', 'PositionMapCount', None, None, 'The number of correspondence pairs for this axis'), + ('AxisValueMap', 'AxisValueMap', 'PositionMapCount', 0, 'The array of axis value map records for this axis'), + ]), + + ('avar', [ + ('Version', 'Version', None, None, 'Version of the avar table- 0x00010000'), + ('uint16', 'Reserved', None, None, 'Permanently reserved; set to zero'), + ('uint16', 'AxisCount', None, None, 'The number of variation axes for this font. This must be the same number as axisCount in the "fvar" table'), + ('AxisSegmentMap', 'AxisSegmentMap', 'AxisCount', 0, 'The segment maps array — one segment map for each axis, in the order of axes specified in the "fvar" table'), + ]), ] From 058af5e8cabc5fe69aae0c2d1f1be6177c54da6d Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 17 Jun 2022 12:49:40 -0600 Subject: [PATCH 02/18] [avar] Add avar2 fields --- Lib/fontTools/ttLib/tables/otData.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py index a08e6ca0b..6a374cdc4 100755 --- a/Lib/fontTools/ttLib/tables/otData.py +++ b/Lib/fontTools/ttLib/tables/otData.py @@ -1970,9 +1970,11 @@ otData = [ ]), ('avar', [ - ('Version', 'Version', None, None, 'Version of the avar table- 0x00010000'), + ('Version', 'Version', None, None, 'Version of the avar table- 0x00010000 or 0x00020000'), ('uint16', 'Reserved', None, None, 'Permanently reserved; set to zero'), ('uint16', 'AxisCount', None, None, 'The number of variation axes for this font. This must be the same number as axisCount in the "fvar" table'), ('AxisSegmentMap', 'AxisSegmentMap', 'AxisCount', 0, 'The segment maps array — one segment map for each axis, in the order of axes specified in the "fvar" table'), + ('LOffset', 'VarIdxMap', None, 'Version >= 0x00020000', ''), + ('LOffset', 'VarStore', None, 'Version >= 0x00020000', ''), ]), ] From 5e5922ee0436e64225c519a0ea1aad8a4210c230 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 22 Jul 2022 08:51:00 -0600 Subject: [PATCH 03/18] [avar2] Misc fixes to actually compile avar2 --- Lib/fontTools/ttLib/tables/otConverters.py | 4 ++-- Lib/fontTools/ttLib/tables/otTables.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/otConverters.py b/Lib/fontTools/ttLib/tables/otConverters.py index 61125878f..31bec2070 100644 --- a/Lib/fontTools/ttLib/tables/otConverters.py +++ b/Lib/fontTools/ttLib/tables/otConverters.py @@ -471,11 +471,11 @@ class Version(SimpleValue): staticSize = 4 def read(self, reader, font, tableDict): value = reader.readLong() - assert (value >> 16) == 1, "Unsupported version 0x%08x" % value + #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 + #assert (value >> 16) == 1, "Unsupported version 0x%08x" % value writer.writeLong(value) @staticmethod def fromString(value): diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py index 6e7f3dfb1..7d7ba4e34 100644 --- a/Lib/fontTools/ttLib/tables/otTables.py +++ b/Lib/fontTools/ttLib/tables/otTables.py @@ -697,8 +697,9 @@ class VarIdxMap(BaseTable): if mapping is None: mapping = self.mapping = {} - glyphOrder = font.getGlyphOrder() - mapping = [mapping[g] for g in glyphOrder] + if type(mapping) == dict: + glyphOrder = font.getGlyphOrder() + mapping = [mapping[g] for g in glyphOrder] while len(mapping) > 1 and mapping[-2] == mapping[-1]: del mapping[-1] From 6ba1302ce4fec1e6f35fa27e702e732fd7187adf Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Tue, 7 Mar 2023 11:19:22 -0700 Subject: [PATCH 04/18] black --- Lib/fontTools/ttLib/tables/_a_v_a_r.py | 3 +- Lib/fontTools/ttLib/tables/otConverters.py | 3136 ++++---- Lib/fontTools/ttLib/tables/otData.py | 8206 +++++++++++++++----- Lib/fontTools/ttLib/tables/otTables.py | 3587 ++++----- 4 files changed, 9686 insertions(+), 5246 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py index 7a6bd81e5..5426cdb4d 100644 --- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py +++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py @@ -16,6 +16,7 @@ log = logging.getLogger(__name__) from .otBase import BaseTTXConverter + class table__a_v_a_r(BaseTTXConverter): """Axis Variations Table @@ -44,7 +45,7 @@ class table__a_v_a_r(BaseTTXConverter): def __init__(self, tag=None): DefaultTable.DefaultTable.__init__(self, tag) - #self.segments = {} + # self.segments = {} """ def compile(self, ttFont): diff --git a/Lib/fontTools/ttLib/tables/otConverters.py b/Lib/fontTools/ttLib/tables/otConverters.py index 883e46805..fc5ce43e7 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,1064 @@ 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) + """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) + 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 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 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 + 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 __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 __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 __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 __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.""" + """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 __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 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 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 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 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 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 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) + def xmlWrite(self, xmlWriter, font, value, name, attrs): + """Write a value to XML.""" + raise NotImplementedError(self) - varIndexBasePlusOffsetRE = re.compile(r"VarIndexBase\s*\+\s*(\d+)") + 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)) + 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() + # 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) class Char64(SimpleValue): - """An ASCII string with up to 64 characters. + """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 + Unused character positions are filled with 0x00 bytes. + Used in Apple AAT fonts in the `gcid` table. + """ - 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 + staticSize = 64 - 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) + 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 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 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 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 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) - 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) - 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 - 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)) - 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 - 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 - return table - - def __repr__(self): - return "Struct of " + repr(self.tableClass) + 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 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] + 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() + 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 + staticSize = 2 - def readOffset(self, reader): - return reader.readUShort() + def readOffset(self, reader): + return reader.readUShort() - def writeNullOffset(self, writer): - writer.writeUShort(0) + 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 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) - 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 xmlWrite(self, xmlWriter, font, value, name, attrs): - super(SubStruct, self).xmlWrite(xmlWriter, font, value, None, attrs) +class SubStruct(Struct): + 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) + 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) + 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 + 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 __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 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)) + 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) + @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 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 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 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 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 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 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 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 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 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 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 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 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 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 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() + 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 +1110,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 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) + 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) + 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 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 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 = { + # 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() - } + _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 __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 _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 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 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 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) + 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 __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 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 _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 _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 _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 _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 + 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 + 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) + 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) + 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 _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 _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 _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 _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 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 _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 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 _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 _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 _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 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 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 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 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 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 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 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 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) - 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 - 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 - 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) - 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 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 VarIdxMapValue(BaseConverter): + def read(self, reader, font, tableDict): + fmt = tableDict["EntryFormat"] + nItems = tableDict["MappingCount"] - 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 - innerBits = 1 + (fmt & 0x000F) - innerMask = (1<> 4) + readArray = { + 1: reader.readUInt8Array, + 2: reader.readUShortArray, + 3: reader.readUInt24Array, + 4: reader.readULongArray, + }[entrySize] - 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) + ] - 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)) - 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 - innerBits = 1 + (fmt & 0x000F) - innerMask = (1<> 4) + writeArray = { + 1: writer.writeUInt8Array, + 2: writer.writeUShortArray, + 3: writer.writeUInt24Array, + 4: writer.writeULongArray, + }[entrySize] - 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]) + 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()) - @classmethod - def toString(cls, value): - return cls.enumClass(value).name.lower() + 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() 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), } diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py index f41c74906..f34619fa0 100755 --- a/Lib/fontTools/ttLib/tables/otData.py +++ b/Lib/fontTools/ttLib/tables/otData.py @@ -1,1980 +1,6230 @@ otData = [ - - # - # common - # - - ('LookupOrder', []), - - ('ScriptList', [ - ('uint16', 'ScriptCount', None, None, 'Number of ScriptRecords'), - ('struct', 'ScriptRecord', 'ScriptCount', 0, 'Array of ScriptRecords -listed alphabetically by ScriptTag'), - ]), - - ('ScriptRecord', [ - ('Tag', 'ScriptTag', None, None, '4-byte ScriptTag identifier'), - ('Offset', 'Script', None, None, 'Offset to Script table-from beginning of ScriptList'), - ]), - - ('Script', [ - ('Offset', 'DefaultLangSys', None, None, 'Offset to DefaultLangSys table-from beginning of Script table-may be NULL'), - ('uint16', 'LangSysCount', None, None, 'Number of LangSysRecords for this script-excluding the DefaultLangSys'), - ('struct', 'LangSysRecord', 'LangSysCount', 0, 'Array of LangSysRecords-listed alphabetically by LangSysTag'), - ]), - - ('LangSysRecord', [ - ('Tag', 'LangSysTag', None, None, '4-byte LangSysTag identifier'), - ('Offset', 'LangSys', None, None, 'Offset to LangSys table-from beginning of Script table'), - ]), - - ('LangSys', [ - ('Offset', 'LookupOrder', None, None, '= NULL (reserved for an offset to a reordering table)'), - ('uint16', 'ReqFeatureIndex', None, None, 'Index of a feature required for this language system- if no required features = 0xFFFF'), - ('uint16', 'FeatureCount', None, None, 'Number of FeatureIndex values for this language system-excludes the required feature'), - ('uint16', 'FeatureIndex', 'FeatureCount', 0, 'Array of indices into the FeatureList-in arbitrary order'), - ]), - - ('FeatureList', [ - ('uint16', 'FeatureCount', None, None, 'Number of FeatureRecords in this table'), - ('struct', 'FeatureRecord', 'FeatureCount', 0, 'Array of FeatureRecords-zero-based (first feature has FeatureIndex = 0)-listed alphabetically by FeatureTag'), - ]), - - ('FeatureRecord', [ - ('Tag', 'FeatureTag', None, None, '4-byte feature identification tag'), - ('Offset', 'Feature', None, None, 'Offset to Feature table-from beginning of FeatureList'), - ]), - - ('Feature', [ - ('Offset', 'FeatureParams', None, None, '= NULL (reserved for offset to FeatureParams)'), - ('uint16', 'LookupCount', None, None, 'Number of LookupList indices for this feature'), - ('uint16', 'LookupListIndex', 'LookupCount', 0, 'Array of LookupList indices for this feature -zero-based (first lookup is LookupListIndex = 0)'), - ]), - - ('FeatureParams', [ - ]), - - ('FeatureParamsSize', [ - ('DeciPoints', 'DesignSize', None, None, 'The design size in 720/inch units (decipoints).'), - ('uint16', 'SubfamilyID', None, None, 'Serves as an identifier that associates fonts in a subfamily.'), - ('NameID', 'SubfamilyNameID', None, None, 'Subfamily NameID.'), - ('DeciPoints', 'RangeStart', None, None, 'Small end of recommended usage range (exclusive) in 720/inch units.'), - ('DeciPoints', 'RangeEnd', None, None, 'Large end of recommended usage range (inclusive) in 720/inch units.'), - ]), - - ('FeatureParamsStylisticSet', [ - ('uint16', 'Version', None, None, 'Set to 0.'), - ('NameID', 'UINameID', None, None, 'UI NameID.'), - ]), - - ('FeatureParamsCharacterVariants', [ - ('uint16', 'Format', None, None, 'Set to 0.'), - ('NameID', 'FeatUILabelNameID', None, None, 'Feature UI label NameID.'), - ('NameID', 'FeatUITooltipTextNameID', None, None, 'Feature UI tooltip text NameID.'), - ('NameID', 'SampleTextNameID', None, None, 'Sample text NameID.'), - ('uint16', 'NumNamedParameters', None, None, 'Number of named parameters.'), - ('NameID', 'FirstParamUILabelNameID', None, None, 'First NameID of UI feature parameters.'), - ('uint16', 'CharCount', None, None, 'Count of characters this feature provides glyph variants for.'), - ('uint24', 'Character', 'CharCount', 0, 'Unicode characters for which this feature provides glyph variants.'), - ]), - - ('LookupList', [ - ('uint16', 'LookupCount', None, None, 'Number of lookups in this table'), - ('Offset', 'Lookup', 'LookupCount', 0, 'Array of offsets to Lookup tables-from beginning of LookupList -zero based (first lookup is Lookup index = 0)'), - ]), - - ('Lookup', [ - ('uint16', 'LookupType', None, None, 'Different enumerations for GSUB and GPOS'), - ('LookupFlag', 'LookupFlag', None, None, 'Lookup qualifiers'), - ('uint16', 'SubTableCount', None, None, 'Number of SubTables for this lookup'), - ('Offset', 'SubTable', 'SubTableCount', 0, 'Array of offsets to SubTables-from beginning of Lookup table'), - ('uint16', 'MarkFilteringSet', None, 'LookupFlag & 0x0010', 'If set, indicates that the lookup table structure is followed by a MarkFilteringSet field. The layout engine skips over all mark glyphs not in the mark filtering set indicated.'), - ]), - - ('CoverageFormat1', [ - ('uint16', 'CoverageFormat', None, None, 'Format identifier-format = 1'), - ('uint16', 'GlyphCount', None, None, 'Number of glyphs in the GlyphArray'), - ('GlyphID', 'GlyphArray', 'GlyphCount', 0, 'Array of GlyphIDs-in numerical order'), - ]), - - ('CoverageFormat2', [ - ('uint16', 'CoverageFormat', None, None, 'Format identifier-format = 2'), - ('uint16', 'RangeCount', None, None, 'Number of RangeRecords'), - ('struct', 'RangeRecord', 'RangeCount', 0, 'Array of glyph ranges-ordered by Start GlyphID'), - ]), - - ('RangeRecord', [ - ('GlyphID', 'Start', None, None, 'First GlyphID in the range'), - ('GlyphID', 'End', None, None, 'Last GlyphID in the range'), - ('uint16', 'StartCoverageIndex', None, None, 'Coverage Index of first GlyphID in range'), - ]), - - ('ClassDefFormat1', [ - ('uint16', 'ClassFormat', None, None, 'Format identifier-format = 1'), - ('GlyphID', 'StartGlyph', None, None, 'First GlyphID of the ClassValueArray'), - ('uint16', 'GlyphCount', None, None, 'Size of the ClassValueArray'), - ('uint16', 'ClassValueArray', 'GlyphCount', 0, 'Array of Class Values-one per GlyphID'), - ]), - - ('ClassDefFormat2', [ - ('uint16', 'ClassFormat', None, None, 'Format identifier-format = 2'), - ('uint16', 'ClassRangeCount', None, None, 'Number of ClassRangeRecords'), - ('struct', 'ClassRangeRecord', 'ClassRangeCount', 0, 'Array of ClassRangeRecords-ordered by Start GlyphID'), - ]), - - ('ClassRangeRecord', [ - ('GlyphID', 'Start', None, None, 'First GlyphID in the range'), - ('GlyphID', 'End', None, None, 'Last GlyphID in the range'), - ('uint16', 'Class', None, None, 'Applied to all glyphs in the range'), - ]), - - ('Device', [ - ('uint16', 'StartSize', None, None, 'Smallest size to correct-in ppem'), - ('uint16', 'EndSize', None, None, 'Largest size to correct-in ppem'), - ('uint16', 'DeltaFormat', None, None, 'Format of DeltaValue array data: 1, 2, or 3'), - ('DeltaValue', 'DeltaValue', '', 'DeltaFormat in (1,2,3)', 'Array of compressed data'), - ]), - - - # - # gpos - # - - ('GPOS', [ - ('Version', 'Version', None, None, 'Version of the GPOS table- 0x00010000 or 0x00010001'), - ('Offset', 'ScriptList', None, None, 'Offset to ScriptList table-from beginning of GPOS table'), - ('Offset', 'FeatureList', None, None, 'Offset to FeatureList table-from beginning of GPOS table'), - ('Offset', 'LookupList', None, None, 'Offset to LookupList table-from beginning of GPOS table'), - ('LOffset', 'FeatureVariations', None, 'Version >= 0x00010001', 'Offset to FeatureVariations table-from beginning of GPOS table'), - ]), - - ('SinglePosFormat1', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of SinglePos subtable'), - ('uint16', 'ValueFormat', None, None, 'Defines the types of data in the ValueRecord'), - ('ValueRecord', 'Value', None, None, 'Defines positioning value(s)-applied to all glyphs in the Coverage table'), - ]), - - ('SinglePosFormat2', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 2'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of SinglePos subtable'), - ('uint16', 'ValueFormat', None, None, 'Defines the types of data in the ValueRecord'), - ('uint16', 'ValueCount', None, None, 'Number of ValueRecords'), - ('ValueRecord', 'Value', 'ValueCount', 0, 'Array of ValueRecords-positioning values applied to glyphs'), - ]), - - ('PairPosFormat1', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of PairPos subtable-only the first glyph in each pair'), - ('uint16', 'ValueFormat1', None, None, 'Defines the types of data in ValueRecord1-for the first glyph in the pair -may be zero (0)'), - ('uint16', 'ValueFormat2', None, None, 'Defines the types of data in ValueRecord2-for the second glyph in the pair -may be zero (0)'), - ('uint16', 'PairSetCount', None, None, 'Number of PairSet tables'), - ('Offset', 'PairSet', 'PairSetCount', 0, 'Array of offsets to PairSet tables-from beginning of PairPos subtable-ordered by Coverage Index'), - ]), - - ('PairSet', [ - ('uint16', 'PairValueCount', None, None, 'Number of PairValueRecords'), - ('struct', 'PairValueRecord', 'PairValueCount', 0, 'Array of PairValueRecords-ordered by GlyphID of the second glyph'), - ]), - - ('PairValueRecord', [ - ('GlyphID', 'SecondGlyph', None, None, 'GlyphID of second glyph in the pair-first glyph is listed in the Coverage table'), - ('ValueRecord', 'Value1', None, None, 'Positioning data for the first glyph in the pair'), - ('ValueRecord', 'Value2', None, None, 'Positioning data for the second glyph in the pair'), - ]), - - ('PairPosFormat2', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 2'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of PairPos subtable-for the first glyph of the pair'), - ('uint16', 'ValueFormat1', None, None, 'ValueRecord definition-for the first glyph of the pair-may be zero (0)'), - ('uint16', 'ValueFormat2', None, None, 'ValueRecord definition-for the second glyph of the pair-may be zero (0)'), - ('Offset', 'ClassDef1', None, None, 'Offset to ClassDef table-from beginning of PairPos subtable-for the first glyph of the pair'), - ('Offset', 'ClassDef2', None, None, 'Offset to ClassDef table-from beginning of PairPos subtable-for the second glyph of the pair'), - ('uint16', 'Class1Count', None, None, 'Number of classes in ClassDef1 table-includes Class0'), - ('uint16', 'Class2Count', None, None, 'Number of classes in ClassDef2 table-includes Class0'), - ('struct', 'Class1Record', 'Class1Count', 0, 'Array of Class1 records-ordered by Class1'), - ]), - - ('Class1Record', [ - ('struct', 'Class2Record', 'Class2Count', 0, 'Array of Class2 records-ordered by Class2'), - ]), - - ('Class2Record', [ - ('ValueRecord', 'Value1', None, None, 'Positioning for first glyph-empty if ValueFormat1 = 0'), - ('ValueRecord', 'Value2', None, None, 'Positioning for second glyph-empty if ValueFormat2 = 0'), - ]), - - ('CursivePosFormat1', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of CursivePos subtable'), - ('uint16', 'EntryExitCount', None, None, 'Number of EntryExit records'), - ('struct', 'EntryExitRecord', 'EntryExitCount', 0, 'Array of EntryExit records-in Coverage Index order'), - ]), - - ('EntryExitRecord', [ - ('Offset', 'EntryAnchor', None, None, 'Offset to EntryAnchor table-from beginning of CursivePos subtable-may be NULL'), - ('Offset', 'ExitAnchor', None, None, 'Offset to ExitAnchor table-from beginning of CursivePos subtable-may be NULL'), - ]), - - ('MarkBasePosFormat1', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'MarkCoverage', None, None, 'Offset to MarkCoverage table-from beginning of MarkBasePos subtable'), - ('Offset', 'BaseCoverage', None, None, 'Offset to BaseCoverage table-from beginning of MarkBasePos subtable'), - ('uint16', 'ClassCount', None, None, 'Number of classes defined for marks'), - ('Offset', 'MarkArray', None, None, 'Offset to MarkArray table-from beginning of MarkBasePos subtable'), - ('Offset', 'BaseArray', None, None, 'Offset to BaseArray table-from beginning of MarkBasePos subtable'), - ]), - - ('BaseArray', [ - ('uint16', 'BaseCount', None, None, 'Number of BaseRecords'), - ('struct', 'BaseRecord', 'BaseCount', 0, 'Array of BaseRecords-in order of BaseCoverage Index'), - ]), - - ('BaseRecord', [ - ('Offset', 'BaseAnchor', 'ClassCount', 0, 'Array of offsets (one per class) to Anchor tables-from beginning of BaseArray table-ordered by class-zero-based'), - ]), - - ('MarkLigPosFormat1', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'MarkCoverage', None, None, 'Offset to Mark Coverage table-from beginning of MarkLigPos subtable'), - ('Offset', 'LigatureCoverage', None, None, 'Offset to Ligature Coverage table-from beginning of MarkLigPos subtable'), - ('uint16', 'ClassCount', None, None, 'Number of defined mark classes'), - ('Offset', 'MarkArray', None, None, 'Offset to MarkArray table-from beginning of MarkLigPos subtable'), - ('Offset', 'LigatureArray', None, None, 'Offset to LigatureArray table-from beginning of MarkLigPos subtable'), - ]), - - ('LigatureArray', [ - ('uint16', 'LigatureCount', None, None, 'Number of LigatureAttach table offsets'), - ('Offset', 'LigatureAttach', 'LigatureCount', 0, 'Array of offsets to LigatureAttach tables-from beginning of LigatureArray table-ordered by LigatureCoverage Index'), - ]), - - ('LigatureAttach', [ - ('uint16', 'ComponentCount', None, None, 'Number of ComponentRecords in this ligature'), - ('struct', 'ComponentRecord', 'ComponentCount', 0, 'Array of Component records-ordered in writing direction'), - ]), - - ('ComponentRecord', [ - ('Offset', 'LigatureAnchor', 'ClassCount', 0, 'Array of offsets (one per class) to Anchor tables-from beginning of LigatureAttach table-ordered by class-NULL if a component does not have an attachment for a class-zero-based array'), - ]), - - ('MarkMarkPosFormat1', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Mark1Coverage', None, None, 'Offset to Combining Mark Coverage table-from beginning of MarkMarkPos subtable'), - ('Offset', 'Mark2Coverage', None, None, 'Offset to Base Mark Coverage table-from beginning of MarkMarkPos subtable'), - ('uint16', 'ClassCount', None, None, 'Number of Combining Mark classes defined'), - ('Offset', 'Mark1Array', None, None, 'Offset to MarkArray table for Mark1-from beginning of MarkMarkPos subtable'), - ('Offset', 'Mark2Array', None, None, 'Offset to Mark2Array table for Mark2-from beginning of MarkMarkPos subtable'), - ]), - - ('Mark2Array', [ - ('uint16', 'Mark2Count', None, None, 'Number of Mark2 records'), - ('struct', 'Mark2Record', 'Mark2Count', 0, 'Array of Mark2 records-in Coverage order'), - ]), - - ('Mark2Record', [ - ('Offset', 'Mark2Anchor', 'ClassCount', 0, 'Array of offsets (one per class) to Anchor tables-from beginning of Mark2Array table-zero-based array'), - ]), - - ('PosLookupRecord', [ - ('uint16', 'SequenceIndex', None, None, 'Index to input glyph sequence-first glyph = 0'), - ('uint16', 'LookupListIndex', None, None, 'Lookup to apply to that position-zero-based'), - ]), - - ('ContextPosFormat1', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of ContextPos subtable'), - ('uint16', 'PosRuleSetCount', None, None, 'Number of PosRuleSet tables'), - ('Offset', 'PosRuleSet', 'PosRuleSetCount', 0, 'Array of offsets to PosRuleSet tables-from beginning of ContextPos subtable-ordered by Coverage Index'), - ]), - - ('PosRuleSet', [ - ('uint16', 'PosRuleCount', None, None, 'Number of PosRule tables'), - ('Offset', 'PosRule', 'PosRuleCount', 0, 'Array of offsets to PosRule tables-from beginning of PosRuleSet-ordered by preference'), - ]), - - ('PosRule', [ - ('uint16', 'GlyphCount', None, None, 'Number of glyphs in the Input glyph sequence'), - ('uint16', 'PosCount', None, None, 'Number of PosLookupRecords'), - ('GlyphID', 'Input', 'GlyphCount', -1, 'Array of input GlyphIDs-starting with the second glyph'), - ('struct', 'PosLookupRecord', 'PosCount', 0, 'Array of positioning lookups-in design order'), - ]), - - ('ContextPosFormat2', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 2'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of ContextPos subtable'), - ('Offset', 'ClassDef', None, None, 'Offset to ClassDef table-from beginning of ContextPos subtable'), - ('uint16', 'PosClassSetCount', None, None, 'Number of PosClassSet tables'), - ('Offset', 'PosClassSet', 'PosClassSetCount', 0, 'Array of offsets to PosClassSet tables-from beginning of ContextPos subtable-ordered by class-may be NULL'), - ]), - - ('PosClassSet', [ - ('uint16', 'PosClassRuleCount', None, None, 'Number of PosClassRule tables'), - ('Offset', 'PosClassRule', 'PosClassRuleCount', 0, 'Array of offsets to PosClassRule tables-from beginning of PosClassSet-ordered by preference'), - ]), - - ('PosClassRule', [ - ('uint16', 'GlyphCount', None, None, 'Number of glyphs to be matched'), - ('uint16', 'PosCount', None, None, 'Number of PosLookupRecords'), - ('uint16', 'Class', 'GlyphCount', -1, 'Array of classes-beginning with the second class-to be matched to the input glyph sequence'), - ('struct', 'PosLookupRecord', 'PosCount', 0, 'Array of positioning lookups-in design order'), - ]), - - ('ContextPosFormat3', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 3'), - ('uint16', 'GlyphCount', None, None, 'Number of glyphs in the input sequence'), - ('uint16', 'PosCount', None, None, 'Number of PosLookupRecords'), - ('Offset', 'Coverage', 'GlyphCount', 0, 'Array of offsets to Coverage tables-from beginning of ContextPos subtable'), - ('struct', 'PosLookupRecord', 'PosCount', 0, 'Array of positioning lookups-in design order'), - ]), - - ('ChainContextPosFormat1', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of ContextPos subtable'), - ('uint16', 'ChainPosRuleSetCount', None, None, 'Number of ChainPosRuleSet tables'), - ('Offset', 'ChainPosRuleSet', 'ChainPosRuleSetCount', 0, 'Array of offsets to ChainPosRuleSet tables-from beginning of ContextPos subtable-ordered by Coverage Index'), - ]), - - ('ChainPosRuleSet', [ - ('uint16', 'ChainPosRuleCount', None, None, 'Number of ChainPosRule tables'), - ('Offset', 'ChainPosRule', 'ChainPosRuleCount', 0, 'Array of offsets to ChainPosRule tables-from beginning of ChainPosRuleSet-ordered by preference'), - ]), - - ('ChainPosRule', [ - ('uint16', 'BacktrackGlyphCount', None, None, 'Total number of glyphs in the backtrack sequence (number of glyphs to be matched before the first glyph)'), - ('GlyphID', 'Backtrack', 'BacktrackGlyphCount', 0, "Array of backtracking GlyphID's (to be matched before the input sequence)"), - ('uint16', 'InputGlyphCount', None, None, 'Total number of glyphs in the input sequence (includes the first glyph)'), - ('GlyphID', 'Input', 'InputGlyphCount', -1, 'Array of input GlyphIDs (start with second glyph)'), - ('uint16', 'LookAheadGlyphCount', None, None, 'Total number of glyphs in the look ahead sequence (number of glyphs to be matched after the input sequence)'), - ('GlyphID', 'LookAhead', 'LookAheadGlyphCount', 0, "Array of lookahead GlyphID's (to be matched after the input sequence)"), - ('uint16', 'PosCount', None, None, 'Number of PosLookupRecords'), - ('struct', 'PosLookupRecord', 'PosCount', 0, 'Array of PosLookupRecords (in design order)'), - ]), - - ('ChainContextPosFormat2', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 2'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of ChainContextPos subtable'), - ('Offset', 'BacktrackClassDef', None, None, 'Offset to ClassDef table containing backtrack sequence context-from beginning of ChainContextPos subtable'), - ('Offset', 'InputClassDef', None, None, 'Offset to ClassDef table containing input sequence context-from beginning of ChainContextPos subtable'), - ('Offset', 'LookAheadClassDef', None, None, 'Offset to ClassDef table containing lookahead sequence context-from beginning of ChainContextPos subtable'), - ('uint16', 'ChainPosClassSetCount', None, None, 'Number of ChainPosClassSet tables'), - ('Offset', 'ChainPosClassSet', 'ChainPosClassSetCount', 0, 'Array of offsets to ChainPosClassSet tables-from beginning of ChainContextPos subtable-ordered by input class-may be NULL'), - ]), - - ('ChainPosClassSet', [ - ('uint16', 'ChainPosClassRuleCount', None, None, 'Number of ChainPosClassRule tables'), - ('Offset', 'ChainPosClassRule', 'ChainPosClassRuleCount', 0, 'Array of offsets to ChainPosClassRule tables-from beginning of ChainPosClassSet-ordered by preference'), - ]), - - ('ChainPosClassRule', [ - ('uint16', 'BacktrackGlyphCount', None, None, 'Total number of glyphs in the backtrack sequence (number of glyphs to be matched before the first glyph)'), - ('uint16', 'Backtrack', 'BacktrackGlyphCount', 0, 'Array of backtracking classes(to be matched before the input sequence)'), - ('uint16', 'InputGlyphCount', None, None, 'Total number of classes in the input sequence (includes the first class)'), - ('uint16', 'Input', 'InputGlyphCount', -1, 'Array of input classes(start with second class; to be matched with the input glyph sequence)'), - ('uint16', 'LookAheadGlyphCount', None, None, 'Total number of classes in the look ahead sequence (number of classes to be matched after the input sequence)'), - ('uint16', 'LookAhead', 'LookAheadGlyphCount', 0, 'Array of lookahead classes(to be matched after the input sequence)'), - ('uint16', 'PosCount', None, None, 'Number of PosLookupRecords'), - ('struct', 'PosLookupRecord', 'PosCount', 0, 'Array of PosLookupRecords (in design order)'), - ]), - - ('ChainContextPosFormat3', [ - ('uint16', 'PosFormat', None, None, 'Format identifier-format = 3'), - ('uint16', 'BacktrackGlyphCount', None, None, 'Number of glyphs in the backtracking sequence'), - ('Offset', 'BacktrackCoverage', 'BacktrackGlyphCount', 0, 'Array of offsets to coverage tables in backtracking sequence, in glyph sequence order'), - ('uint16', 'InputGlyphCount', None, None, 'Number of glyphs in input sequence'), - ('Offset', 'InputCoverage', 'InputGlyphCount', 0, 'Array of offsets to coverage tables in input sequence, in glyph sequence order'), - ('uint16', 'LookAheadGlyphCount', None, None, 'Number of glyphs in lookahead sequence'), - ('Offset', 'LookAheadCoverage', 'LookAheadGlyphCount', 0, 'Array of offsets to coverage tables in lookahead sequence, in glyph sequence order'), - ('uint16', 'PosCount', None, None, 'Number of PosLookupRecords'), - ('struct', 'PosLookupRecord', 'PosCount', 0, 'Array of PosLookupRecords,in design order'), - ]), - - ('ExtensionPosFormat1', [ - ('uint16', 'ExtFormat', None, None, 'Format identifier. Set to 1.'), - ('uint16', 'ExtensionLookupType', None, None, 'Lookup type of subtable referenced by ExtensionOffset (i.e. the extension subtable).'), - ('LOffset', 'ExtSubTable', None, None, 'Offset to SubTable'), - ]), - -# ('ValueRecord', [ -# ('int16', 'XPlacement', None, None, 'Horizontal adjustment for placement-in design units'), -# ('int16', 'YPlacement', None, None, 'Vertical adjustment for placement-in design units'), -# ('int16', 'XAdvance', None, None, 'Horizontal adjustment for advance-in design units (only used for horizontal writing)'), -# ('int16', 'YAdvance', None, None, 'Vertical adjustment for advance-in design units (only used for vertical writing)'), -# ('Offset', 'XPlaDevice', None, None, 'Offset to Device table for horizontal placement-measured from beginning of PosTable (may be NULL)'), -# ('Offset', 'YPlaDevice', None, None, 'Offset to Device table for vertical placement-measured from beginning of PosTable (may be NULL)'), -# ('Offset', 'XAdvDevice', None, None, 'Offset to Device table for horizontal advance-measured from beginning of PosTable (may be NULL)'), -# ('Offset', 'YAdvDevice', None, None, 'Offset to Device table for vertical advance-measured from beginning of PosTable (may be NULL)'), -# ]), - - ('AnchorFormat1', [ - ('uint16', 'AnchorFormat', None, None, 'Format identifier-format = 1'), - ('int16', 'XCoordinate', None, None, 'Horizontal value-in design units'), - ('int16', 'YCoordinate', None, None, 'Vertical value-in design units'), - ]), - - ('AnchorFormat2', [ - ('uint16', 'AnchorFormat', None, None, 'Format identifier-format = 2'), - ('int16', 'XCoordinate', None, None, 'Horizontal value-in design units'), - ('int16', 'YCoordinate', None, None, 'Vertical value-in design units'), - ('uint16', 'AnchorPoint', None, None, 'Index to glyph contour point'), - ]), - - ('AnchorFormat3', [ - ('uint16', 'AnchorFormat', None, None, 'Format identifier-format = 3'), - ('int16', 'XCoordinate', None, None, 'Horizontal value-in design units'), - ('int16', 'YCoordinate', None, None, 'Vertical value-in design units'), - ('Offset', 'XDeviceTable', None, None, 'Offset to Device table for X coordinate- from beginning of Anchor table (may be NULL)'), - ('Offset', 'YDeviceTable', None, None, 'Offset to Device table for Y coordinate- from beginning of Anchor table (may be NULL)'), - ]), - - ('MarkArray', [ - ('uint16', 'MarkCount', None, None, 'Number of MarkRecords'), - ('struct', 'MarkRecord', 'MarkCount', 0, 'Array of MarkRecords-in Coverage order'), - ]), - - ('MarkRecord', [ - ('uint16', 'Class', None, None, 'Class defined for this mark'), - ('Offset', 'MarkAnchor', None, None, 'Offset to Anchor table-from beginning of MarkArray table'), - ]), - - - # - # gsub - # - - ('GSUB', [ - ('Version', 'Version', None, None, 'Version of the GSUB table- 0x00010000 or 0x00010001'), - ('Offset', 'ScriptList', None, None, 'Offset to ScriptList table-from beginning of GSUB table'), - ('Offset', 'FeatureList', None, None, 'Offset to FeatureList table-from beginning of GSUB table'), - ('Offset', 'LookupList', None, None, 'Offset to LookupList table-from beginning of GSUB table'), - ('LOffset', 'FeatureVariations', None, 'Version >= 0x00010001', 'Offset to FeatureVariations table-from beginning of GSUB table'), - ]), - - ('SingleSubstFormat1', [ - ('uint16', 'SubstFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of Substitution table'), - ('uint16', 'DeltaGlyphID', None, None, 'Add to original GlyphID modulo 65536 to get substitute GlyphID'), - ]), - - ('SingleSubstFormat2', [ - ('uint16', 'SubstFormat', None, None, 'Format identifier-format = 2'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of Substitution table'), - ('uint16', 'GlyphCount', None, None, 'Number of GlyphIDs in the Substitute array'), - ('GlyphID', 'Substitute', 'GlyphCount', 0, 'Array of substitute GlyphIDs-ordered by Coverage Index'), - ]), - - ('MultipleSubstFormat1', [ - ('uint16', 'SubstFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of Substitution table'), - ('uint16', 'SequenceCount', None, None, 'Number of Sequence table offsets in the Sequence array'), - ('Offset', 'Sequence', 'SequenceCount', 0, 'Array of offsets to Sequence tables-from beginning of Substitution table-ordered by Coverage Index'), - ]), - - ('Sequence', [ - ('uint16', 'GlyphCount', None, None, 'Number of GlyphIDs in the Substitute array. This should always be greater than 0.'), - ('GlyphID', 'Substitute', 'GlyphCount', 0, 'String of GlyphIDs to substitute'), - ]), - - ('AlternateSubstFormat1', [ - ('uint16', 'SubstFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of Substitution table'), - ('uint16', 'AlternateSetCount', None, None, 'Number of AlternateSet tables'), - ('Offset', 'AlternateSet', 'AlternateSetCount', 0, 'Array of offsets to AlternateSet tables-from beginning of Substitution table-ordered by Coverage Index'), - ]), - - ('AlternateSet', [ - ('uint16', 'GlyphCount', None, None, 'Number of GlyphIDs in the Alternate array'), - ('GlyphID', 'Alternate', 'GlyphCount', 0, 'Array of alternate GlyphIDs-in arbitrary order'), - ]), - - ('LigatureSubstFormat1', [ - ('uint16', 'SubstFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of Substitution table'), - ('uint16', 'LigSetCount', None, None, 'Number of LigatureSet tables'), - ('Offset', 'LigatureSet', 'LigSetCount', 0, 'Array of offsets to LigatureSet tables-from beginning of Substitution table-ordered by Coverage Index'), - ]), - - ('LigatureSet', [ - ('uint16', 'LigatureCount', None, None, 'Number of Ligature tables'), - ('Offset', 'Ligature', 'LigatureCount', 0, 'Array of offsets to Ligature tables-from beginning of LigatureSet table-ordered by preference'), - ]), - - ('Ligature', [ - ('GlyphID', 'LigGlyph', None, None, 'GlyphID of ligature to substitute'), - ('uint16', 'CompCount', None, None, 'Number of components in the ligature'), - ('GlyphID', 'Component', 'CompCount', -1, 'Array of component GlyphIDs-start with the second component-ordered in writing direction'), - ]), - - ('SubstLookupRecord', [ - ('uint16', 'SequenceIndex', None, None, 'Index into current glyph sequence-first glyph = 0'), - ('uint16', 'LookupListIndex', None, None, 'Lookup to apply to that position-zero-based'), - ]), - - ('ContextSubstFormat1', [ - ('uint16', 'SubstFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of Substitution table'), - ('uint16', 'SubRuleSetCount', None, None, 'Number of SubRuleSet tables-must equal GlyphCount in Coverage table'), - ('Offset', 'SubRuleSet', 'SubRuleSetCount', 0, 'Array of offsets to SubRuleSet tables-from beginning of Substitution table-ordered by Coverage Index'), - ]), - - ('SubRuleSet', [ - ('uint16', 'SubRuleCount', None, None, 'Number of SubRule tables'), - ('Offset', 'SubRule', 'SubRuleCount', 0, 'Array of offsets to SubRule tables-from beginning of SubRuleSet table-ordered by preference'), - ]), - - ('SubRule', [ - ('uint16', 'GlyphCount', None, None, 'Total number of glyphs in input glyph sequence-includes the first glyph'), - ('uint16', 'SubstCount', None, None, 'Number of SubstLookupRecords'), - ('GlyphID', 'Input', 'GlyphCount', -1, 'Array of input GlyphIDs-start with second glyph'), - ('struct', 'SubstLookupRecord', 'SubstCount', 0, 'Array of SubstLookupRecords-in design order'), - ]), - - ('ContextSubstFormat2', [ - ('uint16', 'SubstFormat', None, None, 'Format identifier-format = 2'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of Substitution table'), - ('Offset', 'ClassDef', None, None, 'Offset to glyph ClassDef table-from beginning of Substitution table'), - ('uint16', 'SubClassSetCount', None, None, 'Number of SubClassSet tables'), - ('Offset', 'SubClassSet', 'SubClassSetCount', 0, 'Array of offsets to SubClassSet tables-from beginning of Substitution table-ordered by class-may be NULL'), - ]), - - ('SubClassSet', [ - ('uint16', 'SubClassRuleCount', None, None, 'Number of SubClassRule tables'), - ('Offset', 'SubClassRule', 'SubClassRuleCount', 0, 'Array of offsets to SubClassRule tables-from beginning of SubClassSet-ordered by preference'), - ]), - - ('SubClassRule', [ - ('uint16', 'GlyphCount', None, None, 'Total number of classes specified for the context in the rule-includes the first class'), - ('uint16', 'SubstCount', None, None, 'Number of SubstLookupRecords'), - ('uint16', 'Class', 'GlyphCount', -1, 'Array of classes-beginning with the second class-to be matched to the input glyph class sequence'), - ('struct', 'SubstLookupRecord', 'SubstCount', 0, 'Array of Substitution lookups-in design order'), - ]), - - ('ContextSubstFormat3', [ - ('uint16', 'SubstFormat', None, None, 'Format identifier-format = 3'), - ('uint16', 'GlyphCount', None, None, 'Number of glyphs in the input glyph sequence'), - ('uint16', 'SubstCount', None, None, 'Number of SubstLookupRecords'), - ('Offset', 'Coverage', 'GlyphCount', 0, 'Array of offsets to Coverage table-from beginning of Substitution table-in glyph sequence order'), - ('struct', 'SubstLookupRecord', 'SubstCount', 0, 'Array of SubstLookupRecords-in design order'), - ]), - - ('ChainContextSubstFormat1', [ - ('uint16', 'SubstFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of Substitution table'), - ('uint16', 'ChainSubRuleSetCount', None, None, 'Number of ChainSubRuleSet tables-must equal GlyphCount in Coverage table'), - ('Offset', 'ChainSubRuleSet', 'ChainSubRuleSetCount', 0, 'Array of offsets to ChainSubRuleSet tables-from beginning of Substitution table-ordered by Coverage Index'), - ]), - - ('ChainSubRuleSet', [ - ('uint16', 'ChainSubRuleCount', None, None, 'Number of ChainSubRule tables'), - ('Offset', 'ChainSubRule', 'ChainSubRuleCount', 0, 'Array of offsets to ChainSubRule tables-from beginning of ChainSubRuleSet table-ordered by preference'), - ]), - - ('ChainSubRule', [ - ('uint16', 'BacktrackGlyphCount', None, None, 'Total number of glyphs in the backtrack sequence (number of glyphs to be matched before the first glyph)'), - ('GlyphID', 'Backtrack', 'BacktrackGlyphCount', 0, "Array of backtracking GlyphID's (to be matched before the input sequence)"), - ('uint16', 'InputGlyphCount', None, None, 'Total number of glyphs in the input sequence (includes the first glyph)'), - ('GlyphID', 'Input', 'InputGlyphCount', -1, 'Array of input GlyphIDs (start with second glyph)'), - ('uint16', 'LookAheadGlyphCount', None, None, 'Total number of glyphs in the look ahead sequence (number of glyphs to be matched after the input sequence)'), - ('GlyphID', 'LookAhead', 'LookAheadGlyphCount', 0, "Array of lookahead GlyphID's (to be matched after the input sequence)"), - ('uint16', 'SubstCount', None, None, 'Number of SubstLookupRecords'), - ('struct', 'SubstLookupRecord', 'SubstCount', 0, 'Array of SubstLookupRecords (in design order)'), - ]), - - ('ChainContextSubstFormat2', [ - ('uint16', 'SubstFormat', None, None, 'Format identifier-format = 2'), - ('Offset', 'Coverage', None, None, 'Offset to Coverage table-from beginning of Substitution table'), - ('Offset', 'BacktrackClassDef', None, None, 'Offset to glyph ClassDef table containing backtrack sequence data-from beginning of Substitution table'), - ('Offset', 'InputClassDef', None, None, 'Offset to glyph ClassDef table containing input sequence data-from beginning of Substitution table'), - ('Offset', 'LookAheadClassDef', None, None, 'Offset to glyph ClassDef table containing lookahead sequence data-from beginning of Substitution table'), - ('uint16', 'ChainSubClassSetCount', None, None, 'Number of ChainSubClassSet tables'), - ('Offset', 'ChainSubClassSet', 'ChainSubClassSetCount', 0, 'Array of offsets to ChainSubClassSet tables-from beginning of Substitution table-ordered by input class-may be NULL'), - ]), - - ('ChainSubClassSet', [ - ('uint16', 'ChainSubClassRuleCount', None, None, 'Number of ChainSubClassRule tables'), - ('Offset', 'ChainSubClassRule', 'ChainSubClassRuleCount', 0, 'Array of offsets to ChainSubClassRule tables-from beginning of ChainSubClassSet-ordered by preference'), - ]), - - ('ChainSubClassRule', [ - ('uint16', 'BacktrackGlyphCount', None, None, 'Total number of glyphs in the backtrack sequence (number of glyphs to be matched before the first glyph)'), - ('uint16', 'Backtrack', 'BacktrackGlyphCount', 0, 'Array of backtracking classes(to be matched before the input sequence)'), - ('uint16', 'InputGlyphCount', None, None, 'Total number of classes in the input sequence (includes the first class)'), - ('uint16', 'Input', 'InputGlyphCount', -1, 'Array of input classes(start with second class; to be matched with the input glyph sequence)'), - ('uint16', 'LookAheadGlyphCount', None, None, 'Total number of classes in the look ahead sequence (number of classes to be matched after the input sequence)'), - ('uint16', 'LookAhead', 'LookAheadGlyphCount', 0, 'Array of lookahead classes(to be matched after the input sequence)'), - ('uint16', 'SubstCount', None, None, 'Number of SubstLookupRecords'), - ('struct', 'SubstLookupRecord', 'SubstCount', 0, 'Array of SubstLookupRecords (in design order)'), - ]), - - ('ChainContextSubstFormat3', [ - ('uint16', 'SubstFormat', None, None, 'Format identifier-format = 3'), - ('uint16', 'BacktrackGlyphCount', None, None, 'Number of glyphs in the backtracking sequence'), - ('Offset', 'BacktrackCoverage', 'BacktrackGlyphCount', 0, 'Array of offsets to coverage tables in backtracking sequence, in glyph sequence order'), - ('uint16', 'InputGlyphCount', None, None, 'Number of glyphs in input sequence'), - ('Offset', 'InputCoverage', 'InputGlyphCount', 0, 'Array of offsets to coverage tables in input sequence, in glyph sequence order'), - ('uint16', 'LookAheadGlyphCount', None, None, 'Number of glyphs in lookahead sequence'), - ('Offset', 'LookAheadCoverage', 'LookAheadGlyphCount', 0, 'Array of offsets to coverage tables in lookahead sequence, in glyph sequence order'), - ('uint16', 'SubstCount', None, None, 'Number of SubstLookupRecords'), - ('struct', 'SubstLookupRecord', 'SubstCount', 0, 'Array of SubstLookupRecords, in design order'), - ]), - - ('ExtensionSubstFormat1', [ - ('uint16', 'ExtFormat', None, None, 'Format identifier. Set to 1.'), - ('uint16', 'ExtensionLookupType', None, None, 'Lookup type of subtable referenced by ExtensionOffset (i.e. the extension subtable).'), - ('LOffset', 'ExtSubTable', None, None, 'Array of offsets to Lookup tables-from beginning of LookupList -zero based (first lookup is Lookup index = 0)'), - ]), - - ('ReverseChainSingleSubstFormat1', [ - ('uint16', 'SubstFormat', None, None, 'Format identifier-format = 1'), - ('Offset', 'Coverage', None, 0, 'Offset to Coverage table - from beginning of Substitution table'), - ('uint16', 'BacktrackGlyphCount', None, None, 'Number of glyphs in the backtracking sequence'), - ('Offset', 'BacktrackCoverage', 'BacktrackGlyphCount', 0, 'Array of offsets to coverage tables in backtracking sequence, in glyph sequence order'), - ('uint16', 'LookAheadGlyphCount', None, None, 'Number of glyphs in lookahead sequence'), - ('Offset', 'LookAheadCoverage', 'LookAheadGlyphCount', 0, 'Array of offsets to coverage tables in lookahead sequence, in glyph sequence order'), - ('uint16', 'GlyphCount', None, None, 'Number of GlyphIDs in the Substitute array'), - ('GlyphID', 'Substitute', 'GlyphCount', 0, 'Array of substitute GlyphIDs-ordered by Coverage index'), - ]), - - # - # gdef - # - - ('GDEF', [ - ('Version', 'Version', None, None, 'Version of the GDEF table- 0x00010000, 0x00010002, or 0x00010003'), - ('Offset', 'GlyphClassDef', None, None, 'Offset to class definition table for glyph type-from beginning of GDEF header (may be NULL)'), - ('Offset', 'AttachList', None, None, 'Offset to list of glyphs with attachment points-from beginning of GDEF header (may be NULL)'), - ('Offset', 'LigCaretList', None, None, 'Offset to list of positioning points for ligature carets-from beginning of GDEF header (may be NULL)'), - ('Offset', 'MarkAttachClassDef', None, None, 'Offset to class definition table for mark attachment type-from beginning of GDEF header (may be NULL)'), - ('Offset', 'MarkGlyphSetsDef', None, 'Version >= 0x00010002', 'Offset to the table of mark set definitions-from beginning of GDEF header (may be NULL)'), - ('LOffset', 'VarStore', None, 'Version >= 0x00010003', 'Offset to variation store (may be NULL)'), - ]), - - ('AttachList', [ - ('Offset', 'Coverage', None, None, 'Offset to Coverage table - from beginning of AttachList table'), - ('uint16', 'GlyphCount', None, None, 'Number of glyphs with attachment points'), - ('Offset', 'AttachPoint', 'GlyphCount', 0, 'Array of offsets to AttachPoint tables-from beginning of AttachList table-in Coverage Index order'), - ]), - - ('AttachPoint', [ - ('uint16', 'PointCount', None, None, 'Number of attachment points on this glyph'), - ('uint16', 'PointIndex', 'PointCount', 0, 'Array of contour point indices -in increasing numerical order'), - ]), - - ('LigCaretList', [ - ('Offset', 'Coverage', None, None, 'Offset to Coverage table - from beginning of LigCaretList table'), - ('uint16', 'LigGlyphCount', None, None, 'Number of ligature glyphs'), - ('Offset', 'LigGlyph', 'LigGlyphCount', 0, 'Array of offsets to LigGlyph tables-from beginning of LigCaretList table-in Coverage Index order'), - ]), - - ('LigGlyph', [ - ('uint16', 'CaretCount', None, None, 'Number of CaretValues for this ligature (components - 1)'), - ('Offset', 'CaretValue', 'CaretCount', 0, 'Array of offsets to CaretValue tables-from beginning of LigGlyph table-in increasing coordinate order'), - ]), - - ('CaretValueFormat1', [ - ('uint16', 'CaretValueFormat', None, None, 'Format identifier-format = 1'), - ('int16', 'Coordinate', None, None, 'X or Y value, in design units'), - ]), - - ('CaretValueFormat2', [ - ('uint16', 'CaretValueFormat', None, None, 'Format identifier-format = 2'), - ('uint16', 'CaretValuePoint', None, None, 'Contour point index on glyph'), - ]), - - ('CaretValueFormat3', [ - ('uint16', 'CaretValueFormat', None, None, 'Format identifier-format = 3'), - ('int16', 'Coordinate', None, None, 'X or Y value, in design units'), - ('Offset', 'DeviceTable', None, None, 'Offset to Device table for X or Y value-from beginning of CaretValue table'), - ]), - - ('MarkGlyphSetsDef', [ - ('uint16', 'MarkSetTableFormat', None, None, 'Format identifier == 1'), - ('uint16', 'MarkSetCount', None, None, 'Number of mark sets defined'), - ('LOffset', 'Coverage', 'MarkSetCount', 0, 'Array of offsets to mark set coverage tables.'), - ]), - - # - # base - # - - ('BASE', [ - ('Version', 'Version', None, None, 'Version of the BASE table-initially 0x00010000'), - ('Offset', 'HorizAxis', None, None, 'Offset to horizontal Axis table-from beginning of BASE table-may be NULL'), - ('Offset', 'VertAxis', None, None, 'Offset to vertical Axis table-from beginning of BASE table-may be NULL'), - ('LOffset', 'VarStore', None, 'Version >= 0x00010001', 'Offset to variation store (may be NULL)'), - ]), - - ('Axis', [ - ('Offset', 'BaseTagList', None, None, 'Offset to BaseTagList table-from beginning of Axis table-may be NULL'), - ('Offset', 'BaseScriptList', None, None, 'Offset to BaseScriptList table-from beginning of Axis table'), - ]), - - ('BaseTagList', [ - ('uint16', 'BaseTagCount', None, None, 'Number of baseline identification tags in this text direction-may be zero (0)'), - ('Tag', 'BaselineTag', 'BaseTagCount', 0, 'Array of 4-byte baseline identification tags-must be in alphabetical order'), - ]), - - ('BaseScriptList', [ - ('uint16', 'BaseScriptCount', None, None, 'Number of BaseScriptRecords defined'), - ('struct', 'BaseScriptRecord', 'BaseScriptCount', 0, 'Array of BaseScriptRecords-in alphabetical order by BaseScriptTag'), - ]), - - ('BaseScriptRecord', [ - ('Tag', 'BaseScriptTag', None, None, '4-byte script identification tag'), - ('Offset', 'BaseScript', None, None, 'Offset to BaseScript table-from beginning of BaseScriptList'), - ]), - - ('BaseScript', [ - ('Offset', 'BaseValues', None, None, 'Offset to BaseValues table-from beginning of BaseScript table-may be NULL'), - ('Offset', 'DefaultMinMax', None, None, 'Offset to MinMax table- from beginning of BaseScript table-may be NULL'), - ('uint16', 'BaseLangSysCount', None, None, 'Number of BaseLangSysRecords defined-may be zero (0)'), - ('struct', 'BaseLangSysRecord', 'BaseLangSysCount', 0, 'Array of BaseLangSysRecords-in alphabetical order by BaseLangSysTag'), - ]), - - ('BaseLangSysRecord', [ - ('Tag', 'BaseLangSysTag', None, None, '4-byte language system identification tag'), - ('Offset', 'MinMax', None, None, 'Offset to MinMax table-from beginning of BaseScript table'), - ]), - - ('BaseValues', [ - ('uint16', 'DefaultIndex', None, None, 'Index number of default baseline for this script-equals index position of baseline tag in BaselineArray of the BaseTagList'), - ('uint16', 'BaseCoordCount', None, None, 'Number of BaseCoord tables defined-should equal BaseTagCount in the BaseTagList'), - ('Offset', 'BaseCoord', 'BaseCoordCount', 0, 'Array of offsets to BaseCoord-from beginning of BaseValues table-order matches BaselineTag array in the BaseTagList'), - ]), - - ('MinMax', [ - ('Offset', 'MinCoord', None, None, 'Offset to BaseCoord table-defines minimum extent value-from the beginning of MinMax table-may be NULL'), - ('Offset', 'MaxCoord', None, None, 'Offset to BaseCoord table-defines maximum extent value-from the beginning of MinMax table-may be NULL'), - ('uint16', 'FeatMinMaxCount', None, None, 'Number of FeatMinMaxRecords-may be zero (0)'), - ('struct', 'FeatMinMaxRecord', 'FeatMinMaxCount', 0, 'Array of FeatMinMaxRecords-in alphabetical order, by FeatureTableTag'), - ]), - - ('FeatMinMaxRecord', [ - ('Tag', 'FeatureTableTag', None, None, '4-byte feature identification tag-must match FeatureTag in FeatureList'), - ('Offset', 'MinCoord', None, None, 'Offset to BaseCoord table-defines minimum extent value-from beginning of MinMax table-may be NULL'), - ('Offset', 'MaxCoord', None, None, 'Offset to BaseCoord table-defines maximum extent value-from beginning of MinMax table-may be NULL'), - ]), - - ('BaseCoordFormat1', [ - ('uint16', 'BaseCoordFormat', None, None, 'Format identifier-format = 1'), - ('int16', 'Coordinate', None, None, 'X or Y value, in design units'), - ]), - - ('BaseCoordFormat2', [ - ('uint16', 'BaseCoordFormat', None, None, 'Format identifier-format = 2'), - ('int16', 'Coordinate', None, None, 'X or Y value, in design units'), - ('GlyphID', 'ReferenceGlyph', None, None, 'GlyphID of control glyph'), - ('uint16', 'BaseCoordPoint', None, None, 'Index of contour point on the ReferenceGlyph'), - ]), - - ('BaseCoordFormat3', [ - ('uint16', 'BaseCoordFormat', None, None, 'Format identifier-format = 3'), - ('int16', 'Coordinate', None, None, 'X or Y value, in design units'), - ('Offset', 'DeviceTable', None, None, 'Offset to Device table for X or Y value'), - ]), - - - # - # jstf - # - - ('JSTF', [ - ('Version', 'Version', None, None, 'Version of the JSTF table-initially set to 0x00010000'), - ('uint16', 'JstfScriptCount', None, None, 'Number of JstfScriptRecords in this table'), - ('struct', 'JstfScriptRecord', 'JstfScriptCount', 0, 'Array of JstfScriptRecords-in alphabetical order, by JstfScriptTag'), - ]), - - ('JstfScriptRecord', [ - ('Tag', 'JstfScriptTag', None, None, '4-byte JstfScript identification'), - ('Offset', 'JstfScript', None, None, 'Offset to JstfScript table-from beginning of JSTF Header'), - ]), - - ('JstfScript', [ - ('Offset', 'ExtenderGlyph', None, None, 'Offset to ExtenderGlyph table-from beginning of JstfScript table-may be NULL'), - ('Offset', 'DefJstfLangSys', None, None, 'Offset to Default JstfLangSys table-from beginning of JstfScript table-may be NULL'), - ('uint16', 'JstfLangSysCount', None, None, 'Number of JstfLangSysRecords in this table- may be zero (0)'), - ('struct', 'JstfLangSysRecord', 'JstfLangSysCount', 0, 'Array of JstfLangSysRecords-in alphabetical order, by JstfLangSysTag'), - ]), - - ('JstfLangSysRecord', [ - ('Tag', 'JstfLangSysTag', None, None, '4-byte JstfLangSys identifier'), - ('Offset', 'JstfLangSys', None, None, 'Offset to JstfLangSys table-from beginning of JstfScript table'), - ]), - - ('ExtenderGlyph', [ - ('uint16', 'GlyphCount', None, None, 'Number of Extender Glyphs in this script'), - ('GlyphID', 'ExtenderGlyph', 'GlyphCount', 0, 'GlyphIDs-in increasing numerical order'), - ]), - - ('JstfLangSys', [ - ('uint16', 'JstfPriorityCount', None, None, 'Number of JstfPriority tables'), - ('Offset', 'JstfPriority', 'JstfPriorityCount', 0, 'Array of offsets to JstfPriority tables-from beginning of JstfLangSys table-in priority order'), - ]), - - ('JstfPriority', [ - ('Offset', 'ShrinkageEnableGSUB', None, None, 'Offset to Shrinkage Enable JstfGSUBModList table-from beginning of JstfPriority table-may be NULL'), - ('Offset', 'ShrinkageDisableGSUB', None, None, 'Offset to Shrinkage Disable JstfGSUBModList table-from beginning of JstfPriority table-may be NULL'), - ('Offset', 'ShrinkageEnableGPOS', None, None, 'Offset to Shrinkage Enable JstfGPOSModList table-from beginning of JstfPriority table-may be NULL'), - ('Offset', 'ShrinkageDisableGPOS', None, None, 'Offset to Shrinkage Disable JstfGPOSModList table-from beginning of JstfPriority table-may be NULL'), - ('Offset', 'ShrinkageJstfMax', None, None, 'Offset to Shrinkage JstfMax table-from beginning of JstfPriority table -may be NULL'), - ('Offset', 'ExtensionEnableGSUB', None, None, 'Offset to Extension Enable JstfGSUBModList table-may be NULL'), - ('Offset', 'ExtensionDisableGSUB', None, None, 'Offset to Extension Disable JstfGSUBModList table-from beginning of JstfPriority table-may be NULL'), - ('Offset', 'ExtensionEnableGPOS', None, None, 'Offset to Extension Enable JstfGSUBModList table-may be NULL'), - ('Offset', 'ExtensionDisableGPOS', None, None, 'Offset to Extension Disable JstfGSUBModList table-from beginning of JstfPriority table-may be NULL'), - ('Offset', 'ExtensionJstfMax', None, None, 'Offset to Extension JstfMax table-from beginning of JstfPriority table -may be NULL'), - ]), - - ('JstfGSUBModList', [ - ('uint16', 'LookupCount', None, None, 'Number of lookups for this modification'), - ('uint16', 'GSUBLookupIndex', 'LookupCount', 0, 'Array of LookupIndex identifiers in GSUB-in increasing numerical order'), - ]), - - ('JstfGPOSModList', [ - ('uint16', 'LookupCount', None, None, 'Number of lookups for this modification'), - ('uint16', 'GPOSLookupIndex', 'LookupCount', 0, 'Array of LookupIndex identifiers in GPOS-in increasing numerical order'), - ]), - - ('JstfMax', [ - ('uint16', 'LookupCount', None, None, 'Number of lookup Indices for this modification'), - ('Offset', 'Lookup', 'LookupCount', 0, 'Array of offsets to GPOS-type lookup tables-from beginning of JstfMax table-in design order'), - ]), - - - # - # STAT - # - ('STAT', [ - ('Version', 'Version', None, None, 'Version of the table-initially set to 0x00010000, currently 0x00010002.'), - ('uint16', 'DesignAxisRecordSize', None, None, 'Size in bytes of each design axis record'), - ('uint16', 'DesignAxisCount', None, None, 'Number of design axis records'), - ('LOffsetTo(AxisRecordArray)', 'DesignAxisRecord', None, None, 'Offset in bytes from the beginning of the STAT table to the start of the design axes array'), - ('uint16', 'AxisValueCount', None, None, 'Number of axis value tables'), - ('LOffsetTo(AxisValueArray)', 'AxisValueArray', None, None, 'Offset in bytes from the beginning of the STAT table to the start of the axes value offset array'), - ('NameID', 'ElidedFallbackNameID', None, 'Version >= 0x00010001', 'NameID to use when all style attributes are elided.'), - ]), - - ('AxisRecordArray', [ - ('AxisRecord', 'Axis', 'DesignAxisCount', 0, 'Axis records'), - ]), - - ('AxisRecord', [ - ('Tag', 'AxisTag', None, None, 'A tag identifying the axis of design variation'), - ('NameID', 'AxisNameID', None, None, 'The name ID for entries in the "name" table that provide a display string for this axis'), - ('uint16', 'AxisOrdering', None, None, 'A value that applications can use to determine primary sorting of face names, or for ordering of descriptors when composing family or face names'), - ('uint8', 'MoreBytes', 'DesignAxisRecordSize', -8, 'Extra bytes. Set to empty array.'), - ]), - - ('AxisValueArray', [ - ('Offset', 'AxisValue', 'AxisValueCount', 0, 'Axis values'), - ]), - - ('AxisValueFormat1', [ - ('uint16', 'Format', None, None, 'Format, = 1'), - ('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'), - ('STATFlags', 'Flags', None, None, 'Flags.'), - ('NameID', 'ValueNameID', None, None, ''), - ('Fixed', 'Value', None, None, ''), - ]), - - ('AxisValueFormat2', [ - ('uint16', 'Format', None, None, 'Format, = 2'), - ('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'), - ('STATFlags', 'Flags', None, None, 'Flags.'), - ('NameID', 'ValueNameID', None, None, ''), - ('Fixed', 'NominalValue', None, None, ''), - ('Fixed', 'RangeMinValue', None, None, ''), - ('Fixed', 'RangeMaxValue', None, None, ''), - ]), - - ('AxisValueFormat3', [ - ('uint16', 'Format', None, None, 'Format, = 3'), - ('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'), - ('STATFlags', 'Flags', None, None, 'Flags.'), - ('NameID', 'ValueNameID', None, None, ''), - ('Fixed', 'Value', None, None, ''), - ('Fixed', 'LinkedValue', None, None, ''), - ]), - - ('AxisValueFormat4', [ - ('uint16', 'Format', None, None, 'Format, = 4'), - ('uint16', 'AxisCount', None, None, 'The total number of axes contributing to this axis-values combination.'), - ('STATFlags', 'Flags', None, None, 'Flags.'), - ('NameID', 'ValueNameID', None, None, ''), - ('struct', 'AxisValueRecord', 'AxisCount', 0, 'Array of AxisValue records that provide the combination of axis values, one for each contributing axis. '), - ]), - - ('AxisValueRecord', [ - ('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'), - ('Fixed', 'Value', None, None, 'A numeric value for this attribute value.'), - ]), - - - # - # Variation fonts - # - - # GSUB/GPOS FeatureVariations - - ('FeatureVariations', [ - ('Version', 'Version', None, None, 'Version of the table-initially set to 0x00010000'), - ('uint32', 'FeatureVariationCount', None, None, 'Number of records in the FeatureVariationRecord array'), - ('struct', 'FeatureVariationRecord', 'FeatureVariationCount', 0, 'Array of FeatureVariationRecord'), - ]), - - ('FeatureVariationRecord', [ - ('LOffset', 'ConditionSet', None, None, 'Offset to a ConditionSet table, from beginning of the FeatureVariations table.'), - ('LOffset', 'FeatureTableSubstitution', None, None, 'Offset to a FeatureTableSubstitution table, from beginning of the FeatureVariations table'), - ]), - - ('ConditionSet', [ - ('uint16', 'ConditionCount', None, None, 'Number of condition tables in the ConditionTable array'), - ('LOffset', 'ConditionTable', 'ConditionCount', 0, 'Array of condition tables.'), - ]), - - ('ConditionTableFormat1', [ - ('uint16', 'Format', None, None, 'Format, = 1'), - ('uint16', 'AxisIndex', None, None, 'Index for the variation axis within the fvar table, base 0.'), - ('F2Dot14', 'FilterRangeMinValue', None, None, 'Minimum normalized axis value of the font variation instances that satisfy this condition.'), - ('F2Dot14', 'FilterRangeMaxValue', None, None, 'Maximum value that satisfies this condition.'), - ]), - - ('FeatureTableSubstitution', [ - ('Version', 'Version', None, None, 'Version of the table-initially set to 0x00010000'), - ('uint16', 'SubstitutionCount', None, None, 'Number of records in the FeatureVariationRecords array'), - ('FeatureTableSubstitutionRecord', 'SubstitutionRecord', 'SubstitutionCount', 0, 'Array of FeatureTableSubstitutionRecord'), - ]), - - ('FeatureTableSubstitutionRecord', [ - ('uint16', 'FeatureIndex', None, None, 'The feature table index to match.'), - ('LOffset', 'Feature', None, None, 'Offset to an alternate feature table, from start of the FeatureTableSubstitution table.'), - ]), - - # VariationStore - - ('VarRegionAxis', [ - ('F2Dot14', 'StartCoord', None, None, ''), - ('F2Dot14', 'PeakCoord', None, None, ''), - ('F2Dot14', 'EndCoord', None, None, ''), - ]), - - ('VarRegion', [ - ('struct', 'VarRegionAxis', 'RegionAxisCount', 0, ''), - ]), - - ('VarRegionList', [ - ('uint16', 'RegionAxisCount', None, None, ''), - ('uint16', 'RegionCount', None, None, ''), - ('VarRegion', 'Region', 'RegionCount', 0, ''), - ]), - - ('VarData', [ - ('uint16', 'ItemCount', None, None, ''), - ('uint16', 'NumShorts', None, None, ''), - ('uint16', 'VarRegionCount', None, None, ''), - ('uint16', 'VarRegionIndex', 'VarRegionCount', 0, ''), - ('VarDataValue', 'Item', 'ItemCount', 0, ''), - ]), - - ('VarStore', [ - ('uint16', 'Format', None, None, 'Set to 1.'), - ('LOffset', 'VarRegionList', None, None, ''), - ('uint16', 'VarDataCount', None, None, ''), - ('LOffset', 'VarData', 'VarDataCount', 0, ''), - ]), - - # Variation helpers - - ('VarIdxMap', [ - ('uint16', 'EntryFormat', None, None, ''), # Automatically computed - ('uint16', 'MappingCount', None, None, ''), # Automatically computed - ('VarIdxMapValue', 'mapping', '', 0, 'Array of compressed data'), - ]), - - ('DeltaSetIndexMapFormat0', [ - ('uint8', 'Format', None, None, 'Format of the DeltaSetIndexMap = 0'), - ('uint8', 'EntryFormat', None, None, ''), # Automatically computed - ('uint16', 'MappingCount', None, None, ''), # Automatically computed - ('VarIdxMapValue', 'mapping', '', 0, 'Array of compressed data'), - ]), - - ('DeltaSetIndexMapFormat1', [ - ('uint8', 'Format', None, None, 'Format of the DeltaSetIndexMap = 1'), - ('uint8', 'EntryFormat', None, None, ''), # Automatically computed - ('uint32', 'MappingCount', None, None, ''), # Automatically computed - ('VarIdxMapValue', 'mapping', '', 0, 'Array of compressed data'), - ]), - - # Glyph advance variations - - ('HVAR', [ - ('Version', 'Version', None, None, 'Version of the HVAR table-initially = 0x00010000'), - ('LOffset', 'VarStore', None, None, ''), - ('LOffsetTo(VarIdxMap)', 'AdvWidthMap', None, None, ''), - ('LOffsetTo(VarIdxMap)', 'LsbMap', None, None, ''), - ('LOffsetTo(VarIdxMap)', 'RsbMap', None, None, ''), - ]), - ('VVAR', [ - ('Version', 'Version', None, None, 'Version of the VVAR table-initially = 0x00010000'), - ('LOffset', 'VarStore', None, None, ''), - ('LOffsetTo(VarIdxMap)', 'AdvHeightMap', None, None, ''), - ('LOffsetTo(VarIdxMap)', 'TsbMap', None, None, ''), - ('LOffsetTo(VarIdxMap)', 'BsbMap', None, None, ''), - ('LOffsetTo(VarIdxMap)', 'VOrgMap', None, None, 'Vertical origin mapping.'), - ]), - - # Font-wide metrics variations - - ('MetricsValueRecord', [ - ('Tag', 'ValueTag', None, None, '4-byte font-wide measure identifier'), - ('uint32', 'VarIdx', None, None, 'Combined outer-inner variation index'), - ('uint8', 'MoreBytes', 'ValueRecordSize', -8, 'Extra bytes. Set to empty array.'), - ]), - - ('MVAR', [ - ('Version', 'Version', None, None, 'Version of the MVAR table-initially = 0x00010000'), - ('uint16', 'Reserved', None, None, 'Set to 0'), - ('uint16', 'ValueRecordSize', None, None, ''), - ('uint16', 'ValueRecordCount', None, None, ''), - ('Offset', 'VarStore', None, None, ''), - ('MetricsValueRecord', 'ValueRecord', 'ValueRecordCount', 0, ''), - ]), - - - # - # math - # - - ('MATH', [ - ('Version', 'Version', None, None, 'Version of the MATH table-initially set to 0x00010000.'), - ('Offset', 'MathConstants', None, None, 'Offset to MathConstants table - from the beginning of MATH table.'), - ('Offset', 'MathGlyphInfo', None, None, 'Offset to MathGlyphInfo table - from the beginning of MATH table.'), - ('Offset', 'MathVariants', None, None, 'Offset to MathVariants table - from the beginning of MATH table.'), - ]), - - ('MathValueRecord', [ - ('int16', 'Value', None, None, 'The X or Y value in design units.'), - ('Offset', 'DeviceTable', None, None, 'Offset to the device table - from the beginning of parent table. May be NULL. Suggested format for device table is 1.'), - ]), - - ('MathConstants', [ - ('int16', 'ScriptPercentScaleDown', None, None, 'Percentage of scaling down for script level 1. Suggested value: 80%.'), - ('int16', 'ScriptScriptPercentScaleDown', None, None, 'Percentage of scaling down for script level 2 (ScriptScript). Suggested value: 60%.'), - ('uint16', 'DelimitedSubFormulaMinHeight', None, None, 'Minimum height required for a delimited expression to be treated as a subformula. Suggested value: normal line height x1.5.'), - ('uint16', 'DisplayOperatorMinHeight', None, None, 'Minimum height of n-ary operators (such as integral and summation) for formulas in display mode.'), - ('MathValueRecord', 'MathLeading', None, None, 'White space to be left between math formulas to ensure proper line spacing. For example, for applications that treat line gap as a part of line ascender, formulas with ink going above (os2.sTypoAscender + os2.sTypoLineGap - MathLeading) or with ink going below os2.sTypoDescender will result in increasing line height.'), - ('MathValueRecord', 'AxisHeight', None, None, 'Axis height of the font.'), - ('MathValueRecord', 'AccentBaseHeight', None, None, 'Maximum (ink) height of accent base that does not require raising the accents. Suggested: x-height of the font (os2.sxHeight) plus any possible overshots.'), - ('MathValueRecord', 'FlattenedAccentBaseHeight', None, None, 'Maximum (ink) height of accent base that does not require flattening the accents. Suggested: cap height of the font (os2.sCapHeight).'), - ('MathValueRecord', 'SubscriptShiftDown', None, None, 'The standard shift down applied to subscript elements. Positive for moving in the downward direction. Suggested: os2.ySubscriptYOffset.'), - ('MathValueRecord', 'SubscriptTopMax', None, None, 'Maximum allowed height of the (ink) top of subscripts that does not require moving subscripts further down. Suggested: 4/5 x-height.'), - ('MathValueRecord', 'SubscriptBaselineDropMin', None, None, 'Minimum allowed drop of the baseline of subscripts relative to the (ink) bottom of the base. Checked for bases that are treated as a box or extended shape. Positive for subscript baseline dropped below the base bottom.'), - ('MathValueRecord', 'SuperscriptShiftUp', None, None, 'Standard shift up applied to superscript elements. Suggested: os2.ySuperscriptYOffset.'), - ('MathValueRecord', 'SuperscriptShiftUpCramped', None, None, 'Standard shift of superscripts relative to the base, in cramped style.'), - ('MathValueRecord', 'SuperscriptBottomMin', None, None, 'Minimum allowed height of the (ink) bottom of superscripts that does not require moving subscripts further up. Suggested: 1/4 x-height.'), - ('MathValueRecord', 'SuperscriptBaselineDropMax', None, None, 'Maximum allowed drop of the baseline of superscripts relative to the (ink) top of the base. Checked for bases that are treated as a box or extended shape. Positive for superscript baseline below the base top.'), - ('MathValueRecord', 'SubSuperscriptGapMin', None, None, 'Minimum gap between the superscript and subscript ink. Suggested: 4x default rule thickness.'), - ('MathValueRecord', 'SuperscriptBottomMaxWithSubscript', None, None, 'The maximum level to which the (ink) bottom of superscript can be pushed to increase the gap between superscript and subscript, before subscript starts being moved down. Suggested: 4/5 x-height.'), - ('MathValueRecord', 'SpaceAfterScript', None, None, 'Extra white space to be added after each subscript and superscript. Suggested: 0.5pt for a 12 pt font.'), - ('MathValueRecord', 'UpperLimitGapMin', None, None, 'Minimum gap between the (ink) bottom of the upper limit, and the (ink) top of the base operator.'), - ('MathValueRecord', 'UpperLimitBaselineRiseMin', None, None, 'Minimum distance between baseline of upper limit and (ink) top of the base operator.'), - ('MathValueRecord', 'LowerLimitGapMin', None, None, 'Minimum gap between (ink) top of the lower limit, and (ink) bottom of the base operator.'), - ('MathValueRecord', 'LowerLimitBaselineDropMin', None, None, 'Minimum distance between baseline of the lower limit and (ink) bottom of the base operator.'), - ('MathValueRecord', 'StackTopShiftUp', None, None, 'Standard shift up applied to the top element of a stack.'), - ('MathValueRecord', 'StackTopDisplayStyleShiftUp', None, None, 'Standard shift up applied to the top element of a stack in display style.'), - ('MathValueRecord', 'StackBottomShiftDown', None, None, 'Standard shift down applied to the bottom element of a stack. Positive for moving in the downward direction.'), - ('MathValueRecord', 'StackBottomDisplayStyleShiftDown', None, None, 'Standard shift down applied to the bottom element of a stack in display style. Positive for moving in the downward direction.'), - ('MathValueRecord', 'StackGapMin', None, None, 'Minimum gap between (ink) bottom of the top element of a stack, and the (ink) top of the bottom element. Suggested: 3x default rule thickness.'), - ('MathValueRecord', 'StackDisplayStyleGapMin', None, None, 'Minimum gap between (ink) bottom of the top element of a stack, and the (ink) top of the bottom element in display style. Suggested: 7x default rule thickness.'), - ('MathValueRecord', 'StretchStackTopShiftUp', None, None, 'Standard shift up applied to the top element of the stretch stack.'), - ('MathValueRecord', 'StretchStackBottomShiftDown', None, None, 'Standard shift down applied to the bottom element of the stretch stack. Positive for moving in the downward direction.'), - ('MathValueRecord', 'StretchStackGapAboveMin', None, None, 'Minimum gap between the ink of the stretched element, and the (ink) bottom of the element above. Suggested: UpperLimitGapMin'), - ('MathValueRecord', 'StretchStackGapBelowMin', None, None, 'Minimum gap between the ink of the stretched element, and the (ink) top of the element below. Suggested: LowerLimitGapMin.'), - ('MathValueRecord', 'FractionNumeratorShiftUp', None, None, 'Standard shift up applied to the numerator.'), - ('MathValueRecord', 'FractionNumeratorDisplayStyleShiftUp', None, None, 'Standard shift up applied to the numerator in display style. Suggested: StackTopDisplayStyleShiftUp.'), - ('MathValueRecord', 'FractionDenominatorShiftDown', None, None, 'Standard shift down applied to the denominator. Positive for moving in the downward direction.'), - ('MathValueRecord', 'FractionDenominatorDisplayStyleShiftDown', None, None, 'Standard shift down applied to the denominator in display style. Positive for moving in the downward direction. Suggested: StackBottomDisplayStyleShiftDown.'), - ('MathValueRecord', 'FractionNumeratorGapMin', None, None, 'Minimum tolerated gap between the (ink) bottom of the numerator and the ink of the fraction bar. Suggested: default rule thickness'), - ('MathValueRecord', 'FractionNumDisplayStyleGapMin', None, None, 'Minimum tolerated gap between the (ink) bottom of the numerator and the ink of the fraction bar in display style. Suggested: 3x default rule thickness.'), - ('MathValueRecord', 'FractionRuleThickness', None, None, 'Thickness of the fraction bar. Suggested: default rule thickness.'), - ('MathValueRecord', 'FractionDenominatorGapMin', None, None, 'Minimum tolerated gap between the (ink) top of the denominator and the ink of the fraction bar. Suggested: default rule thickness'), - ('MathValueRecord', 'FractionDenomDisplayStyleGapMin', None, None, 'Minimum tolerated gap between the (ink) top of the denominator and the ink of the fraction bar in display style. Suggested: 3x default rule thickness.'), - ('MathValueRecord', 'SkewedFractionHorizontalGap', None, None, 'Horizontal distance between the top and bottom elements of a skewed fraction.'), - ('MathValueRecord', 'SkewedFractionVerticalGap', None, None, 'Vertical distance between the ink of the top and bottom elements of a skewed fraction.'), - ('MathValueRecord', 'OverbarVerticalGap', None, None, 'Distance between the overbar and the (ink) top of he base. Suggested: 3x default rule thickness.'), - ('MathValueRecord', 'OverbarRuleThickness', None, None, 'Thickness of overbar. Suggested: default rule thickness.'), - ('MathValueRecord', 'OverbarExtraAscender', None, None, 'Extra white space reserved above the overbar. Suggested: default rule thickness.'), - ('MathValueRecord', 'UnderbarVerticalGap', None, None, 'Distance between underbar and (ink) bottom of the base. Suggested: 3x default rule thickness.'), - ('MathValueRecord', 'UnderbarRuleThickness', None, None, 'Thickness of underbar. Suggested: default rule thickness.'), - ('MathValueRecord', 'UnderbarExtraDescender', None, None, 'Extra white space reserved below the underbar. Always positive. Suggested: default rule thickness.'), - ('MathValueRecord', 'RadicalVerticalGap', None, None, 'Space between the (ink) top of the expression and the bar over it. Suggested: 1 1/4 default rule thickness.'), - ('MathValueRecord', 'RadicalDisplayStyleVerticalGap', None, None, 'Space between the (ink) top of the expression and the bar over it. Suggested: default rule thickness + 1/4 x-height.'), - ('MathValueRecord', 'RadicalRuleThickness', None, None, 'Thickness of the radical rule. This is the thickness of the rule in designed or constructed radical signs. Suggested: default rule thickness.'), - ('MathValueRecord', 'RadicalExtraAscender', None, None, 'Extra white space reserved above the radical. Suggested: RadicalRuleThickness.'), - ('MathValueRecord', 'RadicalKernBeforeDegree', None, None, 'Extra horizontal kern before the degree of a radical, if such is present. Suggested: 5/18 of em.'), - ('MathValueRecord', 'RadicalKernAfterDegree', None, None, 'Negative kern after the degree of a radical, if such is present. Suggested: 10/18 of em.'), - ('uint16', 'RadicalDegreeBottomRaisePercent', None, None, 'Height of the bottom of the radical degree, if such is present, in proportion to the ascender of the radical sign. Suggested: 60%.'), - ]), - - ('MathGlyphInfo', [ - ('Offset', 'MathItalicsCorrectionInfo', None, None, 'Offset to MathItalicsCorrectionInfo table - from the beginning of MathGlyphInfo table.'), - ('Offset', 'MathTopAccentAttachment', None, None, 'Offset to MathTopAccentAttachment table - from the beginning of MathGlyphInfo table.'), - ('Offset', 'ExtendedShapeCoverage', None, None, 'Offset to coverage table for Extended Shape glyphs - from the beginning of MathGlyphInfo table. When the left or right glyph of a box is an extended shape variant, the (ink) box (and not the default position defined by values in MathConstants table) should be used for vertical positioning purposes. May be NULL.'), - ('Offset', 'MathKernInfo', None, None, 'Offset to MathKernInfo table - from the beginning of MathGlyphInfo table.'), - ]), - - ('MathItalicsCorrectionInfo', [ - ('Offset', 'Coverage', None, None, 'Offset to Coverage table - from the beginning of MathItalicsCorrectionInfo table.'), - ('uint16', 'ItalicsCorrectionCount', None, None, 'Number of italics correction values. Should coincide with the number of covered glyphs.'), - ('MathValueRecord', 'ItalicsCorrection', 'ItalicsCorrectionCount', 0, 'Array of MathValueRecords defining italics correction values for each covered glyph.'), - ]), - - ('MathTopAccentAttachment', [ - ('Offset', 'TopAccentCoverage', None, None, 'Offset to Coverage table - from the beginning of MathTopAccentAttachment table.'), - ('uint16', 'TopAccentAttachmentCount', None, None, 'Number of top accent attachment point values. Should coincide with the number of covered glyphs'), - ('MathValueRecord', 'TopAccentAttachment', 'TopAccentAttachmentCount', 0, 'Array of MathValueRecords defining top accent attachment points for each covered glyph'), - ]), - - ('MathKernInfo', [ - ('Offset', 'MathKernCoverage', None, None, 'Offset to Coverage table - from the beginning of the MathKernInfo table.'), - ('uint16', 'MathKernCount', None, None, 'Number of MathKernInfoRecords.'), - ('MathKernInfoRecord', 'MathKernInfoRecords', 'MathKernCount', 0, 'Array of MathKernInfoRecords, per-glyph information for mathematical positioning of subscripts and superscripts.'), - ]), - - ('MathKernInfoRecord', [ - ('Offset', 'TopRightMathKern', None, None, 'Offset to MathKern table for top right corner - from the beginning of MathKernInfo table. May be NULL.'), - ('Offset', 'TopLeftMathKern', None, None, 'Offset to MathKern table for the top left corner - from the beginning of MathKernInfo table. May be NULL.'), - ('Offset', 'BottomRightMathKern', None, None, 'Offset to MathKern table for bottom right corner - from the beginning of MathKernInfo table. May be NULL.'), - ('Offset', 'BottomLeftMathKern', None, None, 'Offset to MathKern table for bottom left corner - from the beginning of MathKernInfo table. May be NULL.'), - ]), - - ('MathKern', [ - ('uint16', 'HeightCount', None, None, 'Number of heights on which the kern value changes.'), - ('MathValueRecord', 'CorrectionHeight', 'HeightCount', 0, 'Array of correction heights at which the kern value changes. Sorted by the height value in design units.'), - ('MathValueRecord', 'KernValue', 'HeightCount', 1, 'Array of kern values corresponding to heights. First value is the kern value for all heights less or equal than the first height in this table.Last value is the value to be applied for all heights greater than the last height in this table. Negative values are interpreted as move glyphs closer to each other.'), - ]), - - ('MathVariants', [ - ('uint16', 'MinConnectorOverlap', None, None, 'Minimum overlap of connecting glyphs during glyph construction, in design units.'), - ('Offset', 'VertGlyphCoverage', None, None, 'Offset to Coverage table - from the beginning of MathVariants table.'), - ('Offset', 'HorizGlyphCoverage', None, None, 'Offset to Coverage table - from the beginning of MathVariants table.'), - ('uint16', 'VertGlyphCount', None, None, 'Number of glyphs for which information is provided for vertically growing variants.'), - ('uint16', 'HorizGlyphCount', None, None, 'Number of glyphs for which information is provided for horizontally growing variants.'), - ('Offset', 'VertGlyphConstruction', 'VertGlyphCount', 0, 'Array of offsets to MathGlyphConstruction tables - from the beginning of the MathVariants table, for shapes growing in vertical direction.'), - ('Offset', 'HorizGlyphConstruction', 'HorizGlyphCount', 0, 'Array of offsets to MathGlyphConstruction tables - from the beginning of the MathVariants table, for shapes growing in horizontal direction.'), - ]), - - ('MathGlyphConstruction', [ - ('Offset', 'GlyphAssembly', None, None, 'Offset to GlyphAssembly table for this shape - from the beginning of MathGlyphConstruction table. May be NULL'), - ('uint16', 'VariantCount', None, None, 'Count of glyph growing variants for this glyph.'), - ('MathGlyphVariantRecord', 'MathGlyphVariantRecord', 'VariantCount', 0, 'MathGlyphVariantRecords for alternative variants of the glyphs.'), - ]), - - ('MathGlyphVariantRecord', [ - ('GlyphID', 'VariantGlyph', None, None, 'Glyph ID for the variant.'), - ('uint16', 'AdvanceMeasurement', None, None, 'Advance width/height, in design units, of the variant, in the direction of requested glyph extension.'), - ]), - - ('GlyphAssembly', [ - ('MathValueRecord', 'ItalicsCorrection', None, None, 'Italics correction of this GlyphAssembly. Should not depend on the assembly size.'), - ('uint16', 'PartCount', None, None, 'Number of parts in this assembly.'), - ('GlyphPartRecord', 'PartRecords', 'PartCount', 0, 'Array of part records, from left to right and bottom to top.'), - ]), - - ('GlyphPartRecord', [ - ('GlyphID', 'glyph', None, None, 'Glyph ID for the part.'), - ('uint16', 'StartConnectorLength', None, None, 'Advance width/ height of the straight bar connector material, in design units, is at the beginning of the glyph, in the direction of the extension.'), - ('uint16', 'EndConnectorLength', None, None, 'Advance width/ height of the straight bar connector material, in design units, is at the end of the glyph, in the direction of the extension.'), - ('uint16', 'FullAdvance', None, None, 'Full advance width/height for this part, in the direction of the extension. In design units.'), - ('uint16', 'PartFlags', None, None, 'Part qualifiers. PartFlags enumeration currently uses only one bit: 0x0001 fExtender: If set, the part can be skipped or repeated. 0xFFFE Reserved'), - ]), - - - ## - ## Apple Advanced Typography (AAT) tables - ## - - ('AATLookupSegment', [ - ('uint16', 'lastGlyph', None, None, 'Last glyph index in this segment.'), - ('uint16', 'firstGlyph', None, None, 'First glyph index in this segment.'), - ('uint16', 'value', None, None, 'A 16-bit offset from the start of the table to the data.'), - ]), - - - # - # ankr - # - - ('ankr', [ - ('struct', 'AnchorPoints', None, None, 'Anchor points table.'), - ]), - - ('AnchorPointsFormat0', [ - ('uint16', 'Format', None, None, 'Format of the anchor points table, = 0.'), - ('uint16', 'Flags', None, None, 'Flags. Currenty unused, set to zero.'), - ('AATLookupWithDataOffset(AnchorGlyphData)', 'Anchors', None, None, 'Table of with anchor overrides for each glyph.'), - ]), - - ('AnchorGlyphData', [ - ('uint32', 'AnchorPointCount', None, None, 'Number of anchor points for this glyph.'), - ('struct', 'AnchorPoint', 'AnchorPointCount', 0, 'Individual anchor points.'), - ]), - - ('AnchorPoint', [ - ('int16', 'XCoordinate', None, None, 'X coordinate of this anchor point.'), - ('int16', 'YCoordinate', None, None, 'Y coordinate of this anchor point.'), - ]), - - # - # bsln - # - - ('bsln', [ - ('Version', 'Version', None, None, 'Version number of the AAT baseline table (0x00010000 for the initial version).'), - ('struct', 'Baseline', None, None, 'Baseline table.'), - ]), - - ('BaselineFormat0', [ - ('uint16', 'Format', None, None, 'Format of the baseline table, = 0.'), - ('uint16', 'DefaultBaseline', None, None, 'Default baseline value for all glyphs. This value can be from 0 through 31.'), - ('uint16', 'Delta', 32, 0, u'These are the FUnit distance deltas from the font’s natural baseline to the other baselines used in the font. A total of 32 deltas must be assigned.'), - ]), - - ('BaselineFormat1', [ - ('uint16', 'Format', None, None, 'Format of the baseline table, = 1.'), - ('uint16', 'DefaultBaseline', None, None, 'Default baseline value for all glyphs. This value can be from 0 through 31.'), - ('uint16', 'Delta', 32, 0, u'These are the FUnit distance deltas from the font’s natural baseline to the other baselines used in the font. A total of 32 deltas must be assigned.'), - ('AATLookup(uint16)', 'BaselineValues', None, None, 'Lookup table that maps glyphs to their baseline values.'), - ]), - - ('BaselineFormat2', [ - ('uint16', 'Format', None, None, 'Format of the baseline table, = 1.'), - ('uint16', 'DefaultBaseline', None, None, 'Default baseline value for all glyphs. This value can be from 0 through 31.'), - ('GlyphID', 'StandardGlyph', None, None, 'Glyph index of the glyph in this font to be used to set the baseline values. This glyph must contain a set of control points (whose numbers are contained in the following field) that determines baseline distances.'), - ('uint16', 'ControlPoint', 32, 0, 'Array of 32 control point numbers, associated with the standard glyph. A value of 0xFFFF means there is no corresponding control point in the standard glyph.'), - ]), - - ('BaselineFormat3', [ - ('uint16', 'Format', None, None, 'Format of the baseline table, = 1.'), - ('uint16', 'DefaultBaseline', None, None, 'Default baseline value for all glyphs. This value can be from 0 through 31.'), - ('GlyphID', 'StandardGlyph', None, None, 'Glyph index of the glyph in this font to be used to set the baseline values. This glyph must contain a set of control points (whose numbers are contained in the following field) that determines baseline distances.'), - ('uint16', 'ControlPoint', 32, 0, 'Array of 32 control point numbers, associated with the standard glyph. A value of 0xFFFF means there is no corresponding control point in the standard glyph.'), - ('AATLookup(uint16)', 'BaselineValues', None, None, 'Lookup table that maps glyphs to their baseline values.'), - ]), - - - # - # cidg - # - - ('cidg', [ - ('struct', 'CIDGlyphMapping', None, None, 'CID-to-glyph mapping table.'), - ]), - - ('CIDGlyphMappingFormat0', [ - ('uint16', 'Format', None, None, 'Format of the CID-to-glyph mapping table, = 0.'), - ('uint16', 'DataFormat', None, None, 'Currenty unused, set to zero.'), - ('uint32', 'StructLength', None, None, 'Size of the table in bytes.'), - ('uint16', 'Registry', None, None, 'The registry ID.'), - ('char64', 'RegistryName', None, None, 'The registry name in ASCII; unused bytes should be set to 0.'), - ('uint16', 'Order', None, None, 'The order ID.'), - ('char64', 'OrderName', None, None, 'The order name in ASCII; unused bytes should be set to 0.'), - ('uint16', 'SupplementVersion', None, None, 'The supplement version.'), - ('CIDGlyphMap', 'Mapping', None, None, 'A mapping from CIDs to the glyphs in the font, starting with CID 0. If a CID from the identified collection has no glyph in the font, 0xFFFF is used'), - ]), - - - # - # feat - # - - ('feat', [ - ('Version', 'Version', None, None, 'Version of the feat table-initially set to 0x00010000.'), - ('FeatureNames', 'FeatureNames', None, None, 'The feature names.'), - ]), - - ('FeatureNames', [ - ('uint16', 'FeatureNameCount', None, None, 'Number of entries in the feature name array.'), - ('uint16', 'Reserved1', None, None, 'Reserved (set to zero).'), - ('uint32', 'Reserved2', None, None, 'Reserved (set to zero).'), - ('FeatureName', 'FeatureName', 'FeatureNameCount', 0, 'The feature name array.'), - ]), - - ('FeatureName', [ - ('uint16', 'FeatureType', None, None, 'Feature type.'), - ('uint16', 'SettingsCount', None, None, 'The number of records in the setting name array.'), - ('LOffset', 'Settings', None, None, 'Offset to setting table for this feature.'), - ('uint16', 'FeatureFlags', None, None, 'Single-bit flags associated with the feature type.'), - ('NameID', 'FeatureNameID', None, None, 'The name table index for the feature name.'), - ]), - - ('Settings', [ - ('Setting', 'Setting', 'SettingsCount', 0, 'The setting array.'), - ]), - - ('Setting', [ - ('uint16', 'SettingValue', None, None, 'The setting.'), - ('NameID', 'SettingNameID', None, None, 'The name table index for the setting name.'), - ]), - - - # - # gcid - # - - ('gcid', [ - ('struct', 'GlyphCIDMapping', None, None, 'Glyph to CID mapping table.'), - ]), - - ('GlyphCIDMappingFormat0', [ - ('uint16', 'Format', None, None, 'Format of the glyph-to-CID mapping table, = 0.'), - ('uint16', 'DataFormat', None, None, 'Currenty unused, set to zero.'), - ('uint32', 'StructLength', None, None, 'Size of the table in bytes.'), - ('uint16', 'Registry', None, None, 'The registry ID.'), - ('char64', 'RegistryName', None, None, 'The registry name in ASCII; unused bytes should be set to 0.'), - ('uint16', 'Order', None, None, 'The order ID.'), - ('char64', 'OrderName', None, None, 'The order name in ASCII; unused bytes should be set to 0.'), - ('uint16', 'SupplementVersion', None, None, 'The supplement version.'), - ('GlyphCIDMap', 'Mapping', None, None, 'The CIDs for the glyphs in the font, starting with glyph 0. If a glyph does not correspond to a CID in the identified collection, 0xFFFF is used'), - ]), - - - # - # lcar - # - - ('lcar', [ - ('Version', 'Version', None, None, 'Version number of the ligature caret table (0x00010000 for the initial version).'), - ('struct', 'LigatureCarets', None, None, 'Ligature carets table.'), - ]), - - ('LigatureCaretsFormat0', [ - ('uint16', 'Format', None, None, 'Format of the ligature caret table. Format 0 indicates division points are distances in font units, Format 1 indicates division points are indexes of control points.'), - ('AATLookup(LigCaretDistances)', 'Carets', None, None, 'Lookup table associating ligature glyphs with their caret positions, in font unit distances.'), - ]), - - ('LigatureCaretsFormat1', [ - ('uint16', 'Format', None, None, 'Format of the ligature caret table. Format 0 indicates division points are distances in font units, Format 1 indicates division points are indexes of control points.'), - ('AATLookup(LigCaretPoints)', 'Carets', None, None, 'Lookup table associating ligature glyphs with their caret positions, as control points.'), - ]), - - ('LigCaretDistances', [ - ('uint16', 'DivsionPointCount', None, None, 'Number of division points.'), - ('int16', 'DivisionPoint', 'DivsionPointCount', 0, 'Distance in font units through which a subdivision is made orthogonally to the baseline.'), - ]), - - ('LigCaretPoints', [ - ('uint16', 'DivsionPointCount', None, None, 'Number of division points.'), - ('int16', 'DivisionPoint', 'DivsionPointCount', 0, 'The number of the control point through which a subdivision is made orthogonally to the baseline.'), - ]), - - - # - # mort - # - - ('mort', [ - ('Version', 'Version', None, None, 'Version of the mort table.'), - ('uint32', 'MorphChainCount', None, None, 'Number of metamorphosis chains.'), - ('MortChain', 'MorphChain', 'MorphChainCount', 0, 'Array of metamorphosis chains.'), - ]), - - ('MortChain', [ - ('Flags32', 'DefaultFlags', None, None, 'The default specification for subtables.'), - ('uint32', 'StructLength', None, None, 'Total byte count, including this header; must be a multiple of 4.'), - ('uint16', 'MorphFeatureCount', None, None, 'Number of metamorphosis feature entries.'), - ('uint16', 'MorphSubtableCount', None, None, 'The number of subtables in the chain.'), - ('struct', 'MorphFeature', 'MorphFeatureCount', 0, 'Array of metamorphosis features.'), - ('MortSubtable', 'MorphSubtable', 'MorphSubtableCount', 0, 'Array of metamorphosis subtables.'), - ]), - - ('MortSubtable', [ - ('uint16', 'StructLength', None, None, 'Total subtable length, including this header.'), - ('uint8', 'CoverageFlags', None, None, 'Most significant byte of coverage flags.'), - ('uint8', 'MorphType', None, None, 'Subtable type.'), - ('Flags32', 'SubFeatureFlags', None, None, 'The 32-bit mask identifying which subtable this is (the subtable being executed if the AND of this value and the processed defaultFlags is nonzero).'), - ('SubStruct', 'SubStruct', None, None, 'SubTable.'), - ]), - - # - # morx - # - - ('morx', [ - ('uint16', 'Version', None, None, 'Version of the morx table.'), - ('uint16', 'Reserved', None, None, 'Reserved (set to zero).'), - ('uint32', 'MorphChainCount', None, None, 'Number of extended metamorphosis chains.'), - ('MorxChain', 'MorphChain', 'MorphChainCount', 0, 'Array of extended metamorphosis chains.'), - ]), - - ('MorxChain', [ - ('Flags32', 'DefaultFlags', None, None, 'The default specification for subtables.'), - ('uint32', 'StructLength', None, None, 'Total byte count, including this header; must be a multiple of 4.'), - ('uint32', 'MorphFeatureCount', None, None, 'Number of feature subtable entries.'), - ('uint32', 'MorphSubtableCount', None, None, 'The number of subtables in the chain.'), - ('MorphFeature', 'MorphFeature', 'MorphFeatureCount', 0, 'Array of metamorphosis features.'), - ('MorxSubtable', 'MorphSubtable', 'MorphSubtableCount', 0, 'Array of extended metamorphosis subtables.'), - ]), - - ('MorphFeature', [ - ('uint16', 'FeatureType', None, None, 'The type of feature.'), - ('uint16', 'FeatureSetting', None, None, "The feature's setting (aka selector)."), - ('Flags32', 'EnableFlags', None, None, 'Flags for the settings that this feature and setting enables.'), - ('Flags32', 'DisableFlags', None, None, 'Complement of flags for the settings that this feature and setting disable.'), - ]), - - # Apple TrueType Reference Manual, chapter “The ‘morx’ table”, - # section “Metamorphosis Subtables”. - # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html - ('MorxSubtable', [ - ('uint32', 'StructLength', None, None, 'Total subtable length, including this header.'), - ('uint8', 'CoverageFlags', None, None, 'Most significant byte of coverage flags.'), - ('uint16', 'Reserved', None, None, 'Unused.'), - ('uint8', 'MorphType', None, None, 'Subtable type.'), - ('Flags32', 'SubFeatureFlags', None, None, 'The 32-bit mask identifying which subtable this is (the subtable being executed if the AND of this value and the processed defaultFlags is nonzero).'), - ('SubStruct', 'SubStruct', None, None, 'SubTable.'), - ]), - - ('StateHeader', [ - ('uint32', 'ClassCount', None, None, 'Number of classes, which is the number of 16-bit entry indices in a single line in the state array.'), - ('uint32', 'MorphClass', None, None, 'Offset from the start of this state table header to the start of the class table.'), - ('uint32', 'StateArrayOffset', None, None, 'Offset from the start of this state table header to the start of the state array.'), - ('uint32', 'EntryTableOffset', None, None, 'Offset from the start of this state table header to the start of the entry table.'), - ]), - - ('RearrangementMorph', [ - ('STXHeader(RearrangementMorphAction)', 'StateTable', None, None, 'Finite-state transducer table for indic rearrangement.'), - ]), - - ('ContextualMorph', [ - ('STXHeader(ContextualMorphAction)', 'StateTable', None, None, 'Finite-state transducer for contextual glyph substitution.'), - ]), - - ('LigatureMorph', [ - ('STXHeader(LigatureMorphAction)', 'StateTable', None, None, 'Finite-state transducer for ligature substitution.'), - ]), - - ('NoncontextualMorph', [ - ('AATLookup(GlyphID)', 'Substitution', None, None, 'The noncontextual glyph substitution table.'), - ]), - - ('InsertionMorph', [ - ('STXHeader(InsertionMorphAction)', 'StateTable', None, None, 'Finite-state transducer for glyph insertion.'), - ]), - - ('MorphClass', [ - ('uint16', 'FirstGlyph', None, None, 'Glyph index of the first glyph in the class table.'), - #('uint16', 'GlyphCount', None, None, 'Number of glyphs in class table.'), - #('uint8', 'GlyphClass', 'GlyphCount', 0, 'The class codes (indexed by glyph index minus firstGlyph). Class codes range from 0 to the value of stateSize minus 1.'), - ]), - - # If the 'morx' table version is 3 or greater, then the last subtable in the chain is followed by a subtableGlyphCoverageArray, as described below. - # ('Offset', 'MarkGlyphSetsDef', None, 'round(Version*0x10000) >= 0x00010002', 'Offset to the table of mark set definitions-from beginning of GDEF header (may be NULL)'), - - - # - # prop - # - - ('prop', [ - ('Fixed', 'Version', None, None, 'Version number of the AAT glyphs property table. Version 1.0 is the initial table version. Version 2.0, which is recognized by macOS 8.5 and later, adds support for the “attaches on right” bit. Version 3.0, which gets recognized by macOS X and iOS, adds support for the additional directional properties defined in Unicode 3.0.'), - ('struct', 'GlyphProperties', None, None, 'Glyph properties.'), - ]), - - ('GlyphPropertiesFormat0', [ - ('uint16', 'Format', None, None, 'Format, = 0.'), - ('uint16', 'DefaultProperties', None, None, 'Default properties applied to a glyph. Since there is no lookup table in prop format 0, the default properties get applied to every glyph in the font.'), - ]), - - ('GlyphPropertiesFormat1', [ - ('uint16', 'Format', None, None, 'Format, = 1.'), - ('uint16', 'DefaultProperties', None, None, 'Default properties applied to a glyph if that glyph is not present in the Properties lookup table.'), - ('AATLookup(uint16)', 'Properties', None, None, 'Lookup data associating glyphs with their properties.'), - ]), - - - # - # opbd - # - - ('opbd', [ - ('Version', 'Version', None, None, 'Version number of the optical bounds table (0x00010000 for the initial version).'), - ('struct', 'OpticalBounds', None, None, 'Optical bounds table.'), - ]), - - ('OpticalBoundsFormat0', [ - ('uint16', 'Format', None, None, 'Format of the optical bounds table, = 0.'), - ('AATLookup(OpticalBoundsDeltas)', 'OpticalBoundsDeltas', None, None, 'Lookup table associating glyphs with their optical bounds, given as deltas in font units.'), - ]), - - ('OpticalBoundsFormat1', [ - ('uint16', 'Format', None, None, 'Format of the optical bounds table, = 1.'), - ('AATLookup(OpticalBoundsPoints)', 'OpticalBoundsPoints', None, None, 'Lookup table associating glyphs with their optical bounds, given as references to control points.'), - ]), - - ('OpticalBoundsDeltas', [ - ('int16', 'Left', None, None, 'Delta value for the left-side optical edge.'), - ('int16', 'Top', None, None, 'Delta value for the top-side optical edge.'), - ('int16', 'Right', None, None, 'Delta value for the right-side optical edge.'), - ('int16', 'Bottom', None, None, 'Delta value for the bottom-side optical edge.'), - ]), - - ('OpticalBoundsPoints', [ - ('int16', 'Left', None, None, 'Control point index for the left-side optical edge, or -1 if this glyph has none.'), - ('int16', 'Top', None, None, 'Control point index for the top-side optical edge, or -1 if this glyph has none.'), - ('int16', 'Right', None, None, 'Control point index for the right-side optical edge, or -1 if this glyph has none.'), - ('int16', 'Bottom', None, None, 'Control point index for the bottom-side optical edge, or -1 if this glyph has none.'), - ]), - - # - # TSIC - # - ('TSIC', [ - ('Version', 'Version', None, None, 'Version of table initially set to 0x00010000.'), - ('uint16', 'Flags', None, None, 'TSIC flags - set to 0'), - ('uint16', 'AxisCount', None, None, 'Axis count from fvar'), - ('uint16', 'RecordCount', None, None, 'TSIC record count'), - ('uint16', 'Reserved', None, None, 'Set to 0'), - ('Tag', 'AxisArray', 'AxisCount', 0, 'Array of axis tags in fvar order'), - ('LocationRecord', 'RecordLocations', 'RecordCount', 0, 'Location in variation space of TSIC record'), - ('TSICRecord', 'Record', 'RecordCount', 0, 'Array of TSIC records'), - ]), - - ('LocationRecord', [ - ('F2Dot14', 'Axis', 'AxisCount', 0, 'Axis record'), - ]), - - ('TSICRecord', [ - ('uint16', 'Flags', None, None, 'Record flags - set to 0'), - ('uint16', 'NumCVTEntries', None, None, 'Number of CVT number value pairs'), - ('uint16', 'NameLength', None, None, 'Length of optional user record name'), - ('uint16', 'NameArray', 'NameLength', 0, 'Unicode 16 name'), - ('uint16', 'CVTArray', 'NumCVTEntries', 0, 'CVT number array'), - ('int16', 'CVTValueArray', 'NumCVTEntries', 0, 'CVT value'), - ]), - - # - # COLR - # - - ('COLR', [ - ('uint16', 'Version', None, None, 'Table version number (starts at 0).'), - ('uint16', 'BaseGlyphRecordCount', None, None, 'Number of Base Glyph Records.'), - ('LOffset', 'BaseGlyphRecordArray', None, None, 'Offset (from beginning of COLR table) to Base Glyph records.'), - ('LOffset', 'LayerRecordArray', None, None, 'Offset (from beginning of COLR table) to Layer Records.'), - ('uint16', 'LayerRecordCount', None, None, 'Number of Layer Records.'), - ('LOffset', 'BaseGlyphList', None, 'Version >= 1', 'Offset (from beginning of COLR table) to array of Version-1 Base Glyph records.'), - ('LOffset', 'LayerList', None, 'Version >= 1', 'Offset (from beginning of COLR table) to LayerList.'), - ('LOffset', 'ClipList', None, 'Version >= 1', 'Offset to ClipList table (may be NULL)'), - ('LOffsetTo(DeltaSetIndexMap)', 'VarIndexMap', None, 'Version >= 1', 'Offset to DeltaSetIndexMap table (may be NULL)'), - ('LOffset', 'VarStore', None, 'Version >= 1', 'Offset to variation store (may be NULL)'), - ]), - - ('BaseGlyphRecordArray', [ - ('BaseGlyphRecord', 'BaseGlyphRecord', 'BaseGlyphRecordCount', 0, 'Base Glyph records.'), - ]), - - ('BaseGlyphRecord', [ - ('GlyphID', 'BaseGlyph', None, None, 'Glyph ID of reference glyph. This glyph is for reference only and is not rendered for color.'), - ('uint16', 'FirstLayerIndex', None, None, 'Index (from beginning of the Layer Records) to the layer record. There will be numLayers consecutive entries for this base glyph.'), - ('uint16', 'NumLayers', None, None, 'Number of color layers associated with this glyph.'), - ]), - - ('LayerRecordArray', [ - ('LayerRecord', 'LayerRecord', 'LayerRecordCount', 0, 'Layer records.'), - ]), - - ('LayerRecord', [ - ('GlyphID', 'LayerGlyph', None, None, 'Glyph ID of layer glyph (must be in z-order from bottom to top).'), - ('uint16', 'PaletteIndex', None, None, 'Index value to use with a selected color palette.'), - ]), - - ('BaseGlyphList', [ - ('uint32', 'BaseGlyphCount', None, None, 'Number of Version-1 Base Glyph records'), - ('struct', 'BaseGlyphPaintRecord', 'BaseGlyphCount', 0, 'Array of Version-1 Base Glyph records'), - ]), - - ('BaseGlyphPaintRecord', [ - ('GlyphID', 'BaseGlyph', None, None, 'Glyph ID of reference glyph.'), - ('LOffset', 'Paint', None, None, 'Offset (from beginning of BaseGlyphPaintRecord) to Paint, typically a PaintColrLayers.'), - ]), - - ('LayerList', [ - ('uint32', 'LayerCount', None, None, 'Number of Version-1 Layers'), - ('LOffset', 'Paint', 'LayerCount', 0, 'Array of offsets to Paint tables, from the start of the LayerList table.'), - ]), - - ('ClipListFormat1', [ - ('uint8', 'Format', None, None, 'Format for ClipList with 16bit glyph IDs: 1'), - ('uint32', 'ClipCount', None, None, 'Number of Clip records.'), - ('struct', 'ClipRecord', 'ClipCount', 0, 'Array of Clip records sorted by glyph ID.'), - ]), - - ('ClipRecord', [ - ('uint16', 'StartGlyphID', None, None, 'First glyph ID in the range.'), - ('uint16', 'EndGlyphID', None, None, 'Last glyph ID in the range.'), - ('Offset24', 'ClipBox', None, None, 'Offset to a ClipBox table.'), - ]), - - ('ClipBoxFormat1', [ - ('uint8', 'Format', None, None, 'Format for ClipBox without variation: set to 1.'), - ('int16', 'xMin', None, None, 'Minimum x of clip box.'), - ('int16', 'yMin', None, None, 'Minimum y of clip box.'), - ('int16', 'xMax', None, None, 'Maximum x of clip box.'), - ('int16', 'yMax', None, None, 'Maximum y of clip box.'), - ]), - - ('ClipBoxFormat2', [ - ('uint8', 'Format', None, None, 'Format for variable ClipBox: set to 2.'), - ('int16', 'xMin', None, None, 'Minimum x of clip box. VarIndexBase + 0.'), - ('int16', 'yMin', None, None, 'Minimum y of clip box. VarIndexBase + 1.'), - ('int16', 'xMax', None, None, 'Maximum x of clip box. VarIndexBase + 2.'), - ('int16', 'yMax', None, None, 'Maximum y of clip box. VarIndexBase + 3.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # COLRv1 Affine2x3 uses the same column-major order to serialize a 2D - # Affine Transformation as the one used by fontTools.misc.transform. - # However, for historical reasons, the labels 'xy' and 'yx' are swapped. - # Their fundamental meaning is the same though. - # COLRv1 Affine2x3 follows the names found in FreeType and Cairo. - # In all case, the second element in the 6-tuple correspond to the - # y-part of the x basis vector, and the third to the x-part of the y - # basis vector. - # See https://github.com/googlefonts/colr-gradients-spec/pull/85 - ('Affine2x3', [ - ('Fixed', 'xx', None, None, 'x-part of x basis vector'), - ('Fixed', 'yx', None, None, 'y-part of x basis vector'), - ('Fixed', 'xy', None, None, 'x-part of y basis vector'), - ('Fixed', 'yy', None, None, 'y-part of y basis vector'), - ('Fixed', 'dx', None, None, 'Translation in x direction'), - ('Fixed', 'dy', None, None, 'Translation in y direction'), - ]), - ('VarAffine2x3', [ - ('Fixed', 'xx', None, None, 'x-part of x basis vector. VarIndexBase + 0.'), - ('Fixed', 'yx', None, None, 'y-part of x basis vector. VarIndexBase + 1.'), - ('Fixed', 'xy', None, None, 'x-part of y basis vector. VarIndexBase + 2.'), - ('Fixed', 'yy', None, None, 'y-part of y basis vector. VarIndexBase + 3.'), - ('Fixed', 'dx', None, None, 'Translation in x direction. VarIndexBase + 4.'), - ('Fixed', 'dy', None, None, 'Translation in y direction. VarIndexBase + 5.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - ('ColorStop', [ - ('F2Dot14', 'StopOffset', None, None, ''), - ('uint16', 'PaletteIndex', None, None, 'Index for a CPAL palette entry.'), - ('F2Dot14', 'Alpha', None, None, 'Values outsided [0.,1.] reserved'), - ]), - ('VarColorStop', [ - ('F2Dot14', 'StopOffset', None, None, 'VarIndexBase + 0.'), - ('uint16', 'PaletteIndex', None, None, 'Index for a CPAL palette entry.'), - ('F2Dot14', 'Alpha', None, None, 'Values outsided [0.,1.] reserved. VarIndexBase + 1.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - ('ColorLine', [ - ('ExtendMode', 'Extend', None, None, 'Enum {PAD = 0, REPEAT = 1, REFLECT = 2}'), - ('uint16', 'StopCount', None, None, 'Number of Color stops.'), - ('ColorStop', 'ColorStop', 'StopCount', 0, 'Array of Color stops.'), - ]), - ('VarColorLine', [ - ('ExtendMode', 'Extend', None, None, 'Enum {PAD = 0, REPEAT = 1, REFLECT = 2}'), - ('uint16', 'StopCount', None, None, 'Number of Color stops.'), - ('VarColorStop', 'ColorStop', 'StopCount', 0, 'Array of Color stops.'), - ]), - - # PaintColrLayers - ('PaintFormat1', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 1'), - ('uint8', 'NumLayers', None, None, 'Number of offsets to Paint to read from LayerList.'), - ('uint32', 'FirstLayerIndex', None, None, 'Index into LayerList.'), - ]), - - # PaintSolid - ('PaintFormat2', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 2'), - ('uint16', 'PaletteIndex', None, None, 'Index for a CPAL palette entry.'), - ('F2Dot14', 'Alpha', None, None, 'Values outsided [0.,1.] reserved'), - ]), - # PaintVarSolid - ('PaintFormat3', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 3'), - ('uint16', 'PaletteIndex', None, None, 'Index for a CPAL palette entry.'), - ('F2Dot14', 'Alpha', None, None, 'Values outsided [0.,1.] reserved. VarIndexBase + 0.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintLinearGradient - ('PaintFormat4', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 4'), - ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintLinearGradient table) to ColorLine subtable.'), - ('int16', 'x0', None, None, ''), - ('int16', 'y0', None, None, ''), - ('int16', 'x1', None, None, ''), - ('int16', 'y1', None, None, ''), - ('int16', 'x2', None, None, ''), - ('int16', 'y2', None, None, ''), - ]), - # PaintVarLinearGradient - ('PaintFormat5', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 5'), - ('LOffset24To(VarColorLine)', 'ColorLine', None, None, 'Offset (from beginning of PaintVarLinearGradient table) to VarColorLine subtable.'), - ('int16', 'x0', None, None, 'VarIndexBase + 0.'), - ('int16', 'y0', None, None, 'VarIndexBase + 1.'), - ('int16', 'x1', None, None, 'VarIndexBase + 2.'), - ('int16', 'y1', None, None, 'VarIndexBase + 3.'), - ('int16', 'x2', None, None, 'VarIndexBase + 4.'), - ('int16', 'y2', None, None, 'VarIndexBase + 5.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintRadialGradient - ('PaintFormat6', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 6'), - ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintRadialGradient table) to ColorLine subtable.'), - ('int16', 'x0', None, None, ''), - ('int16', 'y0', None, None, ''), - ('uint16', 'r0', None, None, ''), - ('int16', 'x1', None, None, ''), - ('int16', 'y1', None, None, ''), - ('uint16', 'r1', None, None, ''), - ]), - # PaintVarRadialGradient - ('PaintFormat7', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 7'), - ('LOffset24To(VarColorLine)', 'ColorLine', None, None, 'Offset (from beginning of PaintVarRadialGradient table) to VarColorLine subtable.'), - ('int16', 'x0', None, None, 'VarIndexBase + 0.'), - ('int16', 'y0', None, None, 'VarIndexBase + 1.'), - ('uint16', 'r0', None, None, 'VarIndexBase + 2.'), - ('int16', 'x1', None, None, 'VarIndexBase + 3.'), - ('int16', 'y1', None, None, 'VarIndexBase + 4.'), - ('uint16', 'r1', None, None, 'VarIndexBase + 5.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintSweepGradient - ('PaintFormat8', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 8'), - ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintSweepGradient table) to ColorLine subtable.'), - ('int16', 'centerX', None, None, 'Center x coordinate.'), - ('int16', 'centerY', None, None, 'Center y coordinate.'), - ('BiasedAngle', 'startAngle', None, None, 'Start of the angular range of the gradient.'), - ('BiasedAngle', 'endAngle', None, None, 'End of the angular range of the gradient.'), - ]), - # PaintVarSweepGradient - ('PaintFormat9', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 9'), - ('LOffset24To(VarColorLine)', 'ColorLine', None, None, 'Offset (from beginning of PaintVarSweepGradient table) to VarColorLine subtable.'), - ('int16', 'centerX', None, None, 'Center x coordinate. VarIndexBase + 0.'), - ('int16', 'centerY', None, None, 'Center y coordinate. VarIndexBase + 1.'), - ('BiasedAngle', 'startAngle', None, None, 'Start of the angular range of the gradient. VarIndexBase + 2.'), - ('BiasedAngle', 'endAngle', None, None, 'End of the angular range of the gradient. VarIndexBase + 3.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintGlyph - ('PaintFormat10', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 10'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintGlyph table) to Paint subtable.'), - ('GlyphID', 'Glyph', None, None, 'Glyph ID for the source outline.'), - ]), - - # PaintColrGlyph - ('PaintFormat11', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 11'), - ('GlyphID', 'Glyph', None, None, 'Virtual glyph ID for a BaseGlyphList base glyph.'), - ]), - - # PaintTransform - ('PaintFormat12', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 12'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTransform table) to Paint subtable.'), - ('LOffset24To(Affine2x3)', 'Transform', None, None, '2x3 matrix for 2D affine transformations.'), - ]), - # PaintVarTransform - ('PaintFormat13', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 13'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarTransform table) to Paint subtable.'), - ('LOffset24To(VarAffine2x3)', 'Transform', None, None, '2x3 matrix for 2D affine transformations.'), - ]), - - # PaintTranslate - ('PaintFormat14', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 14'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTranslate table) to Paint subtable.'), - ('int16', 'dx', None, None, 'Translation in x direction.'), - ('int16', 'dy', None, None, 'Translation in y direction.'), - ]), - # PaintVarTranslate - ('PaintFormat15', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 15'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarTranslate table) to Paint subtable.'), - ('int16', 'dx', None, None, 'Translation in x direction. VarIndexBase + 0.'), - ('int16', 'dy', None, None, 'Translation in y direction. VarIndexBase + 1.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintScale - ('PaintFormat16', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 16'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintScale table) to Paint subtable.'), - ('F2Dot14', 'scaleX', None, None, ''), - ('F2Dot14', 'scaleY', None, None, ''), - ]), - # PaintVarScale - ('PaintFormat17', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 17'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarScale table) to Paint subtable.'), - ('F2Dot14', 'scaleX', None, None, 'VarIndexBase + 0.'), - ('F2Dot14', 'scaleY', None, None, 'VarIndexBase + 1.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintScaleAroundCenter - ('PaintFormat18', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 18'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintScaleAroundCenter table) to Paint subtable.'), - ('F2Dot14', 'scaleX', None, None, ''), - ('F2Dot14', 'scaleY', None, None, ''), - ('int16', 'centerX', None, None, ''), - ('int16', 'centerY', None, None, ''), - ]), - # PaintVarScaleAroundCenter - ('PaintFormat19', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 19'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarScaleAroundCenter table) to Paint subtable.'), - ('F2Dot14', 'scaleX', None, None, 'VarIndexBase + 0.'), - ('F2Dot14', 'scaleY', None, None, 'VarIndexBase + 1.'), - ('int16', 'centerX', None, None, 'VarIndexBase + 2.'), - ('int16', 'centerY', None, None, 'VarIndexBase + 3.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintScaleUniform - ('PaintFormat20', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 20'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintScaleUniform table) to Paint subtable.'), - ('F2Dot14', 'scale', None, None, ''), - ]), - # PaintVarScaleUniform - ('PaintFormat21', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 21'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarScaleUniform table) to Paint subtable.'), - ('F2Dot14', 'scale', None, None, 'VarIndexBase + 0.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintScaleUniformAroundCenter - ('PaintFormat22', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 22'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintScaleUniformAroundCenter table) to Paint subtable.'), - ('F2Dot14', 'scale', None, None, ''), - ('int16', 'centerX', None, None, ''), - ('int16', 'centerY', None, None, ''), - ]), - # PaintVarScaleUniformAroundCenter - ('PaintFormat23', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 23'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarScaleUniformAroundCenter table) to Paint subtable.'), - ('F2Dot14', 'scale', None, None, 'VarIndexBase + 0'), - ('int16', 'centerX', None, None, 'VarIndexBase + 1'), - ('int16', 'centerY', None, None, 'VarIndexBase + 2'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintRotate - ('PaintFormat24', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 24'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintRotate table) to Paint subtable.'), - ('Angle', 'angle', None, None, ''), - ]), - # PaintVarRotate - ('PaintFormat25', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 25'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarRotate table) to Paint subtable.'), - ('Angle', 'angle', None, None, 'VarIndexBase + 0.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintRotateAroundCenter - ('PaintFormat26', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 26'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintRotateAroundCenter table) to Paint subtable.'), - ('Angle', 'angle', None, None, ''), - ('int16', 'centerX', None, None, ''), - ('int16', 'centerY', None, None, ''), - ]), - # PaintVarRotateAroundCenter - ('PaintFormat27', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 27'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarRotateAroundCenter table) to Paint subtable.'), - ('Angle', 'angle', None, None, 'VarIndexBase + 0.'), - ('int16', 'centerX', None, None, 'VarIndexBase + 1.'), - ('int16', 'centerY', None, None, 'VarIndexBase + 2.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintSkew - ('PaintFormat28', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 28'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintSkew table) to Paint subtable.'), - ('Angle', 'xSkewAngle', None, None, ''), - ('Angle', 'ySkewAngle', None, None, ''), - ]), - # PaintVarSkew - ('PaintFormat29', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 29'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarSkew table) to Paint subtable.'), - ('Angle', 'xSkewAngle', None, None, 'VarIndexBase + 0.'), - ('Angle', 'ySkewAngle', None, None, 'VarIndexBase + 1.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintSkewAroundCenter - ('PaintFormat30', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 30'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintSkewAroundCenter table) to Paint subtable.'), - ('Angle', 'xSkewAngle', None, None, ''), - ('Angle', 'ySkewAngle', None, None, ''), - ('int16', 'centerX', None, None, ''), - ('int16', 'centerY', None, None, ''), - ]), - # PaintVarSkewAroundCenter - ('PaintFormat31', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 31'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintVarSkewAroundCenter table) to Paint subtable.'), - ('Angle', 'xSkewAngle', None, None, 'VarIndexBase + 0.'), - ('Angle', 'ySkewAngle', None, None, 'VarIndexBase + 1.'), - ('int16', 'centerX', None, None, 'VarIndexBase + 2.'), - ('int16', 'centerY', None, None, 'VarIndexBase + 3.'), - ('VarIndex', 'VarIndexBase', None, None, 'Base index into DeltaSetIndexMap.'), - ]), - - # PaintComposite - ('PaintFormat32', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 32'), - ('LOffset24To(Paint)', 'SourcePaint', None, None, 'Offset (from beginning of PaintComposite table) to source Paint subtable.'), - ('CompositeMode', 'CompositeMode', None, None, 'A CompositeMode enumeration value.'), - ('LOffset24To(Paint)', 'BackdropPaint', None, None, 'Offset (from beginning of PaintComposite table) to backdrop Paint subtable.'), - ]), - - # - # avar - # - - ('AxisValueMap', [ - ('F2Dot14', 'FromCoordinate', None, None, 'A normalized coordinate value obtained using default normalization'), - ('F2Dot14', 'ToCoordinate', None, None, 'The modified, normalized coordinate value'), - ]), - - ('AxisSegmentMap', [ - ('uint16', 'PositionMapCount', None, None, 'The number of correspondence pairs for this axis'), - ('AxisValueMap', 'AxisValueMap', 'PositionMapCount', 0, 'The array of axis value map records for this axis'), - ]), - - ('avar', [ - ('Version', 'Version', None, None, 'Version of the avar table- 0x00010000 or 0x00020000'), - ('uint16', 'Reserved', None, None, 'Permanently reserved; set to zero'), - ('uint16', 'AxisCount', None, None, 'The number of variation axes for this font. This must be the same number as axisCount in the "fvar" table'), - ('AxisSegmentMap', 'AxisSegmentMap', 'AxisCount', 0, 'The segment maps array — one segment map for each axis, in the order of axes specified in the "fvar" table'), - ('LOffset', 'VarIdxMap', None, 'Version >= 0x00020000', ''), - ('LOffset', 'VarStore', None, 'Version >= 0x00020000', ''), - ]), + # + # common + # + ("LookupOrder", []), + ( + "ScriptList", + [ + ("uint16", "ScriptCount", None, None, "Number of ScriptRecords"), + ( + "struct", + "ScriptRecord", + "ScriptCount", + 0, + "Array of ScriptRecords -listed alphabetically by ScriptTag", + ), + ], + ), + ( + "ScriptRecord", + [ + ("Tag", "ScriptTag", None, None, "4-byte ScriptTag identifier"), + ( + "Offset", + "Script", + None, + None, + "Offset to Script table-from beginning of ScriptList", + ), + ], + ), + ( + "Script", + [ + ( + "Offset", + "DefaultLangSys", + None, + None, + "Offset to DefaultLangSys table-from beginning of Script table-may be NULL", + ), + ( + "uint16", + "LangSysCount", + None, + None, + "Number of LangSysRecords for this script-excluding the DefaultLangSys", + ), + ( + "struct", + "LangSysRecord", + "LangSysCount", + 0, + "Array of LangSysRecords-listed alphabetically by LangSysTag", + ), + ], + ), + ( + "LangSysRecord", + [ + ("Tag", "LangSysTag", None, None, "4-byte LangSysTag identifier"), + ( + "Offset", + "LangSys", + None, + None, + "Offset to LangSys table-from beginning of Script table", + ), + ], + ), + ( + "LangSys", + [ + ( + "Offset", + "LookupOrder", + None, + None, + "= NULL (reserved for an offset to a reordering table)", + ), + ( + "uint16", + "ReqFeatureIndex", + None, + None, + "Index of a feature required for this language system- if no required features = 0xFFFF", + ), + ( + "uint16", + "FeatureCount", + None, + None, + "Number of FeatureIndex values for this language system-excludes the required feature", + ), + ( + "uint16", + "FeatureIndex", + "FeatureCount", + 0, + "Array of indices into the FeatureList-in arbitrary order", + ), + ], + ), + ( + "FeatureList", + [ + ( + "uint16", + "FeatureCount", + None, + None, + "Number of FeatureRecords in this table", + ), + ( + "struct", + "FeatureRecord", + "FeatureCount", + 0, + "Array of FeatureRecords-zero-based (first feature has FeatureIndex = 0)-listed alphabetically by FeatureTag", + ), + ], + ), + ( + "FeatureRecord", + [ + ("Tag", "FeatureTag", None, None, "4-byte feature identification tag"), + ( + "Offset", + "Feature", + None, + None, + "Offset to Feature table-from beginning of FeatureList", + ), + ], + ), + ( + "Feature", + [ + ( + "Offset", + "FeatureParams", + None, + None, + "= NULL (reserved for offset to FeatureParams)", + ), + ( + "uint16", + "LookupCount", + None, + None, + "Number of LookupList indices for this feature", + ), + ( + "uint16", + "LookupListIndex", + "LookupCount", + 0, + "Array of LookupList indices for this feature -zero-based (first lookup is LookupListIndex = 0)", + ), + ], + ), + ("FeatureParams", []), + ( + "FeatureParamsSize", + [ + ( + "DeciPoints", + "DesignSize", + None, + None, + "The design size in 720/inch units (decipoints).", + ), + ( + "uint16", + "SubfamilyID", + None, + None, + "Serves as an identifier that associates fonts in a subfamily.", + ), + ("NameID", "SubfamilyNameID", None, None, "Subfamily NameID."), + ( + "DeciPoints", + "RangeStart", + None, + None, + "Small end of recommended usage range (exclusive) in 720/inch units.", + ), + ( + "DeciPoints", + "RangeEnd", + None, + None, + "Large end of recommended usage range (inclusive) in 720/inch units.", + ), + ], + ), + ( + "FeatureParamsStylisticSet", + [ + ("uint16", "Version", None, None, "Set to 0."), + ("NameID", "UINameID", None, None, "UI NameID."), + ], + ), + ( + "FeatureParamsCharacterVariants", + [ + ("uint16", "Format", None, None, "Set to 0."), + ("NameID", "FeatUILabelNameID", None, None, "Feature UI label NameID."), + ( + "NameID", + "FeatUITooltipTextNameID", + None, + None, + "Feature UI tooltip text NameID.", + ), + ("NameID", "SampleTextNameID", None, None, "Sample text NameID."), + ("uint16", "NumNamedParameters", None, None, "Number of named parameters."), + ( + "NameID", + "FirstParamUILabelNameID", + None, + None, + "First NameID of UI feature parameters.", + ), + ( + "uint16", + "CharCount", + None, + None, + "Count of characters this feature provides glyph variants for.", + ), + ( + "uint24", + "Character", + "CharCount", + 0, + "Unicode characters for which this feature provides glyph variants.", + ), + ], + ), + ( + "LookupList", + [ + ("uint16", "LookupCount", None, None, "Number of lookups in this table"), + ( + "Offset", + "Lookup", + "LookupCount", + 0, + "Array of offsets to Lookup tables-from beginning of LookupList -zero based (first lookup is Lookup index = 0)", + ), + ], + ), + ( + "Lookup", + [ + ( + "uint16", + "LookupType", + None, + None, + "Different enumerations for GSUB and GPOS", + ), + ("LookupFlag", "LookupFlag", None, None, "Lookup qualifiers"), + ( + "uint16", + "SubTableCount", + None, + None, + "Number of SubTables for this lookup", + ), + ( + "Offset", + "SubTable", + "SubTableCount", + 0, + "Array of offsets to SubTables-from beginning of Lookup table", + ), + ( + "uint16", + "MarkFilteringSet", + None, + "LookupFlag & 0x0010", + "If set, indicates that the lookup table structure is followed by a MarkFilteringSet field. The layout engine skips over all mark glyphs not in the mark filtering set indicated.", + ), + ], + ), + ( + "CoverageFormat1", + [ + ("uint16", "CoverageFormat", None, None, "Format identifier-format = 1"), + ("uint16", "GlyphCount", None, None, "Number of glyphs in the GlyphArray"), + ( + "GlyphID", + "GlyphArray", + "GlyphCount", + 0, + "Array of GlyphIDs-in numerical order", + ), + ], + ), + ( + "CoverageFormat2", + [ + ("uint16", "CoverageFormat", None, None, "Format identifier-format = 2"), + ("uint16", "RangeCount", None, None, "Number of RangeRecords"), + ( + "struct", + "RangeRecord", + "RangeCount", + 0, + "Array of glyph ranges-ordered by Start GlyphID", + ), + ], + ), + ( + "RangeRecord", + [ + ("GlyphID", "Start", None, None, "First GlyphID in the range"), + ("GlyphID", "End", None, None, "Last GlyphID in the range"), + ( + "uint16", + "StartCoverageIndex", + None, + None, + "Coverage Index of first GlyphID in range", + ), + ], + ), + ( + "ClassDefFormat1", + [ + ("uint16", "ClassFormat", None, None, "Format identifier-format = 1"), + ( + "GlyphID", + "StartGlyph", + None, + None, + "First GlyphID of the ClassValueArray", + ), + ("uint16", "GlyphCount", None, None, "Size of the ClassValueArray"), + ( + "uint16", + "ClassValueArray", + "GlyphCount", + 0, + "Array of Class Values-one per GlyphID", + ), + ], + ), + ( + "ClassDefFormat2", + [ + ("uint16", "ClassFormat", None, None, "Format identifier-format = 2"), + ("uint16", "ClassRangeCount", None, None, "Number of ClassRangeRecords"), + ( + "struct", + "ClassRangeRecord", + "ClassRangeCount", + 0, + "Array of ClassRangeRecords-ordered by Start GlyphID", + ), + ], + ), + ( + "ClassRangeRecord", + [ + ("GlyphID", "Start", None, None, "First GlyphID in the range"), + ("GlyphID", "End", None, None, "Last GlyphID in the range"), + ("uint16", "Class", None, None, "Applied to all glyphs in the range"), + ], + ), + ( + "Device", + [ + ("uint16", "StartSize", None, None, "Smallest size to correct-in ppem"), + ("uint16", "EndSize", None, None, "Largest size to correct-in ppem"), + ( + "uint16", + "DeltaFormat", + None, + None, + "Format of DeltaValue array data: 1, 2, or 3", + ), + ( + "DeltaValue", + "DeltaValue", + "", + "DeltaFormat in (1,2,3)", + "Array of compressed data", + ), + ], + ), + # + # gpos + # + ( + "GPOS", + [ + ( + "Version", + "Version", + None, + None, + "Version of the GPOS table- 0x00010000 or 0x00010001", + ), + ( + "Offset", + "ScriptList", + None, + None, + "Offset to ScriptList table-from beginning of GPOS table", + ), + ( + "Offset", + "FeatureList", + None, + None, + "Offset to FeatureList table-from beginning of GPOS table", + ), + ( + "Offset", + "LookupList", + None, + None, + "Offset to LookupList table-from beginning of GPOS table", + ), + ( + "LOffset", + "FeatureVariations", + None, + "Version >= 0x00010001", + "Offset to FeatureVariations table-from beginning of GPOS table", + ), + ], + ), + ( + "SinglePosFormat1", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of SinglePos subtable", + ), + ( + "uint16", + "ValueFormat", + None, + None, + "Defines the types of data in the ValueRecord", + ), + ( + "ValueRecord", + "Value", + None, + None, + "Defines positioning value(s)-applied to all glyphs in the Coverage table", + ), + ], + ), + ( + "SinglePosFormat2", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 2"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of SinglePos subtable", + ), + ( + "uint16", + "ValueFormat", + None, + None, + "Defines the types of data in the ValueRecord", + ), + ("uint16", "ValueCount", None, None, "Number of ValueRecords"), + ( + "ValueRecord", + "Value", + "ValueCount", + 0, + "Array of ValueRecords-positioning values applied to glyphs", + ), + ], + ), + ( + "PairPosFormat1", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of PairPos subtable-only the first glyph in each pair", + ), + ( + "uint16", + "ValueFormat1", + None, + None, + "Defines the types of data in ValueRecord1-for the first glyph in the pair -may be zero (0)", + ), + ( + "uint16", + "ValueFormat2", + None, + None, + "Defines the types of data in ValueRecord2-for the second glyph in the pair -may be zero (0)", + ), + ("uint16", "PairSetCount", None, None, "Number of PairSet tables"), + ( + "Offset", + "PairSet", + "PairSetCount", + 0, + "Array of offsets to PairSet tables-from beginning of PairPos subtable-ordered by Coverage Index", + ), + ], + ), + ( + "PairSet", + [ + ("uint16", "PairValueCount", None, None, "Number of PairValueRecords"), + ( + "struct", + "PairValueRecord", + "PairValueCount", + 0, + "Array of PairValueRecords-ordered by GlyphID of the second glyph", + ), + ], + ), + ( + "PairValueRecord", + [ + ( + "GlyphID", + "SecondGlyph", + None, + None, + "GlyphID of second glyph in the pair-first glyph is listed in the Coverage table", + ), + ( + "ValueRecord", + "Value1", + None, + None, + "Positioning data for the first glyph in the pair", + ), + ( + "ValueRecord", + "Value2", + None, + None, + "Positioning data for the second glyph in the pair", + ), + ], + ), + ( + "PairPosFormat2", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 2"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of PairPos subtable-for the first glyph of the pair", + ), + ( + "uint16", + "ValueFormat1", + None, + None, + "ValueRecord definition-for the first glyph of the pair-may be zero (0)", + ), + ( + "uint16", + "ValueFormat2", + None, + None, + "ValueRecord definition-for the second glyph of the pair-may be zero (0)", + ), + ( + "Offset", + "ClassDef1", + None, + None, + "Offset to ClassDef table-from beginning of PairPos subtable-for the first glyph of the pair", + ), + ( + "Offset", + "ClassDef2", + None, + None, + "Offset to ClassDef table-from beginning of PairPos subtable-for the second glyph of the pair", + ), + ( + "uint16", + "Class1Count", + None, + None, + "Number of classes in ClassDef1 table-includes Class0", + ), + ( + "uint16", + "Class2Count", + None, + None, + "Number of classes in ClassDef2 table-includes Class0", + ), + ( + "struct", + "Class1Record", + "Class1Count", + 0, + "Array of Class1 records-ordered by Class1", + ), + ], + ), + ( + "Class1Record", + [ + ( + "struct", + "Class2Record", + "Class2Count", + 0, + "Array of Class2 records-ordered by Class2", + ), + ], + ), + ( + "Class2Record", + [ + ( + "ValueRecord", + "Value1", + None, + None, + "Positioning for first glyph-empty if ValueFormat1 = 0", + ), + ( + "ValueRecord", + "Value2", + None, + None, + "Positioning for second glyph-empty if ValueFormat2 = 0", + ), + ], + ), + ( + "CursivePosFormat1", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of CursivePos subtable", + ), + ("uint16", "EntryExitCount", None, None, "Number of EntryExit records"), + ( + "struct", + "EntryExitRecord", + "EntryExitCount", + 0, + "Array of EntryExit records-in Coverage Index order", + ), + ], + ), + ( + "EntryExitRecord", + [ + ( + "Offset", + "EntryAnchor", + None, + None, + "Offset to EntryAnchor table-from beginning of CursivePos subtable-may be NULL", + ), + ( + "Offset", + "ExitAnchor", + None, + None, + "Offset to ExitAnchor table-from beginning of CursivePos subtable-may be NULL", + ), + ], + ), + ( + "MarkBasePosFormat1", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "MarkCoverage", + None, + None, + "Offset to MarkCoverage table-from beginning of MarkBasePos subtable", + ), + ( + "Offset", + "BaseCoverage", + None, + None, + "Offset to BaseCoverage table-from beginning of MarkBasePos subtable", + ), + ("uint16", "ClassCount", None, None, "Number of classes defined for marks"), + ( + "Offset", + "MarkArray", + None, + None, + "Offset to MarkArray table-from beginning of MarkBasePos subtable", + ), + ( + "Offset", + "BaseArray", + None, + None, + "Offset to BaseArray table-from beginning of MarkBasePos subtable", + ), + ], + ), + ( + "BaseArray", + [ + ("uint16", "BaseCount", None, None, "Number of BaseRecords"), + ( + "struct", + "BaseRecord", + "BaseCount", + 0, + "Array of BaseRecords-in order of BaseCoverage Index", + ), + ], + ), + ( + "BaseRecord", + [ + ( + "Offset", + "BaseAnchor", + "ClassCount", + 0, + "Array of offsets (one per class) to Anchor tables-from beginning of BaseArray table-ordered by class-zero-based", + ), + ], + ), + ( + "MarkLigPosFormat1", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "MarkCoverage", + None, + None, + "Offset to Mark Coverage table-from beginning of MarkLigPos subtable", + ), + ( + "Offset", + "LigatureCoverage", + None, + None, + "Offset to Ligature Coverage table-from beginning of MarkLigPos subtable", + ), + ("uint16", "ClassCount", None, None, "Number of defined mark classes"), + ( + "Offset", + "MarkArray", + None, + None, + "Offset to MarkArray table-from beginning of MarkLigPos subtable", + ), + ( + "Offset", + "LigatureArray", + None, + None, + "Offset to LigatureArray table-from beginning of MarkLigPos subtable", + ), + ], + ), + ( + "LigatureArray", + [ + ( + "uint16", + "LigatureCount", + None, + None, + "Number of LigatureAttach table offsets", + ), + ( + "Offset", + "LigatureAttach", + "LigatureCount", + 0, + "Array of offsets to LigatureAttach tables-from beginning of LigatureArray table-ordered by LigatureCoverage Index", + ), + ], + ), + ( + "LigatureAttach", + [ + ( + "uint16", + "ComponentCount", + None, + None, + "Number of ComponentRecords in this ligature", + ), + ( + "struct", + "ComponentRecord", + "ComponentCount", + 0, + "Array of Component records-ordered in writing direction", + ), + ], + ), + ( + "ComponentRecord", + [ + ( + "Offset", + "LigatureAnchor", + "ClassCount", + 0, + "Array of offsets (one per class) to Anchor tables-from beginning of LigatureAttach table-ordered by class-NULL if a component does not have an attachment for a class-zero-based array", + ), + ], + ), + ( + "MarkMarkPosFormat1", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Mark1Coverage", + None, + None, + "Offset to Combining Mark Coverage table-from beginning of MarkMarkPos subtable", + ), + ( + "Offset", + "Mark2Coverage", + None, + None, + "Offset to Base Mark Coverage table-from beginning of MarkMarkPos subtable", + ), + ( + "uint16", + "ClassCount", + None, + None, + "Number of Combining Mark classes defined", + ), + ( + "Offset", + "Mark1Array", + None, + None, + "Offset to MarkArray table for Mark1-from beginning of MarkMarkPos subtable", + ), + ( + "Offset", + "Mark2Array", + None, + None, + "Offset to Mark2Array table for Mark2-from beginning of MarkMarkPos subtable", + ), + ], + ), + ( + "Mark2Array", + [ + ("uint16", "Mark2Count", None, None, "Number of Mark2 records"), + ( + "struct", + "Mark2Record", + "Mark2Count", + 0, + "Array of Mark2 records-in Coverage order", + ), + ], + ), + ( + "Mark2Record", + [ + ( + "Offset", + "Mark2Anchor", + "ClassCount", + 0, + "Array of offsets (one per class) to Anchor tables-from beginning of Mark2Array table-zero-based array", + ), + ], + ), + ( + "PosLookupRecord", + [ + ( + "uint16", + "SequenceIndex", + None, + None, + "Index to input glyph sequence-first glyph = 0", + ), + ( + "uint16", + "LookupListIndex", + None, + None, + "Lookup to apply to that position-zero-based", + ), + ], + ), + ( + "ContextPosFormat1", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of ContextPos subtable", + ), + ("uint16", "PosRuleSetCount", None, None, "Number of PosRuleSet tables"), + ( + "Offset", + "PosRuleSet", + "PosRuleSetCount", + 0, + "Array of offsets to PosRuleSet tables-from beginning of ContextPos subtable-ordered by Coverage Index", + ), + ], + ), + ( + "PosRuleSet", + [ + ("uint16", "PosRuleCount", None, None, "Number of PosRule tables"), + ( + "Offset", + "PosRule", + "PosRuleCount", + 0, + "Array of offsets to PosRule tables-from beginning of PosRuleSet-ordered by preference", + ), + ], + ), + ( + "PosRule", + [ + ( + "uint16", + "GlyphCount", + None, + None, + "Number of glyphs in the Input glyph sequence", + ), + ("uint16", "PosCount", None, None, "Number of PosLookupRecords"), + ( + "GlyphID", + "Input", + "GlyphCount", + -1, + "Array of input GlyphIDs-starting with the second glyph", + ), + ( + "struct", + "PosLookupRecord", + "PosCount", + 0, + "Array of positioning lookups-in design order", + ), + ], + ), + ( + "ContextPosFormat2", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 2"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of ContextPos subtable", + ), + ( + "Offset", + "ClassDef", + None, + None, + "Offset to ClassDef table-from beginning of ContextPos subtable", + ), + ("uint16", "PosClassSetCount", None, None, "Number of PosClassSet tables"), + ( + "Offset", + "PosClassSet", + "PosClassSetCount", + 0, + "Array of offsets to PosClassSet tables-from beginning of ContextPos subtable-ordered by class-may be NULL", + ), + ], + ), + ( + "PosClassSet", + [ + ( + "uint16", + "PosClassRuleCount", + None, + None, + "Number of PosClassRule tables", + ), + ( + "Offset", + "PosClassRule", + "PosClassRuleCount", + 0, + "Array of offsets to PosClassRule tables-from beginning of PosClassSet-ordered by preference", + ), + ], + ), + ( + "PosClassRule", + [ + ("uint16", "GlyphCount", None, None, "Number of glyphs to be matched"), + ("uint16", "PosCount", None, None, "Number of PosLookupRecords"), + ( + "uint16", + "Class", + "GlyphCount", + -1, + "Array of classes-beginning with the second class-to be matched to the input glyph sequence", + ), + ( + "struct", + "PosLookupRecord", + "PosCount", + 0, + "Array of positioning lookups-in design order", + ), + ], + ), + ( + "ContextPosFormat3", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 3"), + ( + "uint16", + "GlyphCount", + None, + None, + "Number of glyphs in the input sequence", + ), + ("uint16", "PosCount", None, None, "Number of PosLookupRecords"), + ( + "Offset", + "Coverage", + "GlyphCount", + 0, + "Array of offsets to Coverage tables-from beginning of ContextPos subtable", + ), + ( + "struct", + "PosLookupRecord", + "PosCount", + 0, + "Array of positioning lookups-in design order", + ), + ], + ), + ( + "ChainContextPosFormat1", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of ContextPos subtable", + ), + ( + "uint16", + "ChainPosRuleSetCount", + None, + None, + "Number of ChainPosRuleSet tables", + ), + ( + "Offset", + "ChainPosRuleSet", + "ChainPosRuleSetCount", + 0, + "Array of offsets to ChainPosRuleSet tables-from beginning of ContextPos subtable-ordered by Coverage Index", + ), + ], + ), + ( + "ChainPosRuleSet", + [ + ( + "uint16", + "ChainPosRuleCount", + None, + None, + "Number of ChainPosRule tables", + ), + ( + "Offset", + "ChainPosRule", + "ChainPosRuleCount", + 0, + "Array of offsets to ChainPosRule tables-from beginning of ChainPosRuleSet-ordered by preference", + ), + ], + ), + ( + "ChainPosRule", + [ + ( + "uint16", + "BacktrackGlyphCount", + None, + None, + "Total number of glyphs in the backtrack sequence (number of glyphs to be matched before the first glyph)", + ), + ( + "GlyphID", + "Backtrack", + "BacktrackGlyphCount", + 0, + "Array of backtracking GlyphID's (to be matched before the input sequence)", + ), + ( + "uint16", + "InputGlyphCount", + None, + None, + "Total number of glyphs in the input sequence (includes the first glyph)", + ), + ( + "GlyphID", + "Input", + "InputGlyphCount", + -1, + "Array of input GlyphIDs (start with second glyph)", + ), + ( + "uint16", + "LookAheadGlyphCount", + None, + None, + "Total number of glyphs in the look ahead sequence (number of glyphs to be matched after the input sequence)", + ), + ( + "GlyphID", + "LookAhead", + "LookAheadGlyphCount", + 0, + "Array of lookahead GlyphID's (to be matched after the input sequence)", + ), + ("uint16", "PosCount", None, None, "Number of PosLookupRecords"), + ( + "struct", + "PosLookupRecord", + "PosCount", + 0, + "Array of PosLookupRecords (in design order)", + ), + ], + ), + ( + "ChainContextPosFormat2", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 2"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of ChainContextPos subtable", + ), + ( + "Offset", + "BacktrackClassDef", + None, + None, + "Offset to ClassDef table containing backtrack sequence context-from beginning of ChainContextPos subtable", + ), + ( + "Offset", + "InputClassDef", + None, + None, + "Offset to ClassDef table containing input sequence context-from beginning of ChainContextPos subtable", + ), + ( + "Offset", + "LookAheadClassDef", + None, + None, + "Offset to ClassDef table containing lookahead sequence context-from beginning of ChainContextPos subtable", + ), + ( + "uint16", + "ChainPosClassSetCount", + None, + None, + "Number of ChainPosClassSet tables", + ), + ( + "Offset", + "ChainPosClassSet", + "ChainPosClassSetCount", + 0, + "Array of offsets to ChainPosClassSet tables-from beginning of ChainContextPos subtable-ordered by input class-may be NULL", + ), + ], + ), + ( + "ChainPosClassSet", + [ + ( + "uint16", + "ChainPosClassRuleCount", + None, + None, + "Number of ChainPosClassRule tables", + ), + ( + "Offset", + "ChainPosClassRule", + "ChainPosClassRuleCount", + 0, + "Array of offsets to ChainPosClassRule tables-from beginning of ChainPosClassSet-ordered by preference", + ), + ], + ), + ( + "ChainPosClassRule", + [ + ( + "uint16", + "BacktrackGlyphCount", + None, + None, + "Total number of glyphs in the backtrack sequence (number of glyphs to be matched before the first glyph)", + ), + ( + "uint16", + "Backtrack", + "BacktrackGlyphCount", + 0, + "Array of backtracking classes(to be matched before the input sequence)", + ), + ( + "uint16", + "InputGlyphCount", + None, + None, + "Total number of classes in the input sequence (includes the first class)", + ), + ( + "uint16", + "Input", + "InputGlyphCount", + -1, + "Array of input classes(start with second class; to be matched with the input glyph sequence)", + ), + ( + "uint16", + "LookAheadGlyphCount", + None, + None, + "Total number of classes in the look ahead sequence (number of classes to be matched after the input sequence)", + ), + ( + "uint16", + "LookAhead", + "LookAheadGlyphCount", + 0, + "Array of lookahead classes(to be matched after the input sequence)", + ), + ("uint16", "PosCount", None, None, "Number of PosLookupRecords"), + ( + "struct", + "PosLookupRecord", + "PosCount", + 0, + "Array of PosLookupRecords (in design order)", + ), + ], + ), + ( + "ChainContextPosFormat3", + [ + ("uint16", "PosFormat", None, None, "Format identifier-format = 3"), + ( + "uint16", + "BacktrackGlyphCount", + None, + None, + "Number of glyphs in the backtracking sequence", + ), + ( + "Offset", + "BacktrackCoverage", + "BacktrackGlyphCount", + 0, + "Array of offsets to coverage tables in backtracking sequence, in glyph sequence order", + ), + ( + "uint16", + "InputGlyphCount", + None, + None, + "Number of glyphs in input sequence", + ), + ( + "Offset", + "InputCoverage", + "InputGlyphCount", + 0, + "Array of offsets to coverage tables in input sequence, in glyph sequence order", + ), + ( + "uint16", + "LookAheadGlyphCount", + None, + None, + "Number of glyphs in lookahead sequence", + ), + ( + "Offset", + "LookAheadCoverage", + "LookAheadGlyphCount", + 0, + "Array of offsets to coverage tables in lookahead sequence, in glyph sequence order", + ), + ("uint16", "PosCount", None, None, "Number of PosLookupRecords"), + ( + "struct", + "PosLookupRecord", + "PosCount", + 0, + "Array of PosLookupRecords,in design order", + ), + ], + ), + ( + "ExtensionPosFormat1", + [ + ("uint16", "ExtFormat", None, None, "Format identifier. Set to 1."), + ( + "uint16", + "ExtensionLookupType", + None, + None, + "Lookup type of subtable referenced by ExtensionOffset (i.e. the extension subtable).", + ), + ("LOffset", "ExtSubTable", None, None, "Offset to SubTable"), + ], + ), + # ('ValueRecord', [ + # ('int16', 'XPlacement', None, None, 'Horizontal adjustment for placement-in design units'), + # ('int16', 'YPlacement', None, None, 'Vertical adjustment for placement-in design units'), + # ('int16', 'XAdvance', None, None, 'Horizontal adjustment for advance-in design units (only used for horizontal writing)'), + # ('int16', 'YAdvance', None, None, 'Vertical adjustment for advance-in design units (only used for vertical writing)'), + # ('Offset', 'XPlaDevice', None, None, 'Offset to Device table for horizontal placement-measured from beginning of PosTable (may be NULL)'), + # ('Offset', 'YPlaDevice', None, None, 'Offset to Device table for vertical placement-measured from beginning of PosTable (may be NULL)'), + # ('Offset', 'XAdvDevice', None, None, 'Offset to Device table for horizontal advance-measured from beginning of PosTable (may be NULL)'), + # ('Offset', 'YAdvDevice', None, None, 'Offset to Device table for vertical advance-measured from beginning of PosTable (may be NULL)'), + # ]), + ( + "AnchorFormat1", + [ + ("uint16", "AnchorFormat", None, None, "Format identifier-format = 1"), + ("int16", "XCoordinate", None, None, "Horizontal value-in design units"), + ("int16", "YCoordinate", None, None, "Vertical value-in design units"), + ], + ), + ( + "AnchorFormat2", + [ + ("uint16", "AnchorFormat", None, None, "Format identifier-format = 2"), + ("int16", "XCoordinate", None, None, "Horizontal value-in design units"), + ("int16", "YCoordinate", None, None, "Vertical value-in design units"), + ("uint16", "AnchorPoint", None, None, "Index to glyph contour point"), + ], + ), + ( + "AnchorFormat3", + [ + ("uint16", "AnchorFormat", None, None, "Format identifier-format = 3"), + ("int16", "XCoordinate", None, None, "Horizontal value-in design units"), + ("int16", "YCoordinate", None, None, "Vertical value-in design units"), + ( + "Offset", + "XDeviceTable", + None, + None, + "Offset to Device table for X coordinate- from beginning of Anchor table (may be NULL)", + ), + ( + "Offset", + "YDeviceTable", + None, + None, + "Offset to Device table for Y coordinate- from beginning of Anchor table (may be NULL)", + ), + ], + ), + ( + "MarkArray", + [ + ("uint16", "MarkCount", None, None, "Number of MarkRecords"), + ( + "struct", + "MarkRecord", + "MarkCount", + 0, + "Array of MarkRecords-in Coverage order", + ), + ], + ), + ( + "MarkRecord", + [ + ("uint16", "Class", None, None, "Class defined for this mark"), + ( + "Offset", + "MarkAnchor", + None, + None, + "Offset to Anchor table-from beginning of MarkArray table", + ), + ], + ), + # + # gsub + # + ( + "GSUB", + [ + ( + "Version", + "Version", + None, + None, + "Version of the GSUB table- 0x00010000 or 0x00010001", + ), + ( + "Offset", + "ScriptList", + None, + None, + "Offset to ScriptList table-from beginning of GSUB table", + ), + ( + "Offset", + "FeatureList", + None, + None, + "Offset to FeatureList table-from beginning of GSUB table", + ), + ( + "Offset", + "LookupList", + None, + None, + "Offset to LookupList table-from beginning of GSUB table", + ), + ( + "LOffset", + "FeatureVariations", + None, + "Version >= 0x00010001", + "Offset to FeatureVariations table-from beginning of GSUB table", + ), + ], + ), + ( + "SingleSubstFormat1", + [ + ("uint16", "SubstFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of Substitution table", + ), + ( + "uint16", + "DeltaGlyphID", + None, + None, + "Add to original GlyphID modulo 65536 to get substitute GlyphID", + ), + ], + ), + ( + "SingleSubstFormat2", + [ + ("uint16", "SubstFormat", None, None, "Format identifier-format = 2"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of Substitution table", + ), + ( + "uint16", + "GlyphCount", + None, + None, + "Number of GlyphIDs in the Substitute array", + ), + ( + "GlyphID", + "Substitute", + "GlyphCount", + 0, + "Array of substitute GlyphIDs-ordered by Coverage Index", + ), + ], + ), + ( + "MultipleSubstFormat1", + [ + ("uint16", "SubstFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of Substitution table", + ), + ( + "uint16", + "SequenceCount", + None, + None, + "Number of Sequence table offsets in the Sequence array", + ), + ( + "Offset", + "Sequence", + "SequenceCount", + 0, + "Array of offsets to Sequence tables-from beginning of Substitution table-ordered by Coverage Index", + ), + ], + ), + ( + "Sequence", + [ + ( + "uint16", + "GlyphCount", + None, + None, + "Number of GlyphIDs in the Substitute array. This should always be greater than 0.", + ), + ( + "GlyphID", + "Substitute", + "GlyphCount", + 0, + "String of GlyphIDs to substitute", + ), + ], + ), + ( + "AlternateSubstFormat1", + [ + ("uint16", "SubstFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of Substitution table", + ), + ( + "uint16", + "AlternateSetCount", + None, + None, + "Number of AlternateSet tables", + ), + ( + "Offset", + "AlternateSet", + "AlternateSetCount", + 0, + "Array of offsets to AlternateSet tables-from beginning of Substitution table-ordered by Coverage Index", + ), + ], + ), + ( + "AlternateSet", + [ + ( + "uint16", + "GlyphCount", + None, + None, + "Number of GlyphIDs in the Alternate array", + ), + ( + "GlyphID", + "Alternate", + "GlyphCount", + 0, + "Array of alternate GlyphIDs-in arbitrary order", + ), + ], + ), + ( + "LigatureSubstFormat1", + [ + ("uint16", "SubstFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of Substitution table", + ), + ("uint16", "LigSetCount", None, None, "Number of LigatureSet tables"), + ( + "Offset", + "LigatureSet", + "LigSetCount", + 0, + "Array of offsets to LigatureSet tables-from beginning of Substitution table-ordered by Coverage Index", + ), + ], + ), + ( + "LigatureSet", + [ + ("uint16", "LigatureCount", None, None, "Number of Ligature tables"), + ( + "Offset", + "Ligature", + "LigatureCount", + 0, + "Array of offsets to Ligature tables-from beginning of LigatureSet table-ordered by preference", + ), + ], + ), + ( + "Ligature", + [ + ("GlyphID", "LigGlyph", None, None, "GlyphID of ligature to substitute"), + ("uint16", "CompCount", None, None, "Number of components in the ligature"), + ( + "GlyphID", + "Component", + "CompCount", + -1, + "Array of component GlyphIDs-start with the second component-ordered in writing direction", + ), + ], + ), + ( + "SubstLookupRecord", + [ + ( + "uint16", + "SequenceIndex", + None, + None, + "Index into current glyph sequence-first glyph = 0", + ), + ( + "uint16", + "LookupListIndex", + None, + None, + "Lookup to apply to that position-zero-based", + ), + ], + ), + ( + "ContextSubstFormat1", + [ + ("uint16", "SubstFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of Substitution table", + ), + ( + "uint16", + "SubRuleSetCount", + None, + None, + "Number of SubRuleSet tables-must equal GlyphCount in Coverage table", + ), + ( + "Offset", + "SubRuleSet", + "SubRuleSetCount", + 0, + "Array of offsets to SubRuleSet tables-from beginning of Substitution table-ordered by Coverage Index", + ), + ], + ), + ( + "SubRuleSet", + [ + ("uint16", "SubRuleCount", None, None, "Number of SubRule tables"), + ( + "Offset", + "SubRule", + "SubRuleCount", + 0, + "Array of offsets to SubRule tables-from beginning of SubRuleSet table-ordered by preference", + ), + ], + ), + ( + "SubRule", + [ + ( + "uint16", + "GlyphCount", + None, + None, + "Total number of glyphs in input glyph sequence-includes the first glyph", + ), + ("uint16", "SubstCount", None, None, "Number of SubstLookupRecords"), + ( + "GlyphID", + "Input", + "GlyphCount", + -1, + "Array of input GlyphIDs-start with second glyph", + ), + ( + "struct", + "SubstLookupRecord", + "SubstCount", + 0, + "Array of SubstLookupRecords-in design order", + ), + ], + ), + ( + "ContextSubstFormat2", + [ + ("uint16", "SubstFormat", None, None, "Format identifier-format = 2"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of Substitution table", + ), + ( + "Offset", + "ClassDef", + None, + None, + "Offset to glyph ClassDef table-from beginning of Substitution table", + ), + ("uint16", "SubClassSetCount", None, None, "Number of SubClassSet tables"), + ( + "Offset", + "SubClassSet", + "SubClassSetCount", + 0, + "Array of offsets to SubClassSet tables-from beginning of Substitution table-ordered by class-may be NULL", + ), + ], + ), + ( + "SubClassSet", + [ + ( + "uint16", + "SubClassRuleCount", + None, + None, + "Number of SubClassRule tables", + ), + ( + "Offset", + "SubClassRule", + "SubClassRuleCount", + 0, + "Array of offsets to SubClassRule tables-from beginning of SubClassSet-ordered by preference", + ), + ], + ), + ( + "SubClassRule", + [ + ( + "uint16", + "GlyphCount", + None, + None, + "Total number of classes specified for the context in the rule-includes the first class", + ), + ("uint16", "SubstCount", None, None, "Number of SubstLookupRecords"), + ( + "uint16", + "Class", + "GlyphCount", + -1, + "Array of classes-beginning with the second class-to be matched to the input glyph class sequence", + ), + ( + "struct", + "SubstLookupRecord", + "SubstCount", + 0, + "Array of Substitution lookups-in design order", + ), + ], + ), + ( + "ContextSubstFormat3", + [ + ("uint16", "SubstFormat", None, None, "Format identifier-format = 3"), + ( + "uint16", + "GlyphCount", + None, + None, + "Number of glyphs in the input glyph sequence", + ), + ("uint16", "SubstCount", None, None, "Number of SubstLookupRecords"), + ( + "Offset", + "Coverage", + "GlyphCount", + 0, + "Array of offsets to Coverage table-from beginning of Substitution table-in glyph sequence order", + ), + ( + "struct", + "SubstLookupRecord", + "SubstCount", + 0, + "Array of SubstLookupRecords-in design order", + ), + ], + ), + ( + "ChainContextSubstFormat1", + [ + ("uint16", "SubstFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of Substitution table", + ), + ( + "uint16", + "ChainSubRuleSetCount", + None, + None, + "Number of ChainSubRuleSet tables-must equal GlyphCount in Coverage table", + ), + ( + "Offset", + "ChainSubRuleSet", + "ChainSubRuleSetCount", + 0, + "Array of offsets to ChainSubRuleSet tables-from beginning of Substitution table-ordered by Coverage Index", + ), + ], + ), + ( + "ChainSubRuleSet", + [ + ( + "uint16", + "ChainSubRuleCount", + None, + None, + "Number of ChainSubRule tables", + ), + ( + "Offset", + "ChainSubRule", + "ChainSubRuleCount", + 0, + "Array of offsets to ChainSubRule tables-from beginning of ChainSubRuleSet table-ordered by preference", + ), + ], + ), + ( + "ChainSubRule", + [ + ( + "uint16", + "BacktrackGlyphCount", + None, + None, + "Total number of glyphs in the backtrack sequence (number of glyphs to be matched before the first glyph)", + ), + ( + "GlyphID", + "Backtrack", + "BacktrackGlyphCount", + 0, + "Array of backtracking GlyphID's (to be matched before the input sequence)", + ), + ( + "uint16", + "InputGlyphCount", + None, + None, + "Total number of glyphs in the input sequence (includes the first glyph)", + ), + ( + "GlyphID", + "Input", + "InputGlyphCount", + -1, + "Array of input GlyphIDs (start with second glyph)", + ), + ( + "uint16", + "LookAheadGlyphCount", + None, + None, + "Total number of glyphs in the look ahead sequence (number of glyphs to be matched after the input sequence)", + ), + ( + "GlyphID", + "LookAhead", + "LookAheadGlyphCount", + 0, + "Array of lookahead GlyphID's (to be matched after the input sequence)", + ), + ("uint16", "SubstCount", None, None, "Number of SubstLookupRecords"), + ( + "struct", + "SubstLookupRecord", + "SubstCount", + 0, + "Array of SubstLookupRecords (in design order)", + ), + ], + ), + ( + "ChainContextSubstFormat2", + [ + ("uint16", "SubstFormat", None, None, "Format identifier-format = 2"), + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table-from beginning of Substitution table", + ), + ( + "Offset", + "BacktrackClassDef", + None, + None, + "Offset to glyph ClassDef table containing backtrack sequence data-from beginning of Substitution table", + ), + ( + "Offset", + "InputClassDef", + None, + None, + "Offset to glyph ClassDef table containing input sequence data-from beginning of Substitution table", + ), + ( + "Offset", + "LookAheadClassDef", + None, + None, + "Offset to glyph ClassDef table containing lookahead sequence data-from beginning of Substitution table", + ), + ( + "uint16", + "ChainSubClassSetCount", + None, + None, + "Number of ChainSubClassSet tables", + ), + ( + "Offset", + "ChainSubClassSet", + "ChainSubClassSetCount", + 0, + "Array of offsets to ChainSubClassSet tables-from beginning of Substitution table-ordered by input class-may be NULL", + ), + ], + ), + ( + "ChainSubClassSet", + [ + ( + "uint16", + "ChainSubClassRuleCount", + None, + None, + "Number of ChainSubClassRule tables", + ), + ( + "Offset", + "ChainSubClassRule", + "ChainSubClassRuleCount", + 0, + "Array of offsets to ChainSubClassRule tables-from beginning of ChainSubClassSet-ordered by preference", + ), + ], + ), + ( + "ChainSubClassRule", + [ + ( + "uint16", + "BacktrackGlyphCount", + None, + None, + "Total number of glyphs in the backtrack sequence (number of glyphs to be matched before the first glyph)", + ), + ( + "uint16", + "Backtrack", + "BacktrackGlyphCount", + 0, + "Array of backtracking classes(to be matched before the input sequence)", + ), + ( + "uint16", + "InputGlyphCount", + None, + None, + "Total number of classes in the input sequence (includes the first class)", + ), + ( + "uint16", + "Input", + "InputGlyphCount", + -1, + "Array of input classes(start with second class; to be matched with the input glyph sequence)", + ), + ( + "uint16", + "LookAheadGlyphCount", + None, + None, + "Total number of classes in the look ahead sequence (number of classes to be matched after the input sequence)", + ), + ( + "uint16", + "LookAhead", + "LookAheadGlyphCount", + 0, + "Array of lookahead classes(to be matched after the input sequence)", + ), + ("uint16", "SubstCount", None, None, "Number of SubstLookupRecords"), + ( + "struct", + "SubstLookupRecord", + "SubstCount", + 0, + "Array of SubstLookupRecords (in design order)", + ), + ], + ), + ( + "ChainContextSubstFormat3", + [ + ("uint16", "SubstFormat", None, None, "Format identifier-format = 3"), + ( + "uint16", + "BacktrackGlyphCount", + None, + None, + "Number of glyphs in the backtracking sequence", + ), + ( + "Offset", + "BacktrackCoverage", + "BacktrackGlyphCount", + 0, + "Array of offsets to coverage tables in backtracking sequence, in glyph sequence order", + ), + ( + "uint16", + "InputGlyphCount", + None, + None, + "Number of glyphs in input sequence", + ), + ( + "Offset", + "InputCoverage", + "InputGlyphCount", + 0, + "Array of offsets to coverage tables in input sequence, in glyph sequence order", + ), + ( + "uint16", + "LookAheadGlyphCount", + None, + None, + "Number of glyphs in lookahead sequence", + ), + ( + "Offset", + "LookAheadCoverage", + "LookAheadGlyphCount", + 0, + "Array of offsets to coverage tables in lookahead sequence, in glyph sequence order", + ), + ("uint16", "SubstCount", None, None, "Number of SubstLookupRecords"), + ( + "struct", + "SubstLookupRecord", + "SubstCount", + 0, + "Array of SubstLookupRecords, in design order", + ), + ], + ), + ( + "ExtensionSubstFormat1", + [ + ("uint16", "ExtFormat", None, None, "Format identifier. Set to 1."), + ( + "uint16", + "ExtensionLookupType", + None, + None, + "Lookup type of subtable referenced by ExtensionOffset (i.e. the extension subtable).", + ), + ( + "LOffset", + "ExtSubTable", + None, + None, + "Array of offsets to Lookup tables-from beginning of LookupList -zero based (first lookup is Lookup index = 0)", + ), + ], + ), + ( + "ReverseChainSingleSubstFormat1", + [ + ("uint16", "SubstFormat", None, None, "Format identifier-format = 1"), + ( + "Offset", + "Coverage", + None, + 0, + "Offset to Coverage table - from beginning of Substitution table", + ), + ( + "uint16", + "BacktrackGlyphCount", + None, + None, + "Number of glyphs in the backtracking sequence", + ), + ( + "Offset", + "BacktrackCoverage", + "BacktrackGlyphCount", + 0, + "Array of offsets to coverage tables in backtracking sequence, in glyph sequence order", + ), + ( + "uint16", + "LookAheadGlyphCount", + None, + None, + "Number of glyphs in lookahead sequence", + ), + ( + "Offset", + "LookAheadCoverage", + "LookAheadGlyphCount", + 0, + "Array of offsets to coverage tables in lookahead sequence, in glyph sequence order", + ), + ( + "uint16", + "GlyphCount", + None, + None, + "Number of GlyphIDs in the Substitute array", + ), + ( + "GlyphID", + "Substitute", + "GlyphCount", + 0, + "Array of substitute GlyphIDs-ordered by Coverage index", + ), + ], + ), + # + # gdef + # + ( + "GDEF", + [ + ( + "Version", + "Version", + None, + None, + "Version of the GDEF table- 0x00010000, 0x00010002, or 0x00010003", + ), + ( + "Offset", + "GlyphClassDef", + None, + None, + "Offset to class definition table for glyph type-from beginning of GDEF header (may be NULL)", + ), + ( + "Offset", + "AttachList", + None, + None, + "Offset to list of glyphs with attachment points-from beginning of GDEF header (may be NULL)", + ), + ( + "Offset", + "LigCaretList", + None, + None, + "Offset to list of positioning points for ligature carets-from beginning of GDEF header (may be NULL)", + ), + ( + "Offset", + "MarkAttachClassDef", + None, + None, + "Offset to class definition table for mark attachment type-from beginning of GDEF header (may be NULL)", + ), + ( + "Offset", + "MarkGlyphSetsDef", + None, + "Version >= 0x00010002", + "Offset to the table of mark set definitions-from beginning of GDEF header (may be NULL)", + ), + ( + "LOffset", + "VarStore", + None, + "Version >= 0x00010003", + "Offset to variation store (may be NULL)", + ), + ], + ), + ( + "AttachList", + [ + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table - from beginning of AttachList table", + ), + ( + "uint16", + "GlyphCount", + None, + None, + "Number of glyphs with attachment points", + ), + ( + "Offset", + "AttachPoint", + "GlyphCount", + 0, + "Array of offsets to AttachPoint tables-from beginning of AttachList table-in Coverage Index order", + ), + ], + ), + ( + "AttachPoint", + [ + ( + "uint16", + "PointCount", + None, + None, + "Number of attachment points on this glyph", + ), + ( + "uint16", + "PointIndex", + "PointCount", + 0, + "Array of contour point indices -in increasing numerical order", + ), + ], + ), + ( + "LigCaretList", + [ + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table - from beginning of LigCaretList table", + ), + ("uint16", "LigGlyphCount", None, None, "Number of ligature glyphs"), + ( + "Offset", + "LigGlyph", + "LigGlyphCount", + 0, + "Array of offsets to LigGlyph tables-from beginning of LigCaretList table-in Coverage Index order", + ), + ], + ), + ( + "LigGlyph", + [ + ( + "uint16", + "CaretCount", + None, + None, + "Number of CaretValues for this ligature (components - 1)", + ), + ( + "Offset", + "CaretValue", + "CaretCount", + 0, + "Array of offsets to CaretValue tables-from beginning of LigGlyph table-in increasing coordinate order", + ), + ], + ), + ( + "CaretValueFormat1", + [ + ("uint16", "CaretValueFormat", None, None, "Format identifier-format = 1"), + ("int16", "Coordinate", None, None, "X or Y value, in design units"), + ], + ), + ( + "CaretValueFormat2", + [ + ("uint16", "CaretValueFormat", None, None, "Format identifier-format = 2"), + ("uint16", "CaretValuePoint", None, None, "Contour point index on glyph"), + ], + ), + ( + "CaretValueFormat3", + [ + ("uint16", "CaretValueFormat", None, None, "Format identifier-format = 3"), + ("int16", "Coordinate", None, None, "X or Y value, in design units"), + ( + "Offset", + "DeviceTable", + None, + None, + "Offset to Device table for X or Y value-from beginning of CaretValue table", + ), + ], + ), + ( + "MarkGlyphSetsDef", + [ + ("uint16", "MarkSetTableFormat", None, None, "Format identifier == 1"), + ("uint16", "MarkSetCount", None, None, "Number of mark sets defined"), + ( + "LOffset", + "Coverage", + "MarkSetCount", + 0, + "Array of offsets to mark set coverage tables.", + ), + ], + ), + # + # base + # + ( + "BASE", + [ + ( + "Version", + "Version", + None, + None, + "Version of the BASE table-initially 0x00010000", + ), + ( + "Offset", + "HorizAxis", + None, + None, + "Offset to horizontal Axis table-from beginning of BASE table-may be NULL", + ), + ( + "Offset", + "VertAxis", + None, + None, + "Offset to vertical Axis table-from beginning of BASE table-may be NULL", + ), + ( + "LOffset", + "VarStore", + None, + "Version >= 0x00010001", + "Offset to variation store (may be NULL)", + ), + ], + ), + ( + "Axis", + [ + ( + "Offset", + "BaseTagList", + None, + None, + "Offset to BaseTagList table-from beginning of Axis table-may be NULL", + ), + ( + "Offset", + "BaseScriptList", + None, + None, + "Offset to BaseScriptList table-from beginning of Axis table", + ), + ], + ), + ( + "BaseTagList", + [ + ( + "uint16", + "BaseTagCount", + None, + None, + "Number of baseline identification tags in this text direction-may be zero (0)", + ), + ( + "Tag", + "BaselineTag", + "BaseTagCount", + 0, + "Array of 4-byte baseline identification tags-must be in alphabetical order", + ), + ], + ), + ( + "BaseScriptList", + [ + ( + "uint16", + "BaseScriptCount", + None, + None, + "Number of BaseScriptRecords defined", + ), + ( + "struct", + "BaseScriptRecord", + "BaseScriptCount", + 0, + "Array of BaseScriptRecords-in alphabetical order by BaseScriptTag", + ), + ], + ), + ( + "BaseScriptRecord", + [ + ("Tag", "BaseScriptTag", None, None, "4-byte script identification tag"), + ( + "Offset", + "BaseScript", + None, + None, + "Offset to BaseScript table-from beginning of BaseScriptList", + ), + ], + ), + ( + "BaseScript", + [ + ( + "Offset", + "BaseValues", + None, + None, + "Offset to BaseValues table-from beginning of BaseScript table-may be NULL", + ), + ( + "Offset", + "DefaultMinMax", + None, + None, + "Offset to MinMax table- from beginning of BaseScript table-may be NULL", + ), + ( + "uint16", + "BaseLangSysCount", + None, + None, + "Number of BaseLangSysRecords defined-may be zero (0)", + ), + ( + "struct", + "BaseLangSysRecord", + "BaseLangSysCount", + 0, + "Array of BaseLangSysRecords-in alphabetical order by BaseLangSysTag", + ), + ], + ), + ( + "BaseLangSysRecord", + [ + ( + "Tag", + "BaseLangSysTag", + None, + None, + "4-byte language system identification tag", + ), + ( + "Offset", + "MinMax", + None, + None, + "Offset to MinMax table-from beginning of BaseScript table", + ), + ], + ), + ( + "BaseValues", + [ + ( + "uint16", + "DefaultIndex", + None, + None, + "Index number of default baseline for this script-equals index position of baseline tag in BaselineArray of the BaseTagList", + ), + ( + "uint16", + "BaseCoordCount", + None, + None, + "Number of BaseCoord tables defined-should equal BaseTagCount in the BaseTagList", + ), + ( + "Offset", + "BaseCoord", + "BaseCoordCount", + 0, + "Array of offsets to BaseCoord-from beginning of BaseValues table-order matches BaselineTag array in the BaseTagList", + ), + ], + ), + ( + "MinMax", + [ + ( + "Offset", + "MinCoord", + None, + None, + "Offset to BaseCoord table-defines minimum extent value-from the beginning of MinMax table-may be NULL", + ), + ( + "Offset", + "MaxCoord", + None, + None, + "Offset to BaseCoord table-defines maximum extent value-from the beginning of MinMax table-may be NULL", + ), + ( + "uint16", + "FeatMinMaxCount", + None, + None, + "Number of FeatMinMaxRecords-may be zero (0)", + ), + ( + "struct", + "FeatMinMaxRecord", + "FeatMinMaxCount", + 0, + "Array of FeatMinMaxRecords-in alphabetical order, by FeatureTableTag", + ), + ], + ), + ( + "FeatMinMaxRecord", + [ + ( + "Tag", + "FeatureTableTag", + None, + None, + "4-byte feature identification tag-must match FeatureTag in FeatureList", + ), + ( + "Offset", + "MinCoord", + None, + None, + "Offset to BaseCoord table-defines minimum extent value-from beginning of MinMax table-may be NULL", + ), + ( + "Offset", + "MaxCoord", + None, + None, + "Offset to BaseCoord table-defines maximum extent value-from beginning of MinMax table-may be NULL", + ), + ], + ), + ( + "BaseCoordFormat1", + [ + ("uint16", "BaseCoordFormat", None, None, "Format identifier-format = 1"), + ("int16", "Coordinate", None, None, "X or Y value, in design units"), + ], + ), + ( + "BaseCoordFormat2", + [ + ("uint16", "BaseCoordFormat", None, None, "Format identifier-format = 2"), + ("int16", "Coordinate", None, None, "X or Y value, in design units"), + ("GlyphID", "ReferenceGlyph", None, None, "GlyphID of control glyph"), + ( + "uint16", + "BaseCoordPoint", + None, + None, + "Index of contour point on the ReferenceGlyph", + ), + ], + ), + ( + "BaseCoordFormat3", + [ + ("uint16", "BaseCoordFormat", None, None, "Format identifier-format = 3"), + ("int16", "Coordinate", None, None, "X or Y value, in design units"), + ( + "Offset", + "DeviceTable", + None, + None, + "Offset to Device table for X or Y value", + ), + ], + ), + # + # jstf + # + ( + "JSTF", + [ + ( + "Version", + "Version", + None, + None, + "Version of the JSTF table-initially set to 0x00010000", + ), + ( + "uint16", + "JstfScriptCount", + None, + None, + "Number of JstfScriptRecords in this table", + ), + ( + "struct", + "JstfScriptRecord", + "JstfScriptCount", + 0, + "Array of JstfScriptRecords-in alphabetical order, by JstfScriptTag", + ), + ], + ), + ( + "JstfScriptRecord", + [ + ("Tag", "JstfScriptTag", None, None, "4-byte JstfScript identification"), + ( + "Offset", + "JstfScript", + None, + None, + "Offset to JstfScript table-from beginning of JSTF Header", + ), + ], + ), + ( + "JstfScript", + [ + ( + "Offset", + "ExtenderGlyph", + None, + None, + "Offset to ExtenderGlyph table-from beginning of JstfScript table-may be NULL", + ), + ( + "Offset", + "DefJstfLangSys", + None, + None, + "Offset to Default JstfLangSys table-from beginning of JstfScript table-may be NULL", + ), + ( + "uint16", + "JstfLangSysCount", + None, + None, + "Number of JstfLangSysRecords in this table- may be zero (0)", + ), + ( + "struct", + "JstfLangSysRecord", + "JstfLangSysCount", + 0, + "Array of JstfLangSysRecords-in alphabetical order, by JstfLangSysTag", + ), + ], + ), + ( + "JstfLangSysRecord", + [ + ("Tag", "JstfLangSysTag", None, None, "4-byte JstfLangSys identifier"), + ( + "Offset", + "JstfLangSys", + None, + None, + "Offset to JstfLangSys table-from beginning of JstfScript table", + ), + ], + ), + ( + "ExtenderGlyph", + [ + ( + "uint16", + "GlyphCount", + None, + None, + "Number of Extender Glyphs in this script", + ), + ( + "GlyphID", + "ExtenderGlyph", + "GlyphCount", + 0, + "GlyphIDs-in increasing numerical order", + ), + ], + ), + ( + "JstfLangSys", + [ + ( + "uint16", + "JstfPriorityCount", + None, + None, + "Number of JstfPriority tables", + ), + ( + "Offset", + "JstfPriority", + "JstfPriorityCount", + 0, + "Array of offsets to JstfPriority tables-from beginning of JstfLangSys table-in priority order", + ), + ], + ), + ( + "JstfPriority", + [ + ( + "Offset", + "ShrinkageEnableGSUB", + None, + None, + "Offset to Shrinkage Enable JstfGSUBModList table-from beginning of JstfPriority table-may be NULL", + ), + ( + "Offset", + "ShrinkageDisableGSUB", + None, + None, + "Offset to Shrinkage Disable JstfGSUBModList table-from beginning of JstfPriority table-may be NULL", + ), + ( + "Offset", + "ShrinkageEnableGPOS", + None, + None, + "Offset to Shrinkage Enable JstfGPOSModList table-from beginning of JstfPriority table-may be NULL", + ), + ( + "Offset", + "ShrinkageDisableGPOS", + None, + None, + "Offset to Shrinkage Disable JstfGPOSModList table-from beginning of JstfPriority table-may be NULL", + ), + ( + "Offset", + "ShrinkageJstfMax", + None, + None, + "Offset to Shrinkage JstfMax table-from beginning of JstfPriority table -may be NULL", + ), + ( + "Offset", + "ExtensionEnableGSUB", + None, + None, + "Offset to Extension Enable JstfGSUBModList table-may be NULL", + ), + ( + "Offset", + "ExtensionDisableGSUB", + None, + None, + "Offset to Extension Disable JstfGSUBModList table-from beginning of JstfPriority table-may be NULL", + ), + ( + "Offset", + "ExtensionEnableGPOS", + None, + None, + "Offset to Extension Enable JstfGSUBModList table-may be NULL", + ), + ( + "Offset", + "ExtensionDisableGPOS", + None, + None, + "Offset to Extension Disable JstfGSUBModList table-from beginning of JstfPriority table-may be NULL", + ), + ( + "Offset", + "ExtensionJstfMax", + None, + None, + "Offset to Extension JstfMax table-from beginning of JstfPriority table -may be NULL", + ), + ], + ), + ( + "JstfGSUBModList", + [ + ( + "uint16", + "LookupCount", + None, + None, + "Number of lookups for this modification", + ), + ( + "uint16", + "GSUBLookupIndex", + "LookupCount", + 0, + "Array of LookupIndex identifiers in GSUB-in increasing numerical order", + ), + ], + ), + ( + "JstfGPOSModList", + [ + ( + "uint16", + "LookupCount", + None, + None, + "Number of lookups for this modification", + ), + ( + "uint16", + "GPOSLookupIndex", + "LookupCount", + 0, + "Array of LookupIndex identifiers in GPOS-in increasing numerical order", + ), + ], + ), + ( + "JstfMax", + [ + ( + "uint16", + "LookupCount", + None, + None, + "Number of lookup Indices for this modification", + ), + ( + "Offset", + "Lookup", + "LookupCount", + 0, + "Array of offsets to GPOS-type lookup tables-from beginning of JstfMax table-in design order", + ), + ], + ), + # + # STAT + # + ( + "STAT", + [ + ( + "Version", + "Version", + None, + None, + "Version of the table-initially set to 0x00010000, currently 0x00010002.", + ), + ( + "uint16", + "DesignAxisRecordSize", + None, + None, + "Size in bytes of each design axis record", + ), + ("uint16", "DesignAxisCount", None, None, "Number of design axis records"), + ( + "LOffsetTo(AxisRecordArray)", + "DesignAxisRecord", + None, + None, + "Offset in bytes from the beginning of the STAT table to the start of the design axes array", + ), + ("uint16", "AxisValueCount", None, None, "Number of axis value tables"), + ( + "LOffsetTo(AxisValueArray)", + "AxisValueArray", + None, + None, + "Offset in bytes from the beginning of the STAT table to the start of the axes value offset array", + ), + ( + "NameID", + "ElidedFallbackNameID", + None, + "Version >= 0x00010001", + "NameID to use when all style attributes are elided.", + ), + ], + ), + ( + "AxisRecordArray", + [ + ("AxisRecord", "Axis", "DesignAxisCount", 0, "Axis records"), + ], + ), + ( + "AxisRecord", + [ + ( + "Tag", + "AxisTag", + None, + None, + "A tag identifying the axis of design variation", + ), + ( + "NameID", + "AxisNameID", + None, + None, + 'The name ID for entries in the "name" table that provide a display string for this axis', + ), + ( + "uint16", + "AxisOrdering", + None, + None, + "A value that applications can use to determine primary sorting of face names, or for ordering of descriptors when composing family or face names", + ), + ( + "uint8", + "MoreBytes", + "DesignAxisRecordSize", + -8, + "Extra bytes. Set to empty array.", + ), + ], + ), + ( + "AxisValueArray", + [ + ("Offset", "AxisValue", "AxisValueCount", 0, "Axis values"), + ], + ), + ( + "AxisValueFormat1", + [ + ("uint16", "Format", None, None, "Format, = 1"), + ( + "uint16", + "AxisIndex", + None, + None, + "Index into the axis record array identifying the axis of design variation to which the axis value record applies.", + ), + ("STATFlags", "Flags", None, None, "Flags."), + ("NameID", "ValueNameID", None, None, ""), + ("Fixed", "Value", None, None, ""), + ], + ), + ( + "AxisValueFormat2", + [ + ("uint16", "Format", None, None, "Format, = 2"), + ( + "uint16", + "AxisIndex", + None, + None, + "Index into the axis record array identifying the axis of design variation to which the axis value record applies.", + ), + ("STATFlags", "Flags", None, None, "Flags."), + ("NameID", "ValueNameID", None, None, ""), + ("Fixed", "NominalValue", None, None, ""), + ("Fixed", "RangeMinValue", None, None, ""), + ("Fixed", "RangeMaxValue", None, None, ""), + ], + ), + ( + "AxisValueFormat3", + [ + ("uint16", "Format", None, None, "Format, = 3"), + ( + "uint16", + "AxisIndex", + None, + None, + "Index into the axis record array identifying the axis of design variation to which the axis value record applies.", + ), + ("STATFlags", "Flags", None, None, "Flags."), + ("NameID", "ValueNameID", None, None, ""), + ("Fixed", "Value", None, None, ""), + ("Fixed", "LinkedValue", None, None, ""), + ], + ), + ( + "AxisValueFormat4", + [ + ("uint16", "Format", None, None, "Format, = 4"), + ( + "uint16", + "AxisCount", + None, + None, + "The total number of axes contributing to this axis-values combination.", + ), + ("STATFlags", "Flags", None, None, "Flags."), + ("NameID", "ValueNameID", None, None, ""), + ( + "struct", + "AxisValueRecord", + "AxisCount", + 0, + "Array of AxisValue records that provide the combination of axis values, one for each contributing axis. ", + ), + ], + ), + ( + "AxisValueRecord", + [ + ( + "uint16", + "AxisIndex", + None, + None, + "Index into the axis record array identifying the axis of design variation to which the axis value record applies.", + ), + ("Fixed", "Value", None, None, "A numeric value for this attribute value."), + ], + ), + # + # Variation fonts + # + # GSUB/GPOS FeatureVariations + ( + "FeatureVariations", + [ + ( + "Version", + "Version", + None, + None, + "Version of the table-initially set to 0x00010000", + ), + ( + "uint32", + "FeatureVariationCount", + None, + None, + "Number of records in the FeatureVariationRecord array", + ), + ( + "struct", + "FeatureVariationRecord", + "FeatureVariationCount", + 0, + "Array of FeatureVariationRecord", + ), + ], + ), + ( + "FeatureVariationRecord", + [ + ( + "LOffset", + "ConditionSet", + None, + None, + "Offset to a ConditionSet table, from beginning of the FeatureVariations table.", + ), + ( + "LOffset", + "FeatureTableSubstitution", + None, + None, + "Offset to a FeatureTableSubstitution table, from beginning of the FeatureVariations table", + ), + ], + ), + ( + "ConditionSet", + [ + ( + "uint16", + "ConditionCount", + None, + None, + "Number of condition tables in the ConditionTable array", + ), + ( + "LOffset", + "ConditionTable", + "ConditionCount", + 0, + "Array of condition tables.", + ), + ], + ), + ( + "ConditionTableFormat1", + [ + ("uint16", "Format", None, None, "Format, = 1"), + ( + "uint16", + "AxisIndex", + None, + None, + "Index for the variation axis within the fvar table, base 0.", + ), + ( + "F2Dot14", + "FilterRangeMinValue", + None, + None, + "Minimum normalized axis value of the font variation instances that satisfy this condition.", + ), + ( + "F2Dot14", + "FilterRangeMaxValue", + None, + None, + "Maximum value that satisfies this condition.", + ), + ], + ), + ( + "FeatureTableSubstitution", + [ + ( + "Version", + "Version", + None, + None, + "Version of the table-initially set to 0x00010000", + ), + ( + "uint16", + "SubstitutionCount", + None, + None, + "Number of records in the FeatureVariationRecords array", + ), + ( + "FeatureTableSubstitutionRecord", + "SubstitutionRecord", + "SubstitutionCount", + 0, + "Array of FeatureTableSubstitutionRecord", + ), + ], + ), + ( + "FeatureTableSubstitutionRecord", + [ + ("uint16", "FeatureIndex", None, None, "The feature table index to match."), + ( + "LOffset", + "Feature", + None, + None, + "Offset to an alternate feature table, from start of the FeatureTableSubstitution table.", + ), + ], + ), + # VariationStore + ( + "VarRegionAxis", + [ + ("F2Dot14", "StartCoord", None, None, ""), + ("F2Dot14", "PeakCoord", None, None, ""), + ("F2Dot14", "EndCoord", None, None, ""), + ], + ), + ( + "VarRegion", + [ + ("struct", "VarRegionAxis", "RegionAxisCount", 0, ""), + ], + ), + ( + "VarRegionList", + [ + ("uint16", "RegionAxisCount", None, None, ""), + ("uint16", "RegionCount", None, None, ""), + ("VarRegion", "Region", "RegionCount", 0, ""), + ], + ), + ( + "VarData", + [ + ("uint16", "ItemCount", None, None, ""), + ("uint16", "NumShorts", None, None, ""), + ("uint16", "VarRegionCount", None, None, ""), + ("uint16", "VarRegionIndex", "VarRegionCount", 0, ""), + ("VarDataValue", "Item", "ItemCount", 0, ""), + ], + ), + ( + "VarStore", + [ + ("uint16", "Format", None, None, "Set to 1."), + ("LOffset", "VarRegionList", None, None, ""), + ("uint16", "VarDataCount", None, None, ""), + ("LOffset", "VarData", "VarDataCount", 0, ""), + ], + ), + # Variation helpers + ( + "VarIdxMap", + [ + ("uint16", "EntryFormat", None, None, ""), # Automatically computed + ("uint16", "MappingCount", None, None, ""), # Automatically computed + ("VarIdxMapValue", "mapping", "", 0, "Array of compressed data"), + ], + ), + ( + "DeltaSetIndexMapFormat0", + [ + ("uint8", "Format", None, None, "Format of the DeltaSetIndexMap = 0"), + ("uint8", "EntryFormat", None, None, ""), # Automatically computed + ("uint16", "MappingCount", None, None, ""), # Automatically computed + ("VarIdxMapValue", "mapping", "", 0, "Array of compressed data"), + ], + ), + ( + "DeltaSetIndexMapFormat1", + [ + ("uint8", "Format", None, None, "Format of the DeltaSetIndexMap = 1"), + ("uint8", "EntryFormat", None, None, ""), # Automatically computed + ("uint32", "MappingCount", None, None, ""), # Automatically computed + ("VarIdxMapValue", "mapping", "", 0, "Array of compressed data"), + ], + ), + # Glyph advance variations + ( + "HVAR", + [ + ( + "Version", + "Version", + None, + None, + "Version of the HVAR table-initially = 0x00010000", + ), + ("LOffset", "VarStore", None, None, ""), + ("LOffsetTo(VarIdxMap)", "AdvWidthMap", None, None, ""), + ("LOffsetTo(VarIdxMap)", "LsbMap", None, None, ""), + ("LOffsetTo(VarIdxMap)", "RsbMap", None, None, ""), + ], + ), + ( + "VVAR", + [ + ( + "Version", + "Version", + None, + None, + "Version of the VVAR table-initially = 0x00010000", + ), + ("LOffset", "VarStore", None, None, ""), + ("LOffsetTo(VarIdxMap)", "AdvHeightMap", None, None, ""), + ("LOffsetTo(VarIdxMap)", "TsbMap", None, None, ""), + ("LOffsetTo(VarIdxMap)", "BsbMap", None, None, ""), + ("LOffsetTo(VarIdxMap)", "VOrgMap", None, None, "Vertical origin mapping."), + ], + ), + # Font-wide metrics variations + ( + "MetricsValueRecord", + [ + ("Tag", "ValueTag", None, None, "4-byte font-wide measure identifier"), + ("uint32", "VarIdx", None, None, "Combined outer-inner variation index"), + ( + "uint8", + "MoreBytes", + "ValueRecordSize", + -8, + "Extra bytes. Set to empty array.", + ), + ], + ), + ( + "MVAR", + [ + ( + "Version", + "Version", + None, + None, + "Version of the MVAR table-initially = 0x00010000", + ), + ("uint16", "Reserved", None, None, "Set to 0"), + ("uint16", "ValueRecordSize", None, None, ""), + ("uint16", "ValueRecordCount", None, None, ""), + ("Offset", "VarStore", None, None, ""), + ("MetricsValueRecord", "ValueRecord", "ValueRecordCount", 0, ""), + ], + ), + # + # math + # + ( + "MATH", + [ + ( + "Version", + "Version", + None, + None, + "Version of the MATH table-initially set to 0x00010000.", + ), + ( + "Offset", + "MathConstants", + None, + None, + "Offset to MathConstants table - from the beginning of MATH table.", + ), + ( + "Offset", + "MathGlyphInfo", + None, + None, + "Offset to MathGlyphInfo table - from the beginning of MATH table.", + ), + ( + "Offset", + "MathVariants", + None, + None, + "Offset to MathVariants table - from the beginning of MATH table.", + ), + ], + ), + ( + "MathValueRecord", + [ + ("int16", "Value", None, None, "The X or Y value in design units."), + ( + "Offset", + "DeviceTable", + None, + None, + "Offset to the device table - from the beginning of parent table. May be NULL. Suggested format for device table is 1.", + ), + ], + ), + ( + "MathConstants", + [ + ( + "int16", + "ScriptPercentScaleDown", + None, + None, + "Percentage of scaling down for script level 1. Suggested value: 80%.", + ), + ( + "int16", + "ScriptScriptPercentScaleDown", + None, + None, + "Percentage of scaling down for script level 2 (ScriptScript). Suggested value: 60%.", + ), + ( + "uint16", + "DelimitedSubFormulaMinHeight", + None, + None, + "Minimum height required for a delimited expression to be treated as a subformula. Suggested value: normal line height x1.5.", + ), + ( + "uint16", + "DisplayOperatorMinHeight", + None, + None, + "Minimum height of n-ary operators (such as integral and summation) for formulas in display mode.", + ), + ( + "MathValueRecord", + "MathLeading", + None, + None, + "White space to be left between math formulas to ensure proper line spacing. For example, for applications that treat line gap as a part of line ascender, formulas with ink going above (os2.sTypoAscender + os2.sTypoLineGap - MathLeading) or with ink going below os2.sTypoDescender will result in increasing line height.", + ), + ("MathValueRecord", "AxisHeight", None, None, "Axis height of the font."), + ( + "MathValueRecord", + "AccentBaseHeight", + None, + None, + "Maximum (ink) height of accent base that does not require raising the accents. Suggested: x-height of the font (os2.sxHeight) plus any possible overshots.", + ), + ( + "MathValueRecord", + "FlattenedAccentBaseHeight", + None, + None, + "Maximum (ink) height of accent base that does not require flattening the accents. Suggested: cap height of the font (os2.sCapHeight).", + ), + ( + "MathValueRecord", + "SubscriptShiftDown", + None, + None, + "The standard shift down applied to subscript elements. Positive for moving in the downward direction. Suggested: os2.ySubscriptYOffset.", + ), + ( + "MathValueRecord", + "SubscriptTopMax", + None, + None, + "Maximum allowed height of the (ink) top of subscripts that does not require moving subscripts further down. Suggested: 4/5 x-height.", + ), + ( + "MathValueRecord", + "SubscriptBaselineDropMin", + None, + None, + "Minimum allowed drop of the baseline of subscripts relative to the (ink) bottom of the base. Checked for bases that are treated as a box or extended shape. Positive for subscript baseline dropped below the base bottom.", + ), + ( + "MathValueRecord", + "SuperscriptShiftUp", + None, + None, + "Standard shift up applied to superscript elements. Suggested: os2.ySuperscriptYOffset.", + ), + ( + "MathValueRecord", + "SuperscriptShiftUpCramped", + None, + None, + "Standard shift of superscripts relative to the base, in cramped style.", + ), + ( + "MathValueRecord", + "SuperscriptBottomMin", + None, + None, + "Minimum allowed height of the (ink) bottom of superscripts that does not require moving subscripts further up. Suggested: 1/4 x-height.", + ), + ( + "MathValueRecord", + "SuperscriptBaselineDropMax", + None, + None, + "Maximum allowed drop of the baseline of superscripts relative to the (ink) top of the base. Checked for bases that are treated as a box or extended shape. Positive for superscript baseline below the base top.", + ), + ( + "MathValueRecord", + "SubSuperscriptGapMin", + None, + None, + "Minimum gap between the superscript and subscript ink. Suggested: 4x default rule thickness.", + ), + ( + "MathValueRecord", + "SuperscriptBottomMaxWithSubscript", + None, + None, + "The maximum level to which the (ink) bottom of superscript can be pushed to increase the gap between superscript and subscript, before subscript starts being moved down. Suggested: 4/5 x-height.", + ), + ( + "MathValueRecord", + "SpaceAfterScript", + None, + None, + "Extra white space to be added after each subscript and superscript. Suggested: 0.5pt for a 12 pt font.", + ), + ( + "MathValueRecord", + "UpperLimitGapMin", + None, + None, + "Minimum gap between the (ink) bottom of the upper limit, and the (ink) top of the base operator.", + ), + ( + "MathValueRecord", + "UpperLimitBaselineRiseMin", + None, + None, + "Minimum distance between baseline of upper limit and (ink) top of the base operator.", + ), + ( + "MathValueRecord", + "LowerLimitGapMin", + None, + None, + "Minimum gap between (ink) top of the lower limit, and (ink) bottom of the base operator.", + ), + ( + "MathValueRecord", + "LowerLimitBaselineDropMin", + None, + None, + "Minimum distance between baseline of the lower limit and (ink) bottom of the base operator.", + ), + ( + "MathValueRecord", + "StackTopShiftUp", + None, + None, + "Standard shift up applied to the top element of a stack.", + ), + ( + "MathValueRecord", + "StackTopDisplayStyleShiftUp", + None, + None, + "Standard shift up applied to the top element of a stack in display style.", + ), + ( + "MathValueRecord", + "StackBottomShiftDown", + None, + None, + "Standard shift down applied to the bottom element of a stack. Positive for moving in the downward direction.", + ), + ( + "MathValueRecord", + "StackBottomDisplayStyleShiftDown", + None, + None, + "Standard shift down applied to the bottom element of a stack in display style. Positive for moving in the downward direction.", + ), + ( + "MathValueRecord", + "StackGapMin", + None, + None, + "Minimum gap between (ink) bottom of the top element of a stack, and the (ink) top of the bottom element. Suggested: 3x default rule thickness.", + ), + ( + "MathValueRecord", + "StackDisplayStyleGapMin", + None, + None, + "Minimum gap between (ink) bottom of the top element of a stack, and the (ink) top of the bottom element in display style. Suggested: 7x default rule thickness.", + ), + ( + "MathValueRecord", + "StretchStackTopShiftUp", + None, + None, + "Standard shift up applied to the top element of the stretch stack.", + ), + ( + "MathValueRecord", + "StretchStackBottomShiftDown", + None, + None, + "Standard shift down applied to the bottom element of the stretch stack. Positive for moving in the downward direction.", + ), + ( + "MathValueRecord", + "StretchStackGapAboveMin", + None, + None, + "Minimum gap between the ink of the stretched element, and the (ink) bottom of the element above. Suggested: UpperLimitGapMin", + ), + ( + "MathValueRecord", + "StretchStackGapBelowMin", + None, + None, + "Minimum gap between the ink of the stretched element, and the (ink) top of the element below. Suggested: LowerLimitGapMin.", + ), + ( + "MathValueRecord", + "FractionNumeratorShiftUp", + None, + None, + "Standard shift up applied to the numerator.", + ), + ( + "MathValueRecord", + "FractionNumeratorDisplayStyleShiftUp", + None, + None, + "Standard shift up applied to the numerator in display style. Suggested: StackTopDisplayStyleShiftUp.", + ), + ( + "MathValueRecord", + "FractionDenominatorShiftDown", + None, + None, + "Standard shift down applied to the denominator. Positive for moving in the downward direction.", + ), + ( + "MathValueRecord", + "FractionDenominatorDisplayStyleShiftDown", + None, + None, + "Standard shift down applied to the denominator in display style. Positive for moving in the downward direction. Suggested: StackBottomDisplayStyleShiftDown.", + ), + ( + "MathValueRecord", + "FractionNumeratorGapMin", + None, + None, + "Minimum tolerated gap between the (ink) bottom of the numerator and the ink of the fraction bar. Suggested: default rule thickness", + ), + ( + "MathValueRecord", + "FractionNumDisplayStyleGapMin", + None, + None, + "Minimum tolerated gap between the (ink) bottom of the numerator and the ink of the fraction bar in display style. Suggested: 3x default rule thickness.", + ), + ( + "MathValueRecord", + "FractionRuleThickness", + None, + None, + "Thickness of the fraction bar. Suggested: default rule thickness.", + ), + ( + "MathValueRecord", + "FractionDenominatorGapMin", + None, + None, + "Minimum tolerated gap between the (ink) top of the denominator and the ink of the fraction bar. Suggested: default rule thickness", + ), + ( + "MathValueRecord", + "FractionDenomDisplayStyleGapMin", + None, + None, + "Minimum tolerated gap between the (ink) top of the denominator and the ink of the fraction bar in display style. Suggested: 3x default rule thickness.", + ), + ( + "MathValueRecord", + "SkewedFractionHorizontalGap", + None, + None, + "Horizontal distance between the top and bottom elements of a skewed fraction.", + ), + ( + "MathValueRecord", + "SkewedFractionVerticalGap", + None, + None, + "Vertical distance between the ink of the top and bottom elements of a skewed fraction.", + ), + ( + "MathValueRecord", + "OverbarVerticalGap", + None, + None, + "Distance between the overbar and the (ink) top of he base. Suggested: 3x default rule thickness.", + ), + ( + "MathValueRecord", + "OverbarRuleThickness", + None, + None, + "Thickness of overbar. Suggested: default rule thickness.", + ), + ( + "MathValueRecord", + "OverbarExtraAscender", + None, + None, + "Extra white space reserved above the overbar. Suggested: default rule thickness.", + ), + ( + "MathValueRecord", + "UnderbarVerticalGap", + None, + None, + "Distance between underbar and (ink) bottom of the base. Suggested: 3x default rule thickness.", + ), + ( + "MathValueRecord", + "UnderbarRuleThickness", + None, + None, + "Thickness of underbar. Suggested: default rule thickness.", + ), + ( + "MathValueRecord", + "UnderbarExtraDescender", + None, + None, + "Extra white space reserved below the underbar. Always positive. Suggested: default rule thickness.", + ), + ( + "MathValueRecord", + "RadicalVerticalGap", + None, + None, + "Space between the (ink) top of the expression and the bar over it. Suggested: 1 1/4 default rule thickness.", + ), + ( + "MathValueRecord", + "RadicalDisplayStyleVerticalGap", + None, + None, + "Space between the (ink) top of the expression and the bar over it. Suggested: default rule thickness + 1/4 x-height.", + ), + ( + "MathValueRecord", + "RadicalRuleThickness", + None, + None, + "Thickness of the radical rule. This is the thickness of the rule in designed or constructed radical signs. Suggested: default rule thickness.", + ), + ( + "MathValueRecord", + "RadicalExtraAscender", + None, + None, + "Extra white space reserved above the radical. Suggested: RadicalRuleThickness.", + ), + ( + "MathValueRecord", + "RadicalKernBeforeDegree", + None, + None, + "Extra horizontal kern before the degree of a radical, if such is present. Suggested: 5/18 of em.", + ), + ( + "MathValueRecord", + "RadicalKernAfterDegree", + None, + None, + "Negative kern after the degree of a radical, if such is present. Suggested: 10/18 of em.", + ), + ( + "uint16", + "RadicalDegreeBottomRaisePercent", + None, + None, + "Height of the bottom of the radical degree, if such is present, in proportion to the ascender of the radical sign. Suggested: 60%.", + ), + ], + ), + ( + "MathGlyphInfo", + [ + ( + "Offset", + "MathItalicsCorrectionInfo", + None, + None, + "Offset to MathItalicsCorrectionInfo table - from the beginning of MathGlyphInfo table.", + ), + ( + "Offset", + "MathTopAccentAttachment", + None, + None, + "Offset to MathTopAccentAttachment table - from the beginning of MathGlyphInfo table.", + ), + ( + "Offset", + "ExtendedShapeCoverage", + None, + None, + "Offset to coverage table for Extended Shape glyphs - from the beginning of MathGlyphInfo table. When the left or right glyph of a box is an extended shape variant, the (ink) box (and not the default position defined by values in MathConstants table) should be used for vertical positioning purposes. May be NULL.", + ), + ( + "Offset", + "MathKernInfo", + None, + None, + "Offset to MathKernInfo table - from the beginning of MathGlyphInfo table.", + ), + ], + ), + ( + "MathItalicsCorrectionInfo", + [ + ( + "Offset", + "Coverage", + None, + None, + "Offset to Coverage table - from the beginning of MathItalicsCorrectionInfo table.", + ), + ( + "uint16", + "ItalicsCorrectionCount", + None, + None, + "Number of italics correction values. Should coincide with the number of covered glyphs.", + ), + ( + "MathValueRecord", + "ItalicsCorrection", + "ItalicsCorrectionCount", + 0, + "Array of MathValueRecords defining italics correction values for each covered glyph.", + ), + ], + ), + ( + "MathTopAccentAttachment", + [ + ( + "Offset", + "TopAccentCoverage", + None, + None, + "Offset to Coverage table - from the beginning of MathTopAccentAttachment table.", + ), + ( + "uint16", + "TopAccentAttachmentCount", + None, + None, + "Number of top accent attachment point values. Should coincide with the number of covered glyphs", + ), + ( + "MathValueRecord", + "TopAccentAttachment", + "TopAccentAttachmentCount", + 0, + "Array of MathValueRecords defining top accent attachment points for each covered glyph", + ), + ], + ), + ( + "MathKernInfo", + [ + ( + "Offset", + "MathKernCoverage", + None, + None, + "Offset to Coverage table - from the beginning of the MathKernInfo table.", + ), + ("uint16", "MathKernCount", None, None, "Number of MathKernInfoRecords."), + ( + "MathKernInfoRecord", + "MathKernInfoRecords", + "MathKernCount", + 0, + "Array of MathKernInfoRecords, per-glyph information for mathematical positioning of subscripts and superscripts.", + ), + ], + ), + ( + "MathKernInfoRecord", + [ + ( + "Offset", + "TopRightMathKern", + None, + None, + "Offset to MathKern table for top right corner - from the beginning of MathKernInfo table. May be NULL.", + ), + ( + "Offset", + "TopLeftMathKern", + None, + None, + "Offset to MathKern table for the top left corner - from the beginning of MathKernInfo table. May be NULL.", + ), + ( + "Offset", + "BottomRightMathKern", + None, + None, + "Offset to MathKern table for bottom right corner - from the beginning of MathKernInfo table. May be NULL.", + ), + ( + "Offset", + "BottomLeftMathKern", + None, + None, + "Offset to MathKern table for bottom left corner - from the beginning of MathKernInfo table. May be NULL.", + ), + ], + ), + ( + "MathKern", + [ + ( + "uint16", + "HeightCount", + None, + None, + "Number of heights on which the kern value changes.", + ), + ( + "MathValueRecord", + "CorrectionHeight", + "HeightCount", + 0, + "Array of correction heights at which the kern value changes. Sorted by the height value in design units.", + ), + ( + "MathValueRecord", + "KernValue", + "HeightCount", + 1, + "Array of kern values corresponding to heights. First value is the kern value for all heights less or equal than the first height in this table.Last value is the value to be applied for all heights greater than the last height in this table. Negative values are interpreted as move glyphs closer to each other.", + ), + ], + ), + ( + "MathVariants", + [ + ( + "uint16", + "MinConnectorOverlap", + None, + None, + "Minimum overlap of connecting glyphs during glyph construction, in design units.", + ), + ( + "Offset", + "VertGlyphCoverage", + None, + None, + "Offset to Coverage table - from the beginning of MathVariants table.", + ), + ( + "Offset", + "HorizGlyphCoverage", + None, + None, + "Offset to Coverage table - from the beginning of MathVariants table.", + ), + ( + "uint16", + "VertGlyphCount", + None, + None, + "Number of glyphs for which information is provided for vertically growing variants.", + ), + ( + "uint16", + "HorizGlyphCount", + None, + None, + "Number of glyphs for which information is provided for horizontally growing variants.", + ), + ( + "Offset", + "VertGlyphConstruction", + "VertGlyphCount", + 0, + "Array of offsets to MathGlyphConstruction tables - from the beginning of the MathVariants table, for shapes growing in vertical direction.", + ), + ( + "Offset", + "HorizGlyphConstruction", + "HorizGlyphCount", + 0, + "Array of offsets to MathGlyphConstruction tables - from the beginning of the MathVariants table, for shapes growing in horizontal direction.", + ), + ], + ), + ( + "MathGlyphConstruction", + [ + ( + "Offset", + "GlyphAssembly", + None, + None, + "Offset to GlyphAssembly table for this shape - from the beginning of MathGlyphConstruction table. May be NULL", + ), + ( + "uint16", + "VariantCount", + None, + None, + "Count of glyph growing variants for this glyph.", + ), + ( + "MathGlyphVariantRecord", + "MathGlyphVariantRecord", + "VariantCount", + 0, + "MathGlyphVariantRecords for alternative variants of the glyphs.", + ), + ], + ), + ( + "MathGlyphVariantRecord", + [ + ("GlyphID", "VariantGlyph", None, None, "Glyph ID for the variant."), + ( + "uint16", + "AdvanceMeasurement", + None, + None, + "Advance width/height, in design units, of the variant, in the direction of requested glyph extension.", + ), + ], + ), + ( + "GlyphAssembly", + [ + ( + "MathValueRecord", + "ItalicsCorrection", + None, + None, + "Italics correction of this GlyphAssembly. Should not depend on the assembly size.", + ), + ("uint16", "PartCount", None, None, "Number of parts in this assembly."), + ( + "GlyphPartRecord", + "PartRecords", + "PartCount", + 0, + "Array of part records, from left to right and bottom to top.", + ), + ], + ), + ( + "GlyphPartRecord", + [ + ("GlyphID", "glyph", None, None, "Glyph ID for the part."), + ( + "uint16", + "StartConnectorLength", + None, + None, + "Advance width/ height of the straight bar connector material, in design units, is at the beginning of the glyph, in the direction of the extension.", + ), + ( + "uint16", + "EndConnectorLength", + None, + None, + "Advance width/ height of the straight bar connector material, in design units, is at the end of the glyph, in the direction of the extension.", + ), + ( + "uint16", + "FullAdvance", + None, + None, + "Full advance width/height for this part, in the direction of the extension. In design units.", + ), + ( + "uint16", + "PartFlags", + None, + None, + "Part qualifiers. PartFlags enumeration currently uses only one bit: 0x0001 fExtender: If set, the part can be skipped or repeated. 0xFFFE Reserved", + ), + ], + ), + ## + ## Apple Advanced Typography (AAT) tables + ## + ( + "AATLookupSegment", + [ + ("uint16", "lastGlyph", None, None, "Last glyph index in this segment."), + ("uint16", "firstGlyph", None, None, "First glyph index in this segment."), + ( + "uint16", + "value", + None, + None, + "A 16-bit offset from the start of the table to the data.", + ), + ], + ), + # + # ankr + # + ( + "ankr", + [ + ("struct", "AnchorPoints", None, None, "Anchor points table."), + ], + ), + ( + "AnchorPointsFormat0", + [ + ("uint16", "Format", None, None, "Format of the anchor points table, = 0."), + ("uint16", "Flags", None, None, "Flags. Currenty unused, set to zero."), + ( + "AATLookupWithDataOffset(AnchorGlyphData)", + "Anchors", + None, + None, + "Table of with anchor overrides for each glyph.", + ), + ], + ), + ( + "AnchorGlyphData", + [ + ( + "uint32", + "AnchorPointCount", + None, + None, + "Number of anchor points for this glyph.", + ), + ( + "struct", + "AnchorPoint", + "AnchorPointCount", + 0, + "Individual anchor points.", + ), + ], + ), + ( + "AnchorPoint", + [ + ("int16", "XCoordinate", None, None, "X coordinate of this anchor point."), + ("int16", "YCoordinate", None, None, "Y coordinate of this anchor point."), + ], + ), + # + # bsln + # + ( + "bsln", + [ + ( + "Version", + "Version", + None, + None, + "Version number of the AAT baseline table (0x00010000 for the initial version).", + ), + ("struct", "Baseline", None, None, "Baseline table."), + ], + ), + ( + "BaselineFormat0", + [ + ("uint16", "Format", None, None, "Format of the baseline table, = 0."), + ( + "uint16", + "DefaultBaseline", + None, + None, + "Default baseline value for all glyphs. This value can be from 0 through 31.", + ), + ( + "uint16", + "Delta", + 32, + 0, + "These are the FUnit distance deltas from the font’s natural baseline to the other baselines used in the font. A total of 32 deltas must be assigned.", + ), + ], + ), + ( + "BaselineFormat1", + [ + ("uint16", "Format", None, None, "Format of the baseline table, = 1."), + ( + "uint16", + "DefaultBaseline", + None, + None, + "Default baseline value for all glyphs. This value can be from 0 through 31.", + ), + ( + "uint16", + "Delta", + 32, + 0, + "These are the FUnit distance deltas from the font’s natural baseline to the other baselines used in the font. A total of 32 deltas must be assigned.", + ), + ( + "AATLookup(uint16)", + "BaselineValues", + None, + None, + "Lookup table that maps glyphs to their baseline values.", + ), + ], + ), + ( + "BaselineFormat2", + [ + ("uint16", "Format", None, None, "Format of the baseline table, = 1."), + ( + "uint16", + "DefaultBaseline", + None, + None, + "Default baseline value for all glyphs. This value can be from 0 through 31.", + ), + ( + "GlyphID", + "StandardGlyph", + None, + None, + "Glyph index of the glyph in this font to be used to set the baseline values. This glyph must contain a set of control points (whose numbers are contained in the following field) that determines baseline distances.", + ), + ( + "uint16", + "ControlPoint", + 32, + 0, + "Array of 32 control point numbers, associated with the standard glyph. A value of 0xFFFF means there is no corresponding control point in the standard glyph.", + ), + ], + ), + ( + "BaselineFormat3", + [ + ("uint16", "Format", None, None, "Format of the baseline table, = 1."), + ( + "uint16", + "DefaultBaseline", + None, + None, + "Default baseline value for all glyphs. This value can be from 0 through 31.", + ), + ( + "GlyphID", + "StandardGlyph", + None, + None, + "Glyph index of the glyph in this font to be used to set the baseline values. This glyph must contain a set of control points (whose numbers are contained in the following field) that determines baseline distances.", + ), + ( + "uint16", + "ControlPoint", + 32, + 0, + "Array of 32 control point numbers, associated with the standard glyph. A value of 0xFFFF means there is no corresponding control point in the standard glyph.", + ), + ( + "AATLookup(uint16)", + "BaselineValues", + None, + None, + "Lookup table that maps glyphs to their baseline values.", + ), + ], + ), + # + # cidg + # + ( + "cidg", + [ + ("struct", "CIDGlyphMapping", None, None, "CID-to-glyph mapping table."), + ], + ), + ( + "CIDGlyphMappingFormat0", + [ + ( + "uint16", + "Format", + None, + None, + "Format of the CID-to-glyph mapping table, = 0.", + ), + ("uint16", "DataFormat", None, None, "Currenty unused, set to zero."), + ("uint32", "StructLength", None, None, "Size of the table in bytes."), + ("uint16", "Registry", None, None, "The registry ID."), + ( + "char64", + "RegistryName", + None, + None, + "The registry name in ASCII; unused bytes should be set to 0.", + ), + ("uint16", "Order", None, None, "The order ID."), + ( + "char64", + "OrderName", + None, + None, + "The order name in ASCII; unused bytes should be set to 0.", + ), + ("uint16", "SupplementVersion", None, None, "The supplement version."), + ( + "CIDGlyphMap", + "Mapping", + None, + None, + "A mapping from CIDs to the glyphs in the font, starting with CID 0. If a CID from the identified collection has no glyph in the font, 0xFFFF is used", + ), + ], + ), + # + # feat + # + ( + "feat", + [ + ( + "Version", + "Version", + None, + None, + "Version of the feat table-initially set to 0x00010000.", + ), + ("FeatureNames", "FeatureNames", None, None, "The feature names."), + ], + ), + ( + "FeatureNames", + [ + ( + "uint16", + "FeatureNameCount", + None, + None, + "Number of entries in the feature name array.", + ), + ("uint16", "Reserved1", None, None, "Reserved (set to zero)."), + ("uint32", "Reserved2", None, None, "Reserved (set to zero)."), + ( + "FeatureName", + "FeatureName", + "FeatureNameCount", + 0, + "The feature name array.", + ), + ], + ), + ( + "FeatureName", + [ + ("uint16", "FeatureType", None, None, "Feature type."), + ( + "uint16", + "SettingsCount", + None, + None, + "The number of records in the setting name array.", + ), + ( + "LOffset", + "Settings", + None, + None, + "Offset to setting table for this feature.", + ), + ( + "uint16", + "FeatureFlags", + None, + None, + "Single-bit flags associated with the feature type.", + ), + ( + "NameID", + "FeatureNameID", + None, + None, + "The name table index for the feature name.", + ), + ], + ), + ( + "Settings", + [ + ("Setting", "Setting", "SettingsCount", 0, "The setting array."), + ], + ), + ( + "Setting", + [ + ("uint16", "SettingValue", None, None, "The setting."), + ( + "NameID", + "SettingNameID", + None, + None, + "The name table index for the setting name.", + ), + ], + ), + # + # gcid + # + ( + "gcid", + [ + ("struct", "GlyphCIDMapping", None, None, "Glyph to CID mapping table."), + ], + ), + ( + "GlyphCIDMappingFormat0", + [ + ( + "uint16", + "Format", + None, + None, + "Format of the glyph-to-CID mapping table, = 0.", + ), + ("uint16", "DataFormat", None, None, "Currenty unused, set to zero."), + ("uint32", "StructLength", None, None, "Size of the table in bytes."), + ("uint16", "Registry", None, None, "The registry ID."), + ( + "char64", + "RegistryName", + None, + None, + "The registry name in ASCII; unused bytes should be set to 0.", + ), + ("uint16", "Order", None, None, "The order ID."), + ( + "char64", + "OrderName", + None, + None, + "The order name in ASCII; unused bytes should be set to 0.", + ), + ("uint16", "SupplementVersion", None, None, "The supplement version."), + ( + "GlyphCIDMap", + "Mapping", + None, + None, + "The CIDs for the glyphs in the font, starting with glyph 0. If a glyph does not correspond to a CID in the identified collection, 0xFFFF is used", + ), + ], + ), + # + # lcar + # + ( + "lcar", + [ + ( + "Version", + "Version", + None, + None, + "Version number of the ligature caret table (0x00010000 for the initial version).", + ), + ("struct", "LigatureCarets", None, None, "Ligature carets table."), + ], + ), + ( + "LigatureCaretsFormat0", + [ + ( + "uint16", + "Format", + None, + None, + "Format of the ligature caret table. Format 0 indicates division points are distances in font units, Format 1 indicates division points are indexes of control points.", + ), + ( + "AATLookup(LigCaretDistances)", + "Carets", + None, + None, + "Lookup table associating ligature glyphs with their caret positions, in font unit distances.", + ), + ], + ), + ( + "LigatureCaretsFormat1", + [ + ( + "uint16", + "Format", + None, + None, + "Format of the ligature caret table. Format 0 indicates division points are distances in font units, Format 1 indicates division points are indexes of control points.", + ), + ( + "AATLookup(LigCaretPoints)", + "Carets", + None, + None, + "Lookup table associating ligature glyphs with their caret positions, as control points.", + ), + ], + ), + ( + "LigCaretDistances", + [ + ("uint16", "DivsionPointCount", None, None, "Number of division points."), + ( + "int16", + "DivisionPoint", + "DivsionPointCount", + 0, + "Distance in font units through which a subdivision is made orthogonally to the baseline.", + ), + ], + ), + ( + "LigCaretPoints", + [ + ("uint16", "DivsionPointCount", None, None, "Number of division points."), + ( + "int16", + "DivisionPoint", + "DivsionPointCount", + 0, + "The number of the control point through which a subdivision is made orthogonally to the baseline.", + ), + ], + ), + # + # mort + # + ( + "mort", + [ + ("Version", "Version", None, None, "Version of the mort table."), + ( + "uint32", + "MorphChainCount", + None, + None, + "Number of metamorphosis chains.", + ), + ( + "MortChain", + "MorphChain", + "MorphChainCount", + 0, + "Array of metamorphosis chains.", + ), + ], + ), + ( + "MortChain", + [ + ( + "Flags32", + "DefaultFlags", + None, + None, + "The default specification for subtables.", + ), + ( + "uint32", + "StructLength", + None, + None, + "Total byte count, including this header; must be a multiple of 4.", + ), + ( + "uint16", + "MorphFeatureCount", + None, + None, + "Number of metamorphosis feature entries.", + ), + ( + "uint16", + "MorphSubtableCount", + None, + None, + "The number of subtables in the chain.", + ), + ( + "struct", + "MorphFeature", + "MorphFeatureCount", + 0, + "Array of metamorphosis features.", + ), + ( + "MortSubtable", + "MorphSubtable", + "MorphSubtableCount", + 0, + "Array of metamorphosis subtables.", + ), + ], + ), + ( + "MortSubtable", + [ + ( + "uint16", + "StructLength", + None, + None, + "Total subtable length, including this header.", + ), + ( + "uint8", + "CoverageFlags", + None, + None, + "Most significant byte of coverage flags.", + ), + ("uint8", "MorphType", None, None, "Subtable type."), + ( + "Flags32", + "SubFeatureFlags", + None, + None, + "The 32-bit mask identifying which subtable this is (the subtable being executed if the AND of this value and the processed defaultFlags is nonzero).", + ), + ("SubStruct", "SubStruct", None, None, "SubTable."), + ], + ), + # + # morx + # + ( + "morx", + [ + ("uint16", "Version", None, None, "Version of the morx table."), + ("uint16", "Reserved", None, None, "Reserved (set to zero)."), + ( + "uint32", + "MorphChainCount", + None, + None, + "Number of extended metamorphosis chains.", + ), + ( + "MorxChain", + "MorphChain", + "MorphChainCount", + 0, + "Array of extended metamorphosis chains.", + ), + ], + ), + ( + "MorxChain", + [ + ( + "Flags32", + "DefaultFlags", + None, + None, + "The default specification for subtables.", + ), + ( + "uint32", + "StructLength", + None, + None, + "Total byte count, including this header; must be a multiple of 4.", + ), + ( + "uint32", + "MorphFeatureCount", + None, + None, + "Number of feature subtable entries.", + ), + ( + "uint32", + "MorphSubtableCount", + None, + None, + "The number of subtables in the chain.", + ), + ( + "MorphFeature", + "MorphFeature", + "MorphFeatureCount", + 0, + "Array of metamorphosis features.", + ), + ( + "MorxSubtable", + "MorphSubtable", + "MorphSubtableCount", + 0, + "Array of extended metamorphosis subtables.", + ), + ], + ), + ( + "MorphFeature", + [ + ("uint16", "FeatureType", None, None, "The type of feature."), + ( + "uint16", + "FeatureSetting", + None, + None, + "The feature's setting (aka selector).", + ), + ( + "Flags32", + "EnableFlags", + None, + None, + "Flags for the settings that this feature and setting enables.", + ), + ( + "Flags32", + "DisableFlags", + None, + None, + "Complement of flags for the settings that this feature and setting disable.", + ), + ], + ), + # Apple TrueType Reference Manual, chapter “The ‘morx’ table”, + # section “Metamorphosis Subtables”. + # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html + ( + "MorxSubtable", + [ + ( + "uint32", + "StructLength", + None, + None, + "Total subtable length, including this header.", + ), + ( + "uint8", + "CoverageFlags", + None, + None, + "Most significant byte of coverage flags.", + ), + ("uint16", "Reserved", None, None, "Unused."), + ("uint8", "MorphType", None, None, "Subtable type."), + ( + "Flags32", + "SubFeatureFlags", + None, + None, + "The 32-bit mask identifying which subtable this is (the subtable being executed if the AND of this value and the processed defaultFlags is nonzero).", + ), + ("SubStruct", "SubStruct", None, None, "SubTable."), + ], + ), + ( + "StateHeader", + [ + ( + "uint32", + "ClassCount", + None, + None, + "Number of classes, which is the number of 16-bit entry indices in a single line in the state array.", + ), + ( + "uint32", + "MorphClass", + None, + None, + "Offset from the start of this state table header to the start of the class table.", + ), + ( + "uint32", + "StateArrayOffset", + None, + None, + "Offset from the start of this state table header to the start of the state array.", + ), + ( + "uint32", + "EntryTableOffset", + None, + None, + "Offset from the start of this state table header to the start of the entry table.", + ), + ], + ), + ( + "RearrangementMorph", + [ + ( + "STXHeader(RearrangementMorphAction)", + "StateTable", + None, + None, + "Finite-state transducer table for indic rearrangement.", + ), + ], + ), + ( + "ContextualMorph", + [ + ( + "STXHeader(ContextualMorphAction)", + "StateTable", + None, + None, + "Finite-state transducer for contextual glyph substitution.", + ), + ], + ), + ( + "LigatureMorph", + [ + ( + "STXHeader(LigatureMorphAction)", + "StateTable", + None, + None, + "Finite-state transducer for ligature substitution.", + ), + ], + ), + ( + "NoncontextualMorph", + [ + ( + "AATLookup(GlyphID)", + "Substitution", + None, + None, + "The noncontextual glyph substitution table.", + ), + ], + ), + ( + "InsertionMorph", + [ + ( + "STXHeader(InsertionMorphAction)", + "StateTable", + None, + None, + "Finite-state transducer for glyph insertion.", + ), + ], + ), + ( + "MorphClass", + [ + ( + "uint16", + "FirstGlyph", + None, + None, + "Glyph index of the first glyph in the class table.", + ), + # ('uint16', 'GlyphCount', None, None, 'Number of glyphs in class table.'), + # ('uint8', 'GlyphClass', 'GlyphCount', 0, 'The class codes (indexed by glyph index minus firstGlyph). Class codes range from 0 to the value of stateSize minus 1.'), + ], + ), + # If the 'morx' table version is 3 or greater, then the last subtable in the chain is followed by a subtableGlyphCoverageArray, as described below. + # ('Offset', 'MarkGlyphSetsDef', None, 'round(Version*0x10000) >= 0x00010002', 'Offset to the table of mark set definitions-from beginning of GDEF header (may be NULL)'), + # + # prop + # + ( + "prop", + [ + ( + "Fixed", + "Version", + None, + None, + "Version number of the AAT glyphs property table. Version 1.0 is the initial table version. Version 2.0, which is recognized by macOS 8.5 and later, adds support for the “attaches on right” bit. Version 3.0, which gets recognized by macOS X and iOS, adds support for the additional directional properties defined in Unicode 3.0.", + ), + ("struct", "GlyphProperties", None, None, "Glyph properties."), + ], + ), + ( + "GlyphPropertiesFormat0", + [ + ("uint16", "Format", None, None, "Format, = 0."), + ( + "uint16", + "DefaultProperties", + None, + None, + "Default properties applied to a glyph. Since there is no lookup table in prop format 0, the default properties get applied to every glyph in the font.", + ), + ], + ), + ( + "GlyphPropertiesFormat1", + [ + ("uint16", "Format", None, None, "Format, = 1."), + ( + "uint16", + "DefaultProperties", + None, + None, + "Default properties applied to a glyph if that glyph is not present in the Properties lookup table.", + ), + ( + "AATLookup(uint16)", + "Properties", + None, + None, + "Lookup data associating glyphs with their properties.", + ), + ], + ), + # + # opbd + # + ( + "opbd", + [ + ( + "Version", + "Version", + None, + None, + "Version number of the optical bounds table (0x00010000 for the initial version).", + ), + ("struct", "OpticalBounds", None, None, "Optical bounds table."), + ], + ), + ( + "OpticalBoundsFormat0", + [ + ( + "uint16", + "Format", + None, + None, + "Format of the optical bounds table, = 0.", + ), + ( + "AATLookup(OpticalBoundsDeltas)", + "OpticalBoundsDeltas", + None, + None, + "Lookup table associating glyphs with their optical bounds, given as deltas in font units.", + ), + ], + ), + ( + "OpticalBoundsFormat1", + [ + ( + "uint16", + "Format", + None, + None, + "Format of the optical bounds table, = 1.", + ), + ( + "AATLookup(OpticalBoundsPoints)", + "OpticalBoundsPoints", + None, + None, + "Lookup table associating glyphs with their optical bounds, given as references to control points.", + ), + ], + ), + ( + "OpticalBoundsDeltas", + [ + ( + "int16", + "Left", + None, + None, + "Delta value for the left-side optical edge.", + ), + ("int16", "Top", None, None, "Delta value for the top-side optical edge."), + ( + "int16", + "Right", + None, + None, + "Delta value for the right-side optical edge.", + ), + ( + "int16", + "Bottom", + None, + None, + "Delta value for the bottom-side optical edge.", + ), + ], + ), + ( + "OpticalBoundsPoints", + [ + ( + "int16", + "Left", + None, + None, + "Control point index for the left-side optical edge, or -1 if this glyph has none.", + ), + ( + "int16", + "Top", + None, + None, + "Control point index for the top-side optical edge, or -1 if this glyph has none.", + ), + ( + "int16", + "Right", + None, + None, + "Control point index for the right-side optical edge, or -1 if this glyph has none.", + ), + ( + "int16", + "Bottom", + None, + None, + "Control point index for the bottom-side optical edge, or -1 if this glyph has none.", + ), + ], + ), + # + # TSIC + # + ( + "TSIC", + [ + ( + "Version", + "Version", + None, + None, + "Version of table initially set to 0x00010000.", + ), + ("uint16", "Flags", None, None, "TSIC flags - set to 0"), + ("uint16", "AxisCount", None, None, "Axis count from fvar"), + ("uint16", "RecordCount", None, None, "TSIC record count"), + ("uint16", "Reserved", None, None, "Set to 0"), + ("Tag", "AxisArray", "AxisCount", 0, "Array of axis tags in fvar order"), + ( + "LocationRecord", + "RecordLocations", + "RecordCount", + 0, + "Location in variation space of TSIC record", + ), + ("TSICRecord", "Record", "RecordCount", 0, "Array of TSIC records"), + ], + ), + ( + "LocationRecord", + [ + ("F2Dot14", "Axis", "AxisCount", 0, "Axis record"), + ], + ), + ( + "TSICRecord", + [ + ("uint16", "Flags", None, None, "Record flags - set to 0"), + ("uint16", "NumCVTEntries", None, None, "Number of CVT number value pairs"), + ("uint16", "NameLength", None, None, "Length of optional user record name"), + ("uint16", "NameArray", "NameLength", 0, "Unicode 16 name"), + ("uint16", "CVTArray", "NumCVTEntries", 0, "CVT number array"), + ("int16", "CVTValueArray", "NumCVTEntries", 0, "CVT value"), + ], + ), + # + # COLR + # + ( + "COLR", + [ + ("uint16", "Version", None, None, "Table version number (starts at 0)."), + ( + "uint16", + "BaseGlyphRecordCount", + None, + None, + "Number of Base Glyph Records.", + ), + ( + "LOffset", + "BaseGlyphRecordArray", + None, + None, + "Offset (from beginning of COLR table) to Base Glyph records.", + ), + ( + "LOffset", + "LayerRecordArray", + None, + None, + "Offset (from beginning of COLR table) to Layer Records.", + ), + ("uint16", "LayerRecordCount", None, None, "Number of Layer Records."), + ( + "LOffset", + "BaseGlyphList", + None, + "Version >= 1", + "Offset (from beginning of COLR table) to array of Version-1 Base Glyph records.", + ), + ( + "LOffset", + "LayerList", + None, + "Version >= 1", + "Offset (from beginning of COLR table) to LayerList.", + ), + ( + "LOffset", + "ClipList", + None, + "Version >= 1", + "Offset to ClipList table (may be NULL)", + ), + ( + "LOffsetTo(DeltaSetIndexMap)", + "VarIndexMap", + None, + "Version >= 1", + "Offset to DeltaSetIndexMap table (may be NULL)", + ), + ( + "LOffset", + "VarStore", + None, + "Version >= 1", + "Offset to variation store (may be NULL)", + ), + ], + ), + ( + "BaseGlyphRecordArray", + [ + ( + "BaseGlyphRecord", + "BaseGlyphRecord", + "BaseGlyphRecordCount", + 0, + "Base Glyph records.", + ), + ], + ), + ( + "BaseGlyphRecord", + [ + ( + "GlyphID", + "BaseGlyph", + None, + None, + "Glyph ID of reference glyph. This glyph is for reference only and is not rendered for color.", + ), + ( + "uint16", + "FirstLayerIndex", + None, + None, + "Index (from beginning of the Layer Records) to the layer record. There will be numLayers consecutive entries for this base glyph.", + ), + ( + "uint16", + "NumLayers", + None, + None, + "Number of color layers associated with this glyph.", + ), + ], + ), + ( + "LayerRecordArray", + [ + ("LayerRecord", "LayerRecord", "LayerRecordCount", 0, "Layer records."), + ], + ), + ( + "LayerRecord", + [ + ( + "GlyphID", + "LayerGlyph", + None, + None, + "Glyph ID of layer glyph (must be in z-order from bottom to top).", + ), + ( + "uint16", + "PaletteIndex", + None, + None, + "Index value to use with a selected color palette.", + ), + ], + ), + ( + "BaseGlyphList", + [ + ( + "uint32", + "BaseGlyphCount", + None, + None, + "Number of Version-1 Base Glyph records", + ), + ( + "struct", + "BaseGlyphPaintRecord", + "BaseGlyphCount", + 0, + "Array of Version-1 Base Glyph records", + ), + ], + ), + ( + "BaseGlyphPaintRecord", + [ + ("GlyphID", "BaseGlyph", None, None, "Glyph ID of reference glyph."), + ( + "LOffset", + "Paint", + None, + None, + "Offset (from beginning of BaseGlyphPaintRecord) to Paint, typically a PaintColrLayers.", + ), + ], + ), + ( + "LayerList", + [ + ("uint32", "LayerCount", None, None, "Number of Version-1 Layers"), + ( + "LOffset", + "Paint", + "LayerCount", + 0, + "Array of offsets to Paint tables, from the start of the LayerList table.", + ), + ], + ), + ( + "ClipListFormat1", + [ + ( + "uint8", + "Format", + None, + None, + "Format for ClipList with 16bit glyph IDs: 1", + ), + ("uint32", "ClipCount", None, None, "Number of Clip records."), + ( + "struct", + "ClipRecord", + "ClipCount", + 0, + "Array of Clip records sorted by glyph ID.", + ), + ], + ), + ( + "ClipRecord", + [ + ("uint16", "StartGlyphID", None, None, "First glyph ID in the range."), + ("uint16", "EndGlyphID", None, None, "Last glyph ID in the range."), + ("Offset24", "ClipBox", None, None, "Offset to a ClipBox table."), + ], + ), + ( + "ClipBoxFormat1", + [ + ( + "uint8", + "Format", + None, + None, + "Format for ClipBox without variation: set to 1.", + ), + ("int16", "xMin", None, None, "Minimum x of clip box."), + ("int16", "yMin", None, None, "Minimum y of clip box."), + ("int16", "xMax", None, None, "Maximum x of clip box."), + ("int16", "yMax", None, None, "Maximum y of clip box."), + ], + ), + ( + "ClipBoxFormat2", + [ + ("uint8", "Format", None, None, "Format for variable ClipBox: set to 2."), + ("int16", "xMin", None, None, "Minimum x of clip box. VarIndexBase + 0."), + ("int16", "yMin", None, None, "Minimum y of clip box. VarIndexBase + 1."), + ("int16", "xMax", None, None, "Maximum x of clip box. VarIndexBase + 2."), + ("int16", "yMax", None, None, "Maximum y of clip box. VarIndexBase + 3."), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # COLRv1 Affine2x3 uses the same column-major order to serialize a 2D + # Affine Transformation as the one used by fontTools.misc.transform. + # However, for historical reasons, the labels 'xy' and 'yx' are swapped. + # Their fundamental meaning is the same though. + # COLRv1 Affine2x3 follows the names found in FreeType and Cairo. + # In all case, the second element in the 6-tuple correspond to the + # y-part of the x basis vector, and the third to the x-part of the y + # basis vector. + # See https://github.com/googlefonts/colr-gradients-spec/pull/85 + ( + "Affine2x3", + [ + ("Fixed", "xx", None, None, "x-part of x basis vector"), + ("Fixed", "yx", None, None, "y-part of x basis vector"), + ("Fixed", "xy", None, None, "x-part of y basis vector"), + ("Fixed", "yy", None, None, "y-part of y basis vector"), + ("Fixed", "dx", None, None, "Translation in x direction"), + ("Fixed", "dy", None, None, "Translation in y direction"), + ], + ), + ( + "VarAffine2x3", + [ + ("Fixed", "xx", None, None, "x-part of x basis vector. VarIndexBase + 0."), + ("Fixed", "yx", None, None, "y-part of x basis vector. VarIndexBase + 1."), + ("Fixed", "xy", None, None, "x-part of y basis vector. VarIndexBase + 2."), + ("Fixed", "yy", None, None, "y-part of y basis vector. VarIndexBase + 3."), + ( + "Fixed", + "dx", + None, + None, + "Translation in x direction. VarIndexBase + 4.", + ), + ( + "Fixed", + "dy", + None, + None, + "Translation in y direction. VarIndexBase + 5.", + ), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + ( + "ColorStop", + [ + ("F2Dot14", "StopOffset", None, None, ""), + ("uint16", "PaletteIndex", None, None, "Index for a CPAL palette entry."), + ("F2Dot14", "Alpha", None, None, "Values outsided [0.,1.] reserved"), + ], + ), + ( + "VarColorStop", + [ + ("F2Dot14", "StopOffset", None, None, "VarIndexBase + 0."), + ("uint16", "PaletteIndex", None, None, "Index for a CPAL palette entry."), + ( + "F2Dot14", + "Alpha", + None, + None, + "Values outsided [0.,1.] reserved. VarIndexBase + 1.", + ), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + ( + "ColorLine", + [ + ( + "ExtendMode", + "Extend", + None, + None, + "Enum {PAD = 0, REPEAT = 1, REFLECT = 2}", + ), + ("uint16", "StopCount", None, None, "Number of Color stops."), + ("ColorStop", "ColorStop", "StopCount", 0, "Array of Color stops."), + ], + ), + ( + "VarColorLine", + [ + ( + "ExtendMode", + "Extend", + None, + None, + "Enum {PAD = 0, REPEAT = 1, REFLECT = 2}", + ), + ("uint16", "StopCount", None, None, "Number of Color stops."), + ("VarColorStop", "ColorStop", "StopCount", 0, "Array of Color stops."), + ], + ), + # PaintColrLayers + ( + "PaintFormat1", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 1"), + ( + "uint8", + "NumLayers", + None, + None, + "Number of offsets to Paint to read from LayerList.", + ), + ("uint32", "FirstLayerIndex", None, None, "Index into LayerList."), + ], + ), + # PaintSolid + ( + "PaintFormat2", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 2"), + ("uint16", "PaletteIndex", None, None, "Index for a CPAL palette entry."), + ("F2Dot14", "Alpha", None, None, "Values outsided [0.,1.] reserved"), + ], + ), + # PaintVarSolid + ( + "PaintFormat3", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 3"), + ("uint16", "PaletteIndex", None, None, "Index for a CPAL palette entry."), + ( + "F2Dot14", + "Alpha", + None, + None, + "Values outsided [0.,1.] reserved. VarIndexBase + 0.", + ), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintLinearGradient + ( + "PaintFormat4", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 4"), + ( + "Offset24", + "ColorLine", + None, + None, + "Offset (from beginning of PaintLinearGradient table) to ColorLine subtable.", + ), + ("int16", "x0", None, None, ""), + ("int16", "y0", None, None, ""), + ("int16", "x1", None, None, ""), + ("int16", "y1", None, None, ""), + ("int16", "x2", None, None, ""), + ("int16", "y2", None, None, ""), + ], + ), + # PaintVarLinearGradient + ( + "PaintFormat5", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 5"), + ( + "LOffset24To(VarColorLine)", + "ColorLine", + None, + None, + "Offset (from beginning of PaintVarLinearGradient table) to VarColorLine subtable.", + ), + ("int16", "x0", None, None, "VarIndexBase + 0."), + ("int16", "y0", None, None, "VarIndexBase + 1."), + ("int16", "x1", None, None, "VarIndexBase + 2."), + ("int16", "y1", None, None, "VarIndexBase + 3."), + ("int16", "x2", None, None, "VarIndexBase + 4."), + ("int16", "y2", None, None, "VarIndexBase + 5."), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintRadialGradient + ( + "PaintFormat6", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 6"), + ( + "Offset24", + "ColorLine", + None, + None, + "Offset (from beginning of PaintRadialGradient table) to ColorLine subtable.", + ), + ("int16", "x0", None, None, ""), + ("int16", "y0", None, None, ""), + ("uint16", "r0", None, None, ""), + ("int16", "x1", None, None, ""), + ("int16", "y1", None, None, ""), + ("uint16", "r1", None, None, ""), + ], + ), + # PaintVarRadialGradient + ( + "PaintFormat7", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 7"), + ( + "LOffset24To(VarColorLine)", + "ColorLine", + None, + None, + "Offset (from beginning of PaintVarRadialGradient table) to VarColorLine subtable.", + ), + ("int16", "x0", None, None, "VarIndexBase + 0."), + ("int16", "y0", None, None, "VarIndexBase + 1."), + ("uint16", "r0", None, None, "VarIndexBase + 2."), + ("int16", "x1", None, None, "VarIndexBase + 3."), + ("int16", "y1", None, None, "VarIndexBase + 4."), + ("uint16", "r1", None, None, "VarIndexBase + 5."), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintSweepGradient + ( + "PaintFormat8", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 8"), + ( + "Offset24", + "ColorLine", + None, + None, + "Offset (from beginning of PaintSweepGradient table) to ColorLine subtable.", + ), + ("int16", "centerX", None, None, "Center x coordinate."), + ("int16", "centerY", None, None, "Center y coordinate."), + ( + "BiasedAngle", + "startAngle", + None, + None, + "Start of the angular range of the gradient.", + ), + ( + "BiasedAngle", + "endAngle", + None, + None, + "End of the angular range of the gradient.", + ), + ], + ), + # PaintVarSweepGradient + ( + "PaintFormat9", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 9"), + ( + "LOffset24To(VarColorLine)", + "ColorLine", + None, + None, + "Offset (from beginning of PaintVarSweepGradient table) to VarColorLine subtable.", + ), + ("int16", "centerX", None, None, "Center x coordinate. VarIndexBase + 0."), + ("int16", "centerY", None, None, "Center y coordinate. VarIndexBase + 1."), + ( + "BiasedAngle", + "startAngle", + None, + None, + "Start of the angular range of the gradient. VarIndexBase + 2.", + ), + ( + "BiasedAngle", + "endAngle", + None, + None, + "End of the angular range of the gradient. VarIndexBase + 3.", + ), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintGlyph + ( + "PaintFormat10", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 10"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintGlyph table) to Paint subtable.", + ), + ("GlyphID", "Glyph", None, None, "Glyph ID for the source outline."), + ], + ), + # PaintColrGlyph + ( + "PaintFormat11", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 11"), + ( + "GlyphID", + "Glyph", + None, + None, + "Virtual glyph ID for a BaseGlyphList base glyph.", + ), + ], + ), + # PaintTransform + ( + "PaintFormat12", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 12"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintTransform table) to Paint subtable.", + ), + ( + "LOffset24To(Affine2x3)", + "Transform", + None, + None, + "2x3 matrix for 2D affine transformations.", + ), + ], + ), + # PaintVarTransform + ( + "PaintFormat13", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 13"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintVarTransform table) to Paint subtable.", + ), + ( + "LOffset24To(VarAffine2x3)", + "Transform", + None, + None, + "2x3 matrix for 2D affine transformations.", + ), + ], + ), + # PaintTranslate + ( + "PaintFormat14", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 14"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintTranslate table) to Paint subtable.", + ), + ("int16", "dx", None, None, "Translation in x direction."), + ("int16", "dy", None, None, "Translation in y direction."), + ], + ), + # PaintVarTranslate + ( + "PaintFormat15", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 15"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintVarTranslate table) to Paint subtable.", + ), + ( + "int16", + "dx", + None, + None, + "Translation in x direction. VarIndexBase + 0.", + ), + ( + "int16", + "dy", + None, + None, + "Translation in y direction. VarIndexBase + 1.", + ), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintScale + ( + "PaintFormat16", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 16"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintScale table) to Paint subtable.", + ), + ("F2Dot14", "scaleX", None, None, ""), + ("F2Dot14", "scaleY", None, None, ""), + ], + ), + # PaintVarScale + ( + "PaintFormat17", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 17"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintVarScale table) to Paint subtable.", + ), + ("F2Dot14", "scaleX", None, None, "VarIndexBase + 0."), + ("F2Dot14", "scaleY", None, None, "VarIndexBase + 1."), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintScaleAroundCenter + ( + "PaintFormat18", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 18"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintScaleAroundCenter table) to Paint subtable.", + ), + ("F2Dot14", "scaleX", None, None, ""), + ("F2Dot14", "scaleY", None, None, ""), + ("int16", "centerX", None, None, ""), + ("int16", "centerY", None, None, ""), + ], + ), + # PaintVarScaleAroundCenter + ( + "PaintFormat19", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 19"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintVarScaleAroundCenter table) to Paint subtable.", + ), + ("F2Dot14", "scaleX", None, None, "VarIndexBase + 0."), + ("F2Dot14", "scaleY", None, None, "VarIndexBase + 1."), + ("int16", "centerX", None, None, "VarIndexBase + 2."), + ("int16", "centerY", None, None, "VarIndexBase + 3."), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintScaleUniform + ( + "PaintFormat20", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 20"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintScaleUniform table) to Paint subtable.", + ), + ("F2Dot14", "scale", None, None, ""), + ], + ), + # PaintVarScaleUniform + ( + "PaintFormat21", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 21"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintVarScaleUniform table) to Paint subtable.", + ), + ("F2Dot14", "scale", None, None, "VarIndexBase + 0."), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintScaleUniformAroundCenter + ( + "PaintFormat22", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 22"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintScaleUniformAroundCenter table) to Paint subtable.", + ), + ("F2Dot14", "scale", None, None, ""), + ("int16", "centerX", None, None, ""), + ("int16", "centerY", None, None, ""), + ], + ), + # PaintVarScaleUniformAroundCenter + ( + "PaintFormat23", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 23"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintVarScaleUniformAroundCenter table) to Paint subtable.", + ), + ("F2Dot14", "scale", None, None, "VarIndexBase + 0"), + ("int16", "centerX", None, None, "VarIndexBase + 1"), + ("int16", "centerY", None, None, "VarIndexBase + 2"), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintRotate + ( + "PaintFormat24", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 24"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintRotate table) to Paint subtable.", + ), + ("Angle", "angle", None, None, ""), + ], + ), + # PaintVarRotate + ( + "PaintFormat25", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 25"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintVarRotate table) to Paint subtable.", + ), + ("Angle", "angle", None, None, "VarIndexBase + 0."), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintRotateAroundCenter + ( + "PaintFormat26", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 26"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintRotateAroundCenter table) to Paint subtable.", + ), + ("Angle", "angle", None, None, ""), + ("int16", "centerX", None, None, ""), + ("int16", "centerY", None, None, ""), + ], + ), + # PaintVarRotateAroundCenter + ( + "PaintFormat27", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 27"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintVarRotateAroundCenter table) to Paint subtable.", + ), + ("Angle", "angle", None, None, "VarIndexBase + 0."), + ("int16", "centerX", None, None, "VarIndexBase + 1."), + ("int16", "centerY", None, None, "VarIndexBase + 2."), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintSkew + ( + "PaintFormat28", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 28"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintSkew table) to Paint subtable.", + ), + ("Angle", "xSkewAngle", None, None, ""), + ("Angle", "ySkewAngle", None, None, ""), + ], + ), + # PaintVarSkew + ( + "PaintFormat29", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 29"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintVarSkew table) to Paint subtable.", + ), + ("Angle", "xSkewAngle", None, None, "VarIndexBase + 0."), + ("Angle", "ySkewAngle", None, None, "VarIndexBase + 1."), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintSkewAroundCenter + ( + "PaintFormat30", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 30"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintSkewAroundCenter table) to Paint subtable.", + ), + ("Angle", "xSkewAngle", None, None, ""), + ("Angle", "ySkewAngle", None, None, ""), + ("int16", "centerX", None, None, ""), + ("int16", "centerY", None, None, ""), + ], + ), + # PaintVarSkewAroundCenter + ( + "PaintFormat31", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 31"), + ( + "Offset24", + "Paint", + None, + None, + "Offset (from beginning of PaintVarSkewAroundCenter table) to Paint subtable.", + ), + ("Angle", "xSkewAngle", None, None, "VarIndexBase + 0."), + ("Angle", "ySkewAngle", None, None, "VarIndexBase + 1."), + ("int16", "centerX", None, None, "VarIndexBase + 2."), + ("int16", "centerY", None, None, "VarIndexBase + 3."), + ( + "VarIndex", + "VarIndexBase", + None, + None, + "Base index into DeltaSetIndexMap.", + ), + ], + ), + # PaintComposite + ( + "PaintFormat32", + [ + ("uint8", "PaintFormat", None, None, "Format identifier-format = 32"), + ( + "LOffset24To(Paint)", + "SourcePaint", + None, + None, + "Offset (from beginning of PaintComposite table) to source Paint subtable.", + ), + ( + "CompositeMode", + "CompositeMode", + None, + None, + "A CompositeMode enumeration value.", + ), + ( + "LOffset24To(Paint)", + "BackdropPaint", + None, + None, + "Offset (from beginning of PaintComposite table) to backdrop Paint subtable.", + ), + ], + ), + # + # avar + # + ( + "AxisValueMap", + [ + ( + "F2Dot14", + "FromCoordinate", + None, + None, + "A normalized coordinate value obtained using default normalization", + ), + ( + "F2Dot14", + "ToCoordinate", + None, + None, + "The modified, normalized coordinate value", + ), + ], + ), + ( + "AxisSegmentMap", + [ + ( + "uint16", + "PositionMapCount", + None, + None, + "The number of correspondence pairs for this axis", + ), + ( + "AxisValueMap", + "AxisValueMap", + "PositionMapCount", + 0, + "The array of axis value map records for this axis", + ), + ], + ), + ( + "avar", + [ + ( + "Version", + "Version", + None, + None, + "Version of the avar table- 0x00010000 or 0x00020000", + ), + ("uint16", "Reserved", None, None, "Permanently reserved; set to zero"), + ( + "uint16", + "AxisCount", + None, + None, + 'The number of variation axes for this font. This must be the same number as axisCount in the "fvar" table', + ), + ( + "AxisSegmentMap", + "AxisSegmentMap", + "AxisCount", + 0, + 'The segment maps array — one segment map for each axis, in the order of axes specified in the "fvar" table', + ), + ("LOffset", "VarIdxMap", None, "Version >= 0x00020000", ""), + ("LOffset", "VarStore", None, "Version >= 0x00020000", ""), + ], + ), ] diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py index 7d7ba4e34..9a27a810d 100644 --- a/Lib/fontTools/ttLib/tables/otTables.py +++ b/Lib/fontTools/ttLib/tables/otTables.py @@ -12,8 +12,11 @@ from collections import defaultdict, namedtuple from fontTools.misc.roundTools import otRound from fontTools.misc.textTools import bytesjoin, pad, safeEval from .otBase import ( - BaseTable, FormatSwitchingBaseTable, ValueRecord, CountReference, - getFormatSwitchingBaseTableClass, + BaseTable, + FormatSwitchingBaseTable, + ValueRecord, + CountReference, + getFormatSwitchingBaseTableClass, ) from fontTools.feaLib.lookupDebugInfo import LookupDebugInfo, LOOKUP_DEBUG_INFO_KEY import logging @@ -24,580 +27,593 @@ log = logging.getLogger(__name__) class AATStateTable(object): - def __init__(self): - self.GlyphClasses = {} # GlyphID --> GlyphClass - self.States = [] # List of AATState, indexed by state number - self.PerGlyphLookups = [] # [{GlyphID:GlyphID}, ...] + def __init__(self): + self.GlyphClasses = {} # GlyphID --> GlyphClass + self.States = [] # List of AATState, indexed by state number + self.PerGlyphLookups = [] # [{GlyphID:GlyphID}, ...] class AATState(object): - def __init__(self): - self.Transitions = {} # GlyphClass --> AATAction + def __init__(self): + self.Transitions = {} # GlyphClass --> AATAction class AATAction(object): - _FLAGS = None + _FLAGS = None - @staticmethod - def compileActions(font, states): - return (None, None) + @staticmethod + def compileActions(font, states): + return (None, None) - def _writeFlagsToXML(self, xmlWriter): - flags = [f for f in self._FLAGS if self.__dict__[f]] - if flags: - xmlWriter.simpletag("Flags", value=",".join(flags)) - xmlWriter.newline() - if self.ReservedFlags != 0: - xmlWriter.simpletag( - "ReservedFlags", - value='0x%04X' % self.ReservedFlags) - xmlWriter.newline() + def _writeFlagsToXML(self, xmlWriter): + flags = [f for f in self._FLAGS if self.__dict__[f]] + if flags: + xmlWriter.simpletag("Flags", value=",".join(flags)) + xmlWriter.newline() + if self.ReservedFlags != 0: + xmlWriter.simpletag("ReservedFlags", value="0x%04X" % self.ReservedFlags) + xmlWriter.newline() - def _setFlag(self, flag): - assert flag in self._FLAGS, "unsupported flag %s" % flag - self.__dict__[flag] = True + def _setFlag(self, flag): + assert flag in self._FLAGS, "unsupported flag %s" % flag + self.__dict__[flag] = True class RearrangementMorphAction(AATAction): - staticSize = 4 - actionHeaderSize = 0 - _FLAGS = ["MarkFirst", "DontAdvance", "MarkLast"] + staticSize = 4 + actionHeaderSize = 0 + _FLAGS = ["MarkFirst", "DontAdvance", "MarkLast"] - _VERBS = { - 0: "no change", - 1: "Ax ⇒ xA", - 2: "xD ⇒ Dx", - 3: "AxD ⇒ DxA", - 4: "ABx ⇒ xAB", - 5: "ABx ⇒ xBA", - 6: "xCD ⇒ CDx", - 7: "xCD ⇒ DCx", - 8: "AxCD ⇒ CDxA", - 9: "AxCD ⇒ DCxA", - 10: "ABxD ⇒ DxAB", - 11: "ABxD ⇒ DxBA", - 12: "ABxCD ⇒ CDxAB", - 13: "ABxCD ⇒ CDxBA", - 14: "ABxCD ⇒ DCxAB", - 15: "ABxCD ⇒ DCxBA", - } + _VERBS = { + 0: "no change", + 1: "Ax ⇒ xA", + 2: "xD ⇒ Dx", + 3: "AxD ⇒ DxA", + 4: "ABx ⇒ xAB", + 5: "ABx ⇒ xBA", + 6: "xCD ⇒ CDx", + 7: "xCD ⇒ DCx", + 8: "AxCD ⇒ CDxA", + 9: "AxCD ⇒ DCxA", + 10: "ABxD ⇒ DxAB", + 11: "ABxD ⇒ DxBA", + 12: "ABxCD ⇒ CDxAB", + 13: "ABxCD ⇒ CDxBA", + 14: "ABxCD ⇒ DCxAB", + 15: "ABxCD ⇒ DCxBA", + } - def __init__(self): - self.NewState = 0 - self.Verb = 0 - self.MarkFirst = False - self.DontAdvance = False - self.MarkLast = False - self.ReservedFlags = 0 + def __init__(self): + self.NewState = 0 + self.Verb = 0 + self.MarkFirst = False + self.DontAdvance = False + self.MarkLast = False + self.ReservedFlags = 0 - def compile(self, writer, font, actionIndex): - assert actionIndex is None - writer.writeUShort(self.NewState) - assert self.Verb >= 0 and self.Verb <= 15, self.Verb - flags = self.Verb | self.ReservedFlags - if self.MarkFirst: flags |= 0x8000 - if self.DontAdvance: flags |= 0x4000 - if self.MarkLast: flags |= 0x2000 - writer.writeUShort(flags) + def compile(self, writer, font, actionIndex): + assert actionIndex is None + writer.writeUShort(self.NewState) + assert self.Verb >= 0 and self.Verb <= 15, self.Verb + flags = self.Verb | self.ReservedFlags + if self.MarkFirst: + flags |= 0x8000 + if self.DontAdvance: + flags |= 0x4000 + if self.MarkLast: + flags |= 0x2000 + writer.writeUShort(flags) - def decompile(self, reader, font, actionReader): - assert actionReader is None - self.NewState = reader.readUShort() - flags = reader.readUShort() - self.Verb = flags & 0xF - self.MarkFirst = bool(flags & 0x8000) - self.DontAdvance = bool(flags & 0x4000) - self.MarkLast = bool(flags & 0x2000) - self.ReservedFlags = flags & 0x1FF0 + def decompile(self, reader, font, actionReader): + assert actionReader is None + self.NewState = reader.readUShort() + flags = reader.readUShort() + self.Verb = flags & 0xF + self.MarkFirst = bool(flags & 0x8000) + self.DontAdvance = bool(flags & 0x4000) + self.MarkLast = bool(flags & 0x2000) + self.ReservedFlags = flags & 0x1FF0 - def toXML(self, xmlWriter, font, attrs, name): - xmlWriter.begintag(name, **attrs) - xmlWriter.newline() - xmlWriter.simpletag("NewState", value=self.NewState) - xmlWriter.newline() - self._writeFlagsToXML(xmlWriter) - xmlWriter.simpletag("Verb", value=self.Verb) - verbComment = self._VERBS.get(self.Verb) - if verbComment is not None: - xmlWriter.comment(verbComment) - xmlWriter.newline() - xmlWriter.endtag(name) - xmlWriter.newline() + def toXML(self, xmlWriter, font, attrs, name): + xmlWriter.begintag(name, **attrs) + xmlWriter.newline() + xmlWriter.simpletag("NewState", value=self.NewState) + xmlWriter.newline() + self._writeFlagsToXML(xmlWriter) + xmlWriter.simpletag("Verb", value=self.Verb) + verbComment = self._VERBS.get(self.Verb) + if verbComment is not None: + xmlWriter.comment(verbComment) + xmlWriter.newline() + xmlWriter.endtag(name) + xmlWriter.newline() - def fromXML(self, name, attrs, content, font): - self.NewState = self.Verb = self.ReservedFlags = 0 - self.MarkFirst = self.DontAdvance = self.MarkLast = False - content = [t for t in content if isinstance(t, tuple)] - for eltName, eltAttrs, eltContent in content: - if eltName == "NewState": - self.NewState = safeEval(eltAttrs["value"]) - elif eltName == "Verb": - self.Verb = safeEval(eltAttrs["value"]) - elif eltName == "ReservedFlags": - self.ReservedFlags = safeEval(eltAttrs["value"]) - elif eltName == "Flags": - for flag in eltAttrs["value"].split(","): - self._setFlag(flag.strip()) + def fromXML(self, name, attrs, content, font): + self.NewState = self.Verb = self.ReservedFlags = 0 + self.MarkFirst = self.DontAdvance = self.MarkLast = False + content = [t for t in content if isinstance(t, tuple)] + for eltName, eltAttrs, eltContent in content: + if eltName == "NewState": + self.NewState = safeEval(eltAttrs["value"]) + elif eltName == "Verb": + self.Verb = safeEval(eltAttrs["value"]) + elif eltName == "ReservedFlags": + self.ReservedFlags = safeEval(eltAttrs["value"]) + elif eltName == "Flags": + for flag in eltAttrs["value"].split(","): + self._setFlag(flag.strip()) class ContextualMorphAction(AATAction): - staticSize = 8 - actionHeaderSize = 0 - _FLAGS = ["SetMark", "DontAdvance"] + staticSize = 8 + actionHeaderSize = 0 + _FLAGS = ["SetMark", "DontAdvance"] - def __init__(self): - self.NewState = 0 - self.SetMark, self.DontAdvance = False, False - self.ReservedFlags = 0 - self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF + def __init__(self): + self.NewState = 0 + self.SetMark, self.DontAdvance = False, False + self.ReservedFlags = 0 + self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF - def compile(self, writer, font, actionIndex): - assert actionIndex is None - writer.writeUShort(self.NewState) - flags = self.ReservedFlags - if self.SetMark: flags |= 0x8000 - if self.DontAdvance: flags |= 0x4000 - writer.writeUShort(flags) - writer.writeUShort(self.MarkIndex) - writer.writeUShort(self.CurrentIndex) + def compile(self, writer, font, actionIndex): + assert actionIndex is None + writer.writeUShort(self.NewState) + flags = self.ReservedFlags + if self.SetMark: + flags |= 0x8000 + if self.DontAdvance: + flags |= 0x4000 + writer.writeUShort(flags) + writer.writeUShort(self.MarkIndex) + writer.writeUShort(self.CurrentIndex) - def decompile(self, reader, font, actionReader): - assert actionReader is None - self.NewState = reader.readUShort() - flags = reader.readUShort() - self.SetMark = bool(flags & 0x8000) - self.DontAdvance = bool(flags & 0x4000) - self.ReservedFlags = flags & 0x3FFF - self.MarkIndex = reader.readUShort() - self.CurrentIndex = reader.readUShort() + def decompile(self, reader, font, actionReader): + assert actionReader is None + self.NewState = reader.readUShort() + flags = reader.readUShort() + self.SetMark = bool(flags & 0x8000) + self.DontAdvance = bool(flags & 0x4000) + self.ReservedFlags = flags & 0x3FFF + self.MarkIndex = reader.readUShort() + self.CurrentIndex = reader.readUShort() - def toXML(self, xmlWriter, font, attrs, name): - xmlWriter.begintag(name, **attrs) - xmlWriter.newline() - xmlWriter.simpletag("NewState", value=self.NewState) - xmlWriter.newline() - self._writeFlagsToXML(xmlWriter) - xmlWriter.simpletag("MarkIndex", value=self.MarkIndex) - xmlWriter.newline() - xmlWriter.simpletag("CurrentIndex", - value=self.CurrentIndex) - xmlWriter.newline() - xmlWriter.endtag(name) - xmlWriter.newline() + def toXML(self, xmlWriter, font, attrs, name): + xmlWriter.begintag(name, **attrs) + xmlWriter.newline() + xmlWriter.simpletag("NewState", value=self.NewState) + xmlWriter.newline() + self._writeFlagsToXML(xmlWriter) + xmlWriter.simpletag("MarkIndex", value=self.MarkIndex) + xmlWriter.newline() + xmlWriter.simpletag("CurrentIndex", value=self.CurrentIndex) + xmlWriter.newline() + xmlWriter.endtag(name) + xmlWriter.newline() - def fromXML(self, name, attrs, content, font): - self.NewState = self.ReservedFlags = 0 - self.SetMark = self.DontAdvance = False - self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF - content = [t for t in content if isinstance(t, tuple)] - for eltName, eltAttrs, eltContent in content: - if eltName == "NewState": - self.NewState = safeEval(eltAttrs["value"]) - elif eltName == "Flags": - for flag in eltAttrs["value"].split(","): - self._setFlag(flag.strip()) - elif eltName == "ReservedFlags": - self.ReservedFlags = safeEval(eltAttrs["value"]) - elif eltName == "MarkIndex": - self.MarkIndex = safeEval(eltAttrs["value"]) - elif eltName == "CurrentIndex": - self.CurrentIndex = safeEval(eltAttrs["value"]) + def fromXML(self, name, attrs, content, font): + self.NewState = self.ReservedFlags = 0 + self.SetMark = self.DontAdvance = False + self.MarkIndex, self.CurrentIndex = 0xFFFF, 0xFFFF + content = [t for t in content if isinstance(t, tuple)] + for eltName, eltAttrs, eltContent in content: + if eltName == "NewState": + self.NewState = safeEval(eltAttrs["value"]) + elif eltName == "Flags": + for flag in eltAttrs["value"].split(","): + self._setFlag(flag.strip()) + elif eltName == "ReservedFlags": + self.ReservedFlags = safeEval(eltAttrs["value"]) + elif eltName == "MarkIndex": + self.MarkIndex = safeEval(eltAttrs["value"]) + elif eltName == "CurrentIndex": + self.CurrentIndex = safeEval(eltAttrs["value"]) class LigAction(object): - def __init__(self): - self.Store = False - # GlyphIndexDelta is a (possibly negative) delta that gets - # added to the glyph ID at the top of the AAT runtime - # execution stack. It is *not* a byte offset into the - # morx table. The result of the addition, which is performed - # at run time by the shaping engine, is an index into - # the ligature components table. See 'morx' specification. - # In the AAT specification, this field is called Offset; - # but its meaning is quite different from other offsets - # in either AAT or OpenType, so we use a different name. - self.GlyphIndexDelta = 0 + def __init__(self): + self.Store = False + # GlyphIndexDelta is a (possibly negative) delta that gets + # added to the glyph ID at the top of the AAT runtime + # execution stack. It is *not* a byte offset into the + # morx table. The result of the addition, which is performed + # at run time by the shaping engine, is an index into + # the ligature components table. See 'morx' specification. + # In the AAT specification, this field is called Offset; + # but its meaning is quite different from other offsets + # in either AAT or OpenType, so we use a different name. + self.GlyphIndexDelta = 0 class LigatureMorphAction(AATAction): - staticSize = 6 + staticSize = 6 - # 4 bytes for each of {action,ligComponents,ligatures}Offset - actionHeaderSize = 12 + # 4 bytes for each of {action,ligComponents,ligatures}Offset + actionHeaderSize = 12 - _FLAGS = ["SetComponent", "DontAdvance"] + _FLAGS = ["SetComponent", "DontAdvance"] - def __init__(self): - self.NewState = 0 - self.SetComponent, self.DontAdvance = False, False - self.ReservedFlags = 0 - self.Actions = [] + def __init__(self): + self.NewState = 0 + self.SetComponent, self.DontAdvance = False, False + self.ReservedFlags = 0 + self.Actions = [] - def compile(self, writer, font, actionIndex): - assert actionIndex is not None - writer.writeUShort(self.NewState) - flags = self.ReservedFlags - if self.SetComponent: flags |= 0x8000 - if self.DontAdvance: flags |= 0x4000 - if len(self.Actions) > 0: flags |= 0x2000 - writer.writeUShort(flags) - if len(self.Actions) > 0: - actions = self.compileLigActions() - writer.writeUShort(actionIndex[actions]) - else: - writer.writeUShort(0) + def compile(self, writer, font, actionIndex): + assert actionIndex is not None + writer.writeUShort(self.NewState) + flags = self.ReservedFlags + if self.SetComponent: + flags |= 0x8000 + if self.DontAdvance: + flags |= 0x4000 + if len(self.Actions) > 0: + flags |= 0x2000 + writer.writeUShort(flags) + if len(self.Actions) > 0: + actions = self.compileLigActions() + writer.writeUShort(actionIndex[actions]) + else: + writer.writeUShort(0) - def decompile(self, reader, font, actionReader): - assert actionReader is not None - self.NewState = reader.readUShort() - flags = reader.readUShort() - self.SetComponent = bool(flags & 0x8000) - self.DontAdvance = bool(flags & 0x4000) - performAction = bool(flags & 0x2000) - # As of 2017-09-12, the 'morx' specification says that - # the reserved bitmask in ligature subtables is 0x3FFF. - # However, the specification also defines a flag 0x2000, - # so the reserved value should actually be 0x1FFF. - # TODO: Report this specification bug to Apple. - self.ReservedFlags = flags & 0x1FFF - actionIndex = reader.readUShort() - if performAction: - self.Actions = self._decompileLigActions( - actionReader, actionIndex) - else: - self.Actions = [] + def decompile(self, reader, font, actionReader): + assert actionReader is not None + self.NewState = reader.readUShort() + flags = reader.readUShort() + self.SetComponent = bool(flags & 0x8000) + self.DontAdvance = bool(flags & 0x4000) + performAction = bool(flags & 0x2000) + # As of 2017-09-12, the 'morx' specification says that + # the reserved bitmask in ligature subtables is 0x3FFF. + # However, the specification also defines a flag 0x2000, + # so the reserved value should actually be 0x1FFF. + # TODO: Report this specification bug to Apple. + self.ReservedFlags = flags & 0x1FFF + actionIndex = reader.readUShort() + if performAction: + self.Actions = self._decompileLigActions(actionReader, actionIndex) + else: + self.Actions = [] - @staticmethod - def compileActions(font, states): - result, actions, actionIndex = b"", set(), {} - for state in states: - for _glyphClass, trans in state.Transitions.items(): - actions.add(trans.compileLigActions()) - # Sort the compiled actions in decreasing order of - # length, so that the longer sequence come before the - # shorter ones. For each compiled action ABCD, its - # suffixes BCD, CD, and D do not be encoded separately - # (in case they occur); instead, we can just store an - # index that points into the middle of the longer - # sequence. Every compiled AAT ligature sequence is - # terminated with an end-of-sequence flag, which can - # only be set on the last element of the sequence. - # Therefore, it is sufficient to consider just the - # suffixes. - for a in sorted(actions, key=lambda x:(-len(x), x)): - if a not in actionIndex: - for i in range(0, len(a), 4): - suffix = a[i:] - suffixIndex = (len(result) + i) // 4 - actionIndex.setdefault( - suffix, suffixIndex) - result += a - result = pad(result, 4) - return (result, actionIndex) + @staticmethod + def compileActions(font, states): + result, actions, actionIndex = b"", set(), {} + for state in states: + for _glyphClass, trans in state.Transitions.items(): + actions.add(trans.compileLigActions()) + # Sort the compiled actions in decreasing order of + # length, so that the longer sequence come before the + # shorter ones. For each compiled action ABCD, its + # suffixes BCD, CD, and D do not be encoded separately + # (in case they occur); instead, we can just store an + # index that points into the middle of the longer + # sequence. Every compiled AAT ligature sequence is + # terminated with an end-of-sequence flag, which can + # only be set on the last element of the sequence. + # Therefore, it is sufficient to consider just the + # suffixes. + for a in sorted(actions, key=lambda x: (-len(x), x)): + if a not in actionIndex: + for i in range(0, len(a), 4): + suffix = a[i:] + suffixIndex = (len(result) + i) // 4 + actionIndex.setdefault(suffix, suffixIndex) + result += a + result = pad(result, 4) + return (result, actionIndex) - def compileLigActions(self): - result = [] - for i, action in enumerate(self.Actions): - last = (i == len(self.Actions) - 1) - value = action.GlyphIndexDelta & 0x3FFFFFFF - value |= 0x80000000 if last else 0 - value |= 0x40000000 if action.Store else 0 - result.append(struct.pack(">L", value)) - return bytesjoin(result) + def compileLigActions(self): + result = [] + for i, action in enumerate(self.Actions): + last = i == len(self.Actions) - 1 + value = action.GlyphIndexDelta & 0x3FFFFFFF + value |= 0x80000000 if last else 0 + value |= 0x40000000 if action.Store else 0 + result.append(struct.pack(">L", value)) + return bytesjoin(result) - def _decompileLigActions(self, actionReader, actionIndex): - actions = [] - last = False - reader = actionReader.getSubReader( - actionReader.pos + actionIndex * 4) - while not last: - value = reader.readULong() - last = bool(value & 0x80000000) - action = LigAction() - actions.append(action) - action.Store = bool(value & 0x40000000) - delta = value & 0x3FFFFFFF - if delta >= 0x20000000: # sign-extend 30-bit value - delta = -0x40000000 + delta - action.GlyphIndexDelta = delta - return actions + def _decompileLigActions(self, actionReader, actionIndex): + actions = [] + last = False + reader = actionReader.getSubReader(actionReader.pos + actionIndex * 4) + while not last: + value = reader.readULong() + last = bool(value & 0x80000000) + action = LigAction() + actions.append(action) + action.Store = bool(value & 0x40000000) + delta = value & 0x3FFFFFFF + if delta >= 0x20000000: # sign-extend 30-bit value + delta = -0x40000000 + delta + action.GlyphIndexDelta = delta + return actions - def fromXML(self, name, attrs, content, font): - self.NewState = self.ReservedFlags = 0 - self.SetComponent = self.DontAdvance = False - self.ReservedFlags = 0 - self.Actions = [] - content = [t for t in content if isinstance(t, tuple)] - for eltName, eltAttrs, eltContent in content: - if eltName == "NewState": - self.NewState = safeEval(eltAttrs["value"]) - elif eltName == "Flags": - for flag in eltAttrs["value"].split(","): - self._setFlag(flag.strip()) - elif eltName == "ReservedFlags": - self.ReservedFlags = safeEval(eltAttrs["value"]) - elif eltName == "Action": - action = LigAction() - flags = eltAttrs.get("Flags", "").split(",") - flags = [f.strip() for f in flags] - action.Store = "Store" in flags - action.GlyphIndexDelta = safeEval( - eltAttrs["GlyphIndexDelta"]) - self.Actions.append(action) + def fromXML(self, name, attrs, content, font): + self.NewState = self.ReservedFlags = 0 + self.SetComponent = self.DontAdvance = False + self.ReservedFlags = 0 + self.Actions = [] + content = [t for t in content if isinstance(t, tuple)] + for eltName, eltAttrs, eltContent in content: + if eltName == "NewState": + self.NewState = safeEval(eltAttrs["value"]) + elif eltName == "Flags": + for flag in eltAttrs["value"].split(","): + self._setFlag(flag.strip()) + elif eltName == "ReservedFlags": + self.ReservedFlags = safeEval(eltAttrs["value"]) + elif eltName == "Action": + action = LigAction() + flags = eltAttrs.get("Flags", "").split(",") + flags = [f.strip() for f in flags] + action.Store = "Store" in flags + action.GlyphIndexDelta = safeEval(eltAttrs["GlyphIndexDelta"]) + self.Actions.append(action) - def toXML(self, xmlWriter, font, attrs, name): - xmlWriter.begintag(name, **attrs) - xmlWriter.newline() - xmlWriter.simpletag("NewState", value=self.NewState) - xmlWriter.newline() - self._writeFlagsToXML(xmlWriter) - for action in self.Actions: - attribs = [("GlyphIndexDelta", action.GlyphIndexDelta)] - if action.Store: - attribs.append(("Flags", "Store")) - xmlWriter.simpletag("Action", attribs) - xmlWriter.newline() - xmlWriter.endtag(name) - xmlWriter.newline() + def toXML(self, xmlWriter, font, attrs, name): + xmlWriter.begintag(name, **attrs) + xmlWriter.newline() + xmlWriter.simpletag("NewState", value=self.NewState) + xmlWriter.newline() + self._writeFlagsToXML(xmlWriter) + for action in self.Actions: + attribs = [("GlyphIndexDelta", action.GlyphIndexDelta)] + if action.Store: + attribs.append(("Flags", "Store")) + xmlWriter.simpletag("Action", attribs) + xmlWriter.newline() + xmlWriter.endtag(name) + xmlWriter.newline() class InsertionMorphAction(AATAction): - staticSize = 8 - actionHeaderSize = 4 # 4 bytes for actionOffset - _FLAGS = ["SetMark", "DontAdvance", - "CurrentIsKashidaLike", "MarkedIsKashidaLike", - "CurrentInsertBefore", "MarkedInsertBefore"] + staticSize = 8 + actionHeaderSize = 4 # 4 bytes for actionOffset + _FLAGS = [ + "SetMark", + "DontAdvance", + "CurrentIsKashidaLike", + "MarkedIsKashidaLike", + "CurrentInsertBefore", + "MarkedInsertBefore", + ] - def __init__(self): - self.NewState = 0 - for flag in self._FLAGS: - setattr(self, flag, False) - self.ReservedFlags = 0 - self.CurrentInsertionAction, self.MarkedInsertionAction = [], [] + def __init__(self): + self.NewState = 0 + for flag in self._FLAGS: + setattr(self, flag, False) + self.ReservedFlags = 0 + self.CurrentInsertionAction, self.MarkedInsertionAction = [], [] - def compile(self, writer, font, actionIndex): - assert actionIndex is not None - writer.writeUShort(self.NewState) - flags = self.ReservedFlags - if self.SetMark: flags |= 0x8000 - if self.DontAdvance: flags |= 0x4000 - if self.CurrentIsKashidaLike: flags |= 0x2000 - if self.MarkedIsKashidaLike: flags |= 0x1000 - if self.CurrentInsertBefore: flags |= 0x0800 - if self.MarkedInsertBefore: flags |= 0x0400 - flags |= len(self.CurrentInsertionAction) << 5 - flags |= len(self.MarkedInsertionAction) - writer.writeUShort(flags) - if len(self.CurrentInsertionAction) > 0: - currentIndex = actionIndex[ - tuple(self.CurrentInsertionAction)] - else: - currentIndex = 0xFFFF - writer.writeUShort(currentIndex) - if len(self.MarkedInsertionAction) > 0: - markedIndex = actionIndex[ - tuple(self.MarkedInsertionAction)] - else: - markedIndex = 0xFFFF - writer.writeUShort(markedIndex) + def compile(self, writer, font, actionIndex): + assert actionIndex is not None + writer.writeUShort(self.NewState) + flags = self.ReservedFlags + if self.SetMark: + flags |= 0x8000 + if self.DontAdvance: + flags |= 0x4000 + if self.CurrentIsKashidaLike: + flags |= 0x2000 + if self.MarkedIsKashidaLike: + flags |= 0x1000 + if self.CurrentInsertBefore: + flags |= 0x0800 + if self.MarkedInsertBefore: + flags |= 0x0400 + flags |= len(self.CurrentInsertionAction) << 5 + flags |= len(self.MarkedInsertionAction) + writer.writeUShort(flags) + if len(self.CurrentInsertionAction) > 0: + currentIndex = actionIndex[tuple(self.CurrentInsertionAction)] + else: + currentIndex = 0xFFFF + writer.writeUShort(currentIndex) + if len(self.MarkedInsertionAction) > 0: + markedIndex = actionIndex[tuple(self.MarkedInsertionAction)] + else: + markedIndex = 0xFFFF + writer.writeUShort(markedIndex) - def decompile(self, reader, font, actionReader): - assert actionReader is not None - self.NewState = reader.readUShort() - flags = reader.readUShort() - self.SetMark = bool(flags & 0x8000) - self.DontAdvance = bool(flags & 0x4000) - self.CurrentIsKashidaLike = bool(flags & 0x2000) - self.MarkedIsKashidaLike = bool(flags & 0x1000) - self.CurrentInsertBefore = bool(flags & 0x0800) - self.MarkedInsertBefore = bool(flags & 0x0400) - self.CurrentInsertionAction = self._decompileInsertionAction( - actionReader, font, - index=reader.readUShort(), - count=((flags & 0x03E0) >> 5)) - self.MarkedInsertionAction = self._decompileInsertionAction( - actionReader, font, - index=reader.readUShort(), - count=(flags & 0x001F)) + def decompile(self, reader, font, actionReader): + assert actionReader is not None + self.NewState = reader.readUShort() + flags = reader.readUShort() + self.SetMark = bool(flags & 0x8000) + self.DontAdvance = bool(flags & 0x4000) + self.CurrentIsKashidaLike = bool(flags & 0x2000) + self.MarkedIsKashidaLike = bool(flags & 0x1000) + self.CurrentInsertBefore = bool(flags & 0x0800) + self.MarkedInsertBefore = bool(flags & 0x0400) + self.CurrentInsertionAction = self._decompileInsertionAction( + actionReader, font, index=reader.readUShort(), count=((flags & 0x03E0) >> 5) + ) + self.MarkedInsertionAction = self._decompileInsertionAction( + actionReader, font, index=reader.readUShort(), count=(flags & 0x001F) + ) - def _decompileInsertionAction(self, actionReader, font, index, count): - if index == 0xFFFF or count == 0: - return [] - reader = actionReader.getSubReader( - actionReader.pos + index * 2) - return font.getGlyphNameMany(reader.readUShortArray(count)) + def _decompileInsertionAction(self, actionReader, font, index, count): + if index == 0xFFFF or count == 0: + return [] + reader = actionReader.getSubReader(actionReader.pos + index * 2) + return font.getGlyphNameMany(reader.readUShortArray(count)) - def toXML(self, xmlWriter, font, attrs, name): - xmlWriter.begintag(name, **attrs) - xmlWriter.newline() - xmlWriter.simpletag("NewState", value=self.NewState) - xmlWriter.newline() - self._writeFlagsToXML(xmlWriter) - for g in self.CurrentInsertionAction: - xmlWriter.simpletag("CurrentInsertionAction", glyph=g) - xmlWriter.newline() - for g in self.MarkedInsertionAction: - xmlWriter.simpletag("MarkedInsertionAction", glyph=g) - xmlWriter.newline() - xmlWriter.endtag(name) - xmlWriter.newline() + def toXML(self, xmlWriter, font, attrs, name): + xmlWriter.begintag(name, **attrs) + xmlWriter.newline() + xmlWriter.simpletag("NewState", value=self.NewState) + xmlWriter.newline() + self._writeFlagsToXML(xmlWriter) + for g in self.CurrentInsertionAction: + xmlWriter.simpletag("CurrentInsertionAction", glyph=g) + xmlWriter.newline() + for g in self.MarkedInsertionAction: + xmlWriter.simpletag("MarkedInsertionAction", glyph=g) + xmlWriter.newline() + xmlWriter.endtag(name) + xmlWriter.newline() - def fromXML(self, name, attrs, content, font): - self.__init__() - content = [t for t in content if isinstance(t, tuple)] - for eltName, eltAttrs, eltContent in content: - if eltName == "NewState": - self.NewState = safeEval(eltAttrs["value"]) - elif eltName == "Flags": - for flag in eltAttrs["value"].split(","): - self._setFlag(flag.strip()) - elif eltName == "CurrentInsertionAction": - self.CurrentInsertionAction.append( - eltAttrs["glyph"]) - elif eltName == "MarkedInsertionAction": - self.MarkedInsertionAction.append( - eltAttrs["glyph"]) - else: - assert False, eltName + def fromXML(self, name, attrs, content, font): + self.__init__() + content = [t for t in content if isinstance(t, tuple)] + for eltName, eltAttrs, eltContent in content: + if eltName == "NewState": + self.NewState = safeEval(eltAttrs["value"]) + elif eltName == "Flags": + for flag in eltAttrs["value"].split(","): + self._setFlag(flag.strip()) + elif eltName == "CurrentInsertionAction": + self.CurrentInsertionAction.append(eltAttrs["glyph"]) + elif eltName == "MarkedInsertionAction": + self.MarkedInsertionAction.append(eltAttrs["glyph"]) + else: + assert False, eltName - @staticmethod - def compileActions(font, states): - actions, actionIndex, result = set(), {}, b"" - for state in states: - for _glyphClass, trans in state.Transitions.items(): - if trans.CurrentInsertionAction is not None: - actions.add(tuple(trans.CurrentInsertionAction)) - if trans.MarkedInsertionAction is not None: - actions.add(tuple(trans.MarkedInsertionAction)) - # Sort the compiled actions in decreasing order of - # length, so that the longer sequence come before the - # shorter ones. - for action in sorted(actions, key=lambda x:(-len(x), x)): - # We insert all sub-sequences of the action glyph sequence - # into actionIndex. For example, if one action triggers on - # glyph sequence [A, B, C, D, E] and another action triggers - # on [C, D], we return result=[A, B, C, D, E] (as list of - # encoded glyph IDs), and actionIndex={('A','B','C','D','E'): 0, - # ('C','D'): 2}. - if action in actionIndex: - continue - for start in range(0, len(action)): - startIndex = (len(result) // 2) + start - for limit in range(start, len(action)): - glyphs = action[start : limit + 1] - actionIndex.setdefault(glyphs, startIndex) - for glyph in action: - glyphID = font.getGlyphID(glyph) - result += struct.pack(">H", glyphID) - return result, actionIndex + @staticmethod + def compileActions(font, states): + actions, actionIndex, result = set(), {}, b"" + for state in states: + for _glyphClass, trans in state.Transitions.items(): + if trans.CurrentInsertionAction is not None: + actions.add(tuple(trans.CurrentInsertionAction)) + if trans.MarkedInsertionAction is not None: + actions.add(tuple(trans.MarkedInsertionAction)) + # Sort the compiled actions in decreasing order of + # length, so that the longer sequence come before the + # shorter ones. + for action in sorted(actions, key=lambda x: (-len(x), x)): + # We insert all sub-sequences of the action glyph sequence + # into actionIndex. For example, if one action triggers on + # glyph sequence [A, B, C, D, E] and another action triggers + # on [C, D], we return result=[A, B, C, D, E] (as list of + # encoded glyph IDs), and actionIndex={('A','B','C','D','E'): 0, + # ('C','D'): 2}. + if action in actionIndex: + continue + for start in range(0, len(action)): + startIndex = (len(result) // 2) + start + for limit in range(start, len(action)): + glyphs = action[start : limit + 1] + actionIndex.setdefault(glyphs, startIndex) + for glyph in action: + glyphID = font.getGlyphID(glyph) + result += struct.pack(">H", glyphID) + return result, actionIndex class FeatureParams(BaseTable): + def compile(self, writer, font): + assert ( + featureParamTypes.get(writer["FeatureTag"]) == self.__class__ + ), "Wrong FeatureParams type for feature '%s': %s" % ( + writer["FeatureTag"], + self.__class__.__name__, + ) + BaseTable.compile(self, writer, font) - def compile(self, writer, font): - assert featureParamTypes.get(writer['FeatureTag']) == self.__class__, "Wrong FeatureParams type for feature '%s': %s" % (writer['FeatureTag'], self.__class__.__name__) - BaseTable.compile(self, writer, font) + def toXML(self, xmlWriter, font, attrs=None, name=None): + BaseTable.toXML(self, xmlWriter, font, attrs, name=self.__class__.__name__) - def toXML(self, xmlWriter, font, attrs=None, name=None): - BaseTable.toXML(self, xmlWriter, font, attrs, name=self.__class__.__name__) class FeatureParamsSize(FeatureParams): - pass + pass + class FeatureParamsStylisticSet(FeatureParams): - pass + pass + class FeatureParamsCharacterVariants(FeatureParams): - pass + pass + class Coverage(FormatSwitchingBaseTable): - # manual implementation to get rid of glyphID dependencies + # manual implementation to get rid of glyphID dependencies - def populateDefaults(self, propagator=None): - if not hasattr(self, 'glyphs'): - self.glyphs = [] + def populateDefaults(self, propagator=None): + if not hasattr(self, "glyphs"): + self.glyphs = [] - def postRead(self, rawTable, font): - if self.Format == 1: - self.glyphs = rawTable["GlyphArray"] - elif self.Format == 2: - glyphs = self.glyphs = [] - ranges = rawTable["RangeRecord"] - # Some SIL fonts have coverage entries that don't have sorted - # StartCoverageIndex. If it is so, fixup and warn. We undo - # this when writing font out. - sorted_ranges = sorted(ranges, key=lambda a: a.StartCoverageIndex) - if ranges != sorted_ranges: - log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.") - ranges = sorted_ranges - del sorted_ranges - for r in ranges: - start = r.Start - end = r.End - startID = font.getGlyphID(start) - endID = font.getGlyphID(end) + 1 - glyphs.extend(font.getGlyphNameMany(range(startID, endID))) - else: - self.glyphs = [] - log.warning("Unknown Coverage format: %s", self.Format) - del self.Format # Don't need this anymore + def postRead(self, rawTable, font): + if self.Format == 1: + self.glyphs = rawTable["GlyphArray"] + elif self.Format == 2: + glyphs = self.glyphs = [] + ranges = rawTable["RangeRecord"] + # Some SIL fonts have coverage entries that don't have sorted + # StartCoverageIndex. If it is so, fixup and warn. We undo + # this when writing font out. + sorted_ranges = sorted(ranges, key=lambda a: a.StartCoverageIndex) + if ranges != sorted_ranges: + log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.") + ranges = sorted_ranges + del sorted_ranges + for r in ranges: + start = r.Start + end = r.End + startID = font.getGlyphID(start) + endID = font.getGlyphID(end) + 1 + glyphs.extend(font.getGlyphNameMany(range(startID, endID))) + else: + self.glyphs = [] + log.warning("Unknown Coverage format: %s", self.Format) + del self.Format # Don't need this anymore - def preWrite(self, font): - glyphs = getattr(self, "glyphs", None) - if glyphs is None: - glyphs = self.glyphs = [] - format = 1 - rawTable = {"GlyphArray": glyphs} - if glyphs: - # find out whether Format 2 is more compact or not - glyphIDs = font.getGlyphIDMany(glyphs) - brokenOrder = sorted(glyphIDs) != glyphIDs + def preWrite(self, font): + glyphs = getattr(self, "glyphs", None) + if glyphs is None: + glyphs = self.glyphs = [] + format = 1 + rawTable = {"GlyphArray": glyphs} + if glyphs: + # find out whether Format 2 is more compact or not + glyphIDs = font.getGlyphIDMany(glyphs) + brokenOrder = sorted(glyphIDs) != glyphIDs - last = glyphIDs[0] - ranges = [[last]] - for glyphID in glyphIDs[1:]: - if glyphID != last + 1: - ranges[-1].append(last) - ranges.append([glyphID]) - last = glyphID - ranges[-1].append(last) + last = glyphIDs[0] + ranges = [[last]] + for glyphID in glyphIDs[1:]: + if glyphID != last + 1: + ranges[-1].append(last) + ranges.append([glyphID]) + last = glyphID + ranges[-1].append(last) - if brokenOrder or len(ranges) * 3 < len(glyphs): # 3 words vs. 1 word - # Format 2 is more compact - index = 0 - for i in range(len(ranges)): - start, end = ranges[i] - r = RangeRecord() - r.StartID = start - r.Start = font.getGlyphName(start) - r.End = font.getGlyphName(end) - r.StartCoverageIndex = index - ranges[i] = r - index = index + end - start + 1 - if brokenOrder: - log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.") - ranges.sort(key=lambda a: a.StartID) - for r in ranges: - del r.StartID - format = 2 - rawTable = {"RangeRecord": ranges} - #else: - # fallthrough; Format 1 is more compact - self.Format = format - return rawTable + if brokenOrder or len(ranges) * 3 < len(glyphs): # 3 words vs. 1 word + # Format 2 is more compact + index = 0 + for i in range(len(ranges)): + start, end = ranges[i] + r = RangeRecord() + r.StartID = start + r.Start = font.getGlyphName(start) + r.End = font.getGlyphName(end) + r.StartCoverageIndex = index + ranges[i] = r + index = index + end - start + 1 + if brokenOrder: + log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.") + ranges.sort(key=lambda a: a.StartID) + for r in ranges: + del r.StartID + format = 2 + rawTable = {"RangeRecord": ranges} + # else: + # fallthrough; Format 1 is more compact + self.Format = format + return rawTable - def toXML2(self, xmlWriter, font): - for glyphName in getattr(self, "glyphs", []): - xmlWriter.simpletag("Glyph", value=glyphName) - xmlWriter.newline() + def toXML2(self, xmlWriter, font): + for glyphName in getattr(self, "glyphs", []): + xmlWriter.simpletag("Glyph", value=glyphName) + xmlWriter.newline() - def fromXML(self, name, attrs, content, font): - glyphs = getattr(self, "glyphs", None) - if glyphs is None: - glyphs = [] - self.glyphs = glyphs - glyphs.append(attrs["value"]) + def fromXML(self, name, attrs, content, font): + glyphs = getattr(self, "glyphs", None) + if glyphs is None: + glyphs = [] + self.glyphs = glyphs + glyphs.append(attrs["value"]) # The special 0xFFFFFFFF delta-set index is used to indicate that there @@ -606,987 +622,977 @@ NO_VARIATION_INDEX = 0xFFFFFFFF class DeltaSetIndexMap(getFormatSwitchingBaseTableClass("uint8")): + def populateDefaults(self, propagator=None): + if not hasattr(self, "mapping"): + self.mapping = [] - def populateDefaults(self, propagator=None): - if not hasattr(self, 'mapping'): - self.mapping = [] + def postRead(self, rawTable, font): + assert (rawTable["EntryFormat"] & 0xFFC0) == 0 + self.mapping = rawTable["mapping"] - def postRead(self, rawTable, font): - assert (rawTable['EntryFormat'] & 0xFFC0) == 0 - self.mapping = rawTable['mapping'] + @staticmethod + def getEntryFormat(mapping): + ored = 0 + for idx in mapping: + ored |= idx - @staticmethod - def getEntryFormat(mapping): - ored = 0 - for idx in mapping: - ored |= idx + inner = ored & 0xFFFF + innerBits = 0 + while inner: + innerBits += 1 + inner >>= 1 + innerBits = max(innerBits, 1) + assert innerBits <= 16 - inner = ored & 0xFFFF - innerBits = 0 - while inner: - innerBits += 1 - inner >>= 1 - innerBits = max(innerBits, 1) - assert innerBits <= 16 + ored = (ored >> (16 - innerBits)) | (ored & ((1 << innerBits) - 1)) + if ored <= 0x000000FF: + entrySize = 1 + elif ored <= 0x0000FFFF: + entrySize = 2 + elif ored <= 0x00FFFFFF: + entrySize = 3 + else: + entrySize = 4 - ored = (ored >> (16-innerBits)) | (ored & ((1< 0xFFFF else 0 + rawTable = self.__dict__.copy() + rawTable["MappingCount"] = len(mapping) + rawTable["EntryFormat"] = self.getEntryFormat(mapping) + return rawTable - def preWrite(self, font): - mapping = getattr(self, "mapping", None) - if mapping is None: - mapping = self.mapping = [] - self.Format = 1 if len(mapping) > 0xFFFF else 0 - rawTable = self.__dict__.copy() - rawTable['MappingCount'] = len(mapping) - rawTable['EntryFormat'] = self.getEntryFormat(mapping) - return rawTable + def toXML2(self, xmlWriter, font): + # Make xml dump less verbose, by omitting no-op entries like: + # + xmlWriter.comment("Omitted values default to 0xFFFF/0xFFFF (no variations)") + xmlWriter.newline() + for i, value in enumerate(getattr(self, "mapping", [])): + attrs = [("index", i)] + if value != NO_VARIATION_INDEX: + attrs.extend( + [ + ("outer", value >> 16), + ("inner", value & 0xFFFF), + ] + ) + xmlWriter.simpletag("Map", attrs) + xmlWriter.newline() - def toXML2(self, xmlWriter, font): - # Make xml dump less verbose, by omitting no-op entries like: - # - xmlWriter.comment( - "Omitted values default to 0xFFFF/0xFFFF (no variations)" - ) - xmlWriter.newline() - for i, value in enumerate(getattr(self, "mapping", [])): - attrs = [('index', i)] - if value != NO_VARIATION_INDEX: - attrs.extend([ - ('outer', value >> 16), - ('inner', value & 0xFFFF), - ]) - xmlWriter.simpletag("Map", attrs) - xmlWriter.newline() - - def fromXML(self, name, attrs, content, font): - mapping = getattr(self, "mapping", None) - if mapping is None: - self.mapping = mapping = [] - index = safeEval(attrs['index']) - outer = safeEval(attrs.get('outer', '0xFFFF')) - inner = safeEval(attrs.get('inner', '0xFFFF')) - assert inner <= 0xFFFF - mapping.insert(index, (outer << 16) | inner) + def fromXML(self, name, attrs, content, font): + mapping = getattr(self, "mapping", None) + if mapping is None: + self.mapping = mapping = [] + index = safeEval(attrs["index"]) + outer = safeEval(attrs.get("outer", "0xFFFF")) + inner = safeEval(attrs.get("inner", "0xFFFF")) + assert inner <= 0xFFFF + mapping.insert(index, (outer << 16) | inner) class VarIdxMap(BaseTable): + def populateDefaults(self, propagator=None): + if not hasattr(self, "mapping"): + self.mapping = {} - def populateDefaults(self, propagator=None): - if not hasattr(self, 'mapping'): - self.mapping = {} + def postRead(self, rawTable, font): + assert (rawTable["EntryFormat"] & 0xFFC0) == 0 + glyphOrder = font.getGlyphOrder() + mapList = rawTable["mapping"] + mapList.extend([mapList[-1]] * (len(glyphOrder) - len(mapList))) + self.mapping = dict(zip(glyphOrder, mapList)) - def postRead(self, rawTable, font): - assert (rawTable['EntryFormat'] & 0xFFC0) == 0 - glyphOrder = font.getGlyphOrder() - mapList = rawTable['mapping'] - mapList.extend([mapList[-1]] * (len(glyphOrder) - len(mapList))) - self.mapping = dict(zip(glyphOrder, mapList)) + def preWrite(self, font): + mapping = getattr(self, "mapping", None) + if mapping is None: + mapping = self.mapping = {} - def preWrite(self, font): - mapping = getattr(self, "mapping", None) - if mapping is None: - mapping = self.mapping = {} + if type(mapping) == dict: + glyphOrder = font.getGlyphOrder() + mapping = [mapping[g] for g in glyphOrder] + while len(mapping) > 1 and mapping[-2] == mapping[-1]: + del mapping[-1] - if type(mapping) == dict: - glyphOrder = font.getGlyphOrder() - mapping = [mapping[g] for g in glyphOrder] - while len(mapping) > 1 and mapping[-2] == mapping[-1]: - del mapping[-1] + rawTable = {"mapping": mapping} + rawTable["MappingCount"] = len(mapping) + rawTable["EntryFormat"] = DeltaSetIndexMap.getEntryFormat(mapping) + return rawTable - rawTable = {'mapping': mapping} - rawTable['MappingCount'] = len(mapping) - rawTable['EntryFormat'] = DeltaSetIndexMap.getEntryFormat(mapping) - return rawTable + def toXML2(self, xmlWriter, font): + for glyph, value in sorted(getattr(self, "mapping", {}).items()): + attrs = ( + ("glyph", glyph), + ("outer", value >> 16), + ("inner", value & 0xFFFF), + ) + xmlWriter.simpletag("Map", attrs) + xmlWriter.newline() - def toXML2(self, xmlWriter, font): - for glyph, value in sorted(getattr(self, "mapping", {}).items()): - attrs = ( - ('glyph', glyph), - ('outer', value >> 16), - ('inner', value & 0xFFFF), - ) - xmlWriter.simpletag("Map", attrs) - xmlWriter.newline() - - def fromXML(self, name, attrs, content, font): - mapping = getattr(self, "mapping", None) - if mapping is None: - mapping = {} - self.mapping = mapping - try: - glyph = attrs['glyph'] - except: # https://github.com/fonttools/fonttools/commit/21cbab8ce9ded3356fef3745122da64dcaf314e9#commitcomment-27649836 - glyph = font.getGlyphOrder()[attrs['index']] - outer = safeEval(attrs['outer']) - inner = safeEval(attrs['inner']) - assert inner <= 0xFFFF - mapping[glyph] = (outer << 16) | inner + def fromXML(self, name, attrs, content, font): + mapping = getattr(self, "mapping", None) + if mapping is None: + mapping = {} + self.mapping = mapping + try: + glyph = attrs["glyph"] + except: # https://github.com/fonttools/fonttools/commit/21cbab8ce9ded3356fef3745122da64dcaf314e9#commitcomment-27649836 + glyph = font.getGlyphOrder()[attrs["index"]] + outer = safeEval(attrs["outer"]) + inner = safeEval(attrs["inner"]) + assert inner <= 0xFFFF + mapping[glyph] = (outer << 16) | inner class VarRegionList(BaseTable): - - def preWrite(self, font): - # The OT spec says VarStore.VarRegionList.RegionAxisCount should always - # be equal to the fvar.axisCount, and OTS < v8.0.0 enforces this rule - # even when the VarRegionList is empty. We can't treat RegionAxisCount - # like a normal propagated count (== len(Region[i].VarRegionAxis)), - # otherwise it would default to 0 if VarRegionList is empty. - # Thus, we force it to always be equal to fvar.axisCount. - # https://github.com/khaledhosny/ots/pull/192 - fvarTable = font.get("fvar") - if fvarTable: - self.RegionAxisCount = len(fvarTable.axes) - return { - **self.__dict__, - "RegionAxisCount": CountReference(self.__dict__, "RegionAxisCount") - } + def preWrite(self, font): + # The OT spec says VarStore.VarRegionList.RegionAxisCount should always + # be equal to the fvar.axisCount, and OTS < v8.0.0 enforces this rule + # even when the VarRegionList is empty. We can't treat RegionAxisCount + # like a normal propagated count (== len(Region[i].VarRegionAxis)), + # otherwise it would default to 0 if VarRegionList is empty. + # Thus, we force it to always be equal to fvar.axisCount. + # https://github.com/khaledhosny/ots/pull/192 + fvarTable = font.get("fvar") + if fvarTable: + self.RegionAxisCount = len(fvarTable.axes) + return { + **self.__dict__, + "RegionAxisCount": CountReference(self.__dict__, "RegionAxisCount"), + } class SingleSubst(FormatSwitchingBaseTable): + def populateDefaults(self, propagator=None): + if not hasattr(self, "mapping"): + self.mapping = {} - def populateDefaults(self, propagator=None): - if not hasattr(self, 'mapping'): - self.mapping = {} + def postRead(self, rawTable, font): + mapping = {} + input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) + if self.Format == 1: + delta = rawTable["DeltaGlyphID"] + inputGIDS = font.getGlyphIDMany(input) + outGIDS = [(glyphID + delta) % 65536 for glyphID in inputGIDS] + outNames = font.getGlyphNameMany(outGIDS) + for inp, out in zip(input, outNames): + mapping[inp] = out + elif self.Format == 2: + assert ( + len(input) == rawTable["GlyphCount"] + ), "invalid SingleSubstFormat2 table" + subst = rawTable["Substitute"] + for inp, sub in zip(input, subst): + mapping[inp] = sub + else: + assert 0, "unknown format: %s" % self.Format + self.mapping = mapping + del self.Format # Don't need this anymore - def postRead(self, rawTable, font): - mapping = {} - input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) - if self.Format == 1: - delta = rawTable["DeltaGlyphID"] - inputGIDS = font.getGlyphIDMany(input) - outGIDS = [ (glyphID + delta) % 65536 for glyphID in inputGIDS ] - outNames = font.getGlyphNameMany(outGIDS) - for inp, out in zip(input, outNames): - mapping[inp] = out - elif self.Format == 2: - assert len(input) == rawTable["GlyphCount"], \ - "invalid SingleSubstFormat2 table" - subst = rawTable["Substitute"] - for inp, sub in zip(input, subst): - mapping[inp] = sub - else: - assert 0, "unknown format: %s" % self.Format - self.mapping = mapping - del self.Format # Don't need this anymore + def preWrite(self, font): + mapping = getattr(self, "mapping", None) + if mapping is None: + mapping = self.mapping = {} + items = list(mapping.items()) + getGlyphID = font.getGlyphID + gidItems = [(getGlyphID(a), getGlyphID(b)) for a, b in items] + sortableItems = sorted(zip(gidItems, items)) - def preWrite(self, font): - mapping = getattr(self, "mapping", None) - if mapping is None: - mapping = self.mapping = {} - items = list(mapping.items()) - getGlyphID = font.getGlyphID - gidItems = [(getGlyphID(a), getGlyphID(b)) for a,b in items] - sortableItems = sorted(zip(gidItems, items)) + # figure out format + format = 2 + delta = None + for inID, outID in gidItems: + if delta is None: + delta = (outID - inID) % 65536 - # figure out format - format = 2 - delta = None - for inID, outID in gidItems: - if delta is None: - delta = (outID - inID) % 65536 + if (inID + delta) % 65536 != outID: + break + else: + if delta is None: + # the mapping is empty, better use format 2 + format = 2 + else: + format = 1 - if (inID + delta) % 65536 != outID: - break - else: - if delta is None: - # the mapping is empty, better use format 2 - format = 2 - else: - format = 1 + rawTable = {} + self.Format = format + cov = Coverage() + input = [item[1][0] for item in sortableItems] + subst = [item[1][1] for item in sortableItems] + cov.glyphs = input + rawTable["Coverage"] = cov + if format == 1: + assert delta is not None + rawTable["DeltaGlyphID"] = delta + else: + rawTable["Substitute"] = subst + return rawTable - rawTable = {} - self.Format = format - cov = Coverage() - input = [ item [1][0] for item in sortableItems] - subst = [ item [1][1] for item in sortableItems] - cov.glyphs = input - rawTable["Coverage"] = cov - if format == 1: - assert delta is not None - rawTable["DeltaGlyphID"] = delta - else: - rawTable["Substitute"] = subst - return rawTable + def toXML2(self, xmlWriter, font): + items = sorted(self.mapping.items()) + for inGlyph, outGlyph in items: + xmlWriter.simpletag("Substitution", [("in", inGlyph), ("out", outGlyph)]) + xmlWriter.newline() - def toXML2(self, xmlWriter, font): - items = sorted(self.mapping.items()) - for inGlyph, outGlyph in items: - xmlWriter.simpletag("Substitution", - [("in", inGlyph), ("out", outGlyph)]) - xmlWriter.newline() - - def fromXML(self, name, attrs, content, font): - mapping = getattr(self, "mapping", None) - if mapping is None: - mapping = {} - self.mapping = mapping - mapping[attrs["in"]] = attrs["out"] + def fromXML(self, name, attrs, content, font): + mapping = getattr(self, "mapping", None) + if mapping is None: + mapping = {} + self.mapping = mapping + mapping[attrs["in"]] = attrs["out"] class MultipleSubst(FormatSwitchingBaseTable): + def populateDefaults(self, propagator=None): + if not hasattr(self, "mapping"): + self.mapping = {} - def populateDefaults(self, propagator=None): - if not hasattr(self, 'mapping'): - self.mapping = {} + def postRead(self, rawTable, font): + mapping = {} + if self.Format == 1: + glyphs = _getGlyphsFromCoverageTable(rawTable["Coverage"]) + subst = [s.Substitute for s in rawTable["Sequence"]] + mapping = dict(zip(glyphs, subst)) + else: + assert 0, "unknown format: %s" % self.Format + self.mapping = mapping + del self.Format # Don't need this anymore - def postRead(self, rawTable, font): - mapping = {} - if self.Format == 1: - glyphs = _getGlyphsFromCoverageTable(rawTable["Coverage"]) - subst = [s.Substitute for s in rawTable["Sequence"]] - mapping = dict(zip(glyphs, subst)) - else: - assert 0, "unknown format: %s" % self.Format - self.mapping = mapping - del self.Format # Don't need this anymore + def preWrite(self, font): + mapping = getattr(self, "mapping", None) + if mapping is None: + mapping = self.mapping = {} + cov = Coverage() + cov.glyphs = sorted(list(mapping.keys()), key=font.getGlyphID) + self.Format = 1 + rawTable = { + "Coverage": cov, + "Sequence": [self.makeSequence_(mapping[glyph]) for glyph in cov.glyphs], + } + return rawTable - def preWrite(self, font): - mapping = getattr(self, "mapping", None) - if mapping is None: - mapping = self.mapping = {} - cov = Coverage() - cov.glyphs = sorted(list(mapping.keys()), key=font.getGlyphID) - self.Format = 1 - rawTable = { - "Coverage": cov, - "Sequence": [self.makeSequence_(mapping[glyph]) - for glyph in cov.glyphs], - } - return rawTable + def toXML2(self, xmlWriter, font): + items = sorted(self.mapping.items()) + for inGlyph, outGlyphs in items: + out = ",".join(outGlyphs) + xmlWriter.simpletag("Substitution", [("in", inGlyph), ("out", out)]) + xmlWriter.newline() - def toXML2(self, xmlWriter, font): - items = sorted(self.mapping.items()) - for inGlyph, outGlyphs in items: - out = ",".join(outGlyphs) - xmlWriter.simpletag("Substitution", - [("in", inGlyph), ("out", out)]) - xmlWriter.newline() + def fromXML(self, name, attrs, content, font): + mapping = getattr(self, "mapping", None) + if mapping is None: + mapping = {} + self.mapping = mapping - def fromXML(self, name, attrs, content, font): - mapping = getattr(self, "mapping", None) - if mapping is None: - mapping = {} - self.mapping = mapping + # TTX v3.0 and earlier. + if name == "Coverage": + self.old_coverage_ = [] + for element in content: + if not isinstance(element, tuple): + continue + element_name, element_attrs, _ = element + if element_name == "Glyph": + self.old_coverage_.append(element_attrs["value"]) + return + if name == "Sequence": + index = int(attrs.get("index", len(mapping))) + glyph = self.old_coverage_[index] + glyph_mapping = mapping[glyph] = [] + for element in content: + if not isinstance(element, tuple): + continue + element_name, element_attrs, _ = element + if element_name == "Substitute": + glyph_mapping.append(element_attrs["value"]) + return - # TTX v3.0 and earlier. - if name == "Coverage": - self.old_coverage_ = [] - for element in content: - if not isinstance(element, tuple): - continue - element_name, element_attrs, _ = element - if element_name == "Glyph": - self.old_coverage_.append(element_attrs["value"]) - return - if name == "Sequence": - index = int(attrs.get("index", len(mapping))) - glyph = self.old_coverage_[index] - glyph_mapping = mapping[glyph] = [] - for element in content: - if not isinstance(element, tuple): - continue - element_name, element_attrs, _ = element - if element_name == "Substitute": - glyph_mapping.append(element_attrs["value"]) - return + # TTX v3.1 and later. + outGlyphs = attrs["out"].split(",") if attrs["out"] else [] + mapping[attrs["in"]] = [g.strip() for g in outGlyphs] - # TTX v3.1 and later. - outGlyphs = attrs["out"].split(",") if attrs["out"] else [] - mapping[attrs["in"]] = [g.strip() for g in outGlyphs] - - @staticmethod - def makeSequence_(g): - seq = Sequence() - seq.Substitute = g - return seq + @staticmethod + def makeSequence_(g): + seq = Sequence() + seq.Substitute = g + return seq class ClassDef(FormatSwitchingBaseTable): + def populateDefaults(self, propagator=None): + if not hasattr(self, "classDefs"): + self.classDefs = {} - def populateDefaults(self, propagator=None): - if not hasattr(self, 'classDefs'): - self.classDefs = {} + def postRead(self, rawTable, font): + classDefs = {} - def postRead(self, rawTable, font): - classDefs = {} + if self.Format == 1: + start = rawTable["StartGlyph"] + classList = rawTable["ClassValueArray"] + startID = font.getGlyphID(start) + endID = startID + len(classList) + glyphNames = font.getGlyphNameMany(range(startID, endID)) + for glyphName, cls in zip(glyphNames, classList): + if cls: + classDefs[glyphName] = cls - if self.Format == 1: - start = rawTable["StartGlyph"] - classList = rawTable["ClassValueArray"] - startID = font.getGlyphID(start) - endID = startID + len(classList) - glyphNames = font.getGlyphNameMany(range(startID, endID)) - for glyphName, cls in zip(glyphNames, classList): - if cls: - classDefs[glyphName] = cls + elif self.Format == 2: + records = rawTable["ClassRangeRecord"] + for rec in records: + cls = rec.Class + if not cls: + continue + start = rec.Start + end = rec.End + startID = font.getGlyphID(start) + endID = font.getGlyphID(end) + 1 + glyphNames = font.getGlyphNameMany(range(startID, endID)) + for glyphName in glyphNames: + classDefs[glyphName] = cls + else: + log.warning("Unknown ClassDef format: %s", self.Format) + self.classDefs = classDefs + del self.Format # Don't need this anymore - elif self.Format == 2: - records = rawTable["ClassRangeRecord"] - for rec in records: - cls = rec.Class - if not cls: - continue - start = rec.Start - end = rec.End - startID = font.getGlyphID(start) - endID = font.getGlyphID(end) + 1 - glyphNames = font.getGlyphNameMany(range(startID, endID)) - for glyphName in glyphNames: - classDefs[glyphName] = cls - else: - log.warning("Unknown ClassDef format: %s", self.Format) - self.classDefs = classDefs - del self.Format # Don't need this anymore + def _getClassRanges(self, font): + classDefs = getattr(self, "classDefs", None) + if classDefs is None: + self.classDefs = {} + return + getGlyphID = font.getGlyphID + items = [] + for glyphName, cls in classDefs.items(): + if not cls: + continue + items.append((getGlyphID(glyphName), glyphName, cls)) + if items: + items.sort() + last, lastName, lastCls = items[0] + ranges = [[lastCls, last, lastName]] + for glyphID, glyphName, cls in items[1:]: + if glyphID != last + 1 or cls != lastCls: + ranges[-1].extend([last, lastName]) + ranges.append([cls, glyphID, glyphName]) + last = glyphID + lastName = glyphName + lastCls = cls + ranges[-1].extend([last, lastName]) + return ranges - def _getClassRanges(self, font): - classDefs = getattr(self, "classDefs", None) - if classDefs is None: - self.classDefs = {} - return - getGlyphID = font.getGlyphID - items = [] - for glyphName, cls in classDefs.items(): - if not cls: - continue - items.append((getGlyphID(glyphName), glyphName, cls)) - if items: - items.sort() - last, lastName, lastCls = items[0] - ranges = [[lastCls, last, lastName]] - for glyphID, glyphName, cls in items[1:]: - if glyphID != last + 1 or cls != lastCls: - ranges[-1].extend([last, lastName]) - ranges.append([cls, glyphID, glyphName]) - last = glyphID - lastName = glyphName - lastCls = cls - ranges[-1].extend([last, lastName]) - return ranges + def preWrite(self, font): + format = 2 + rawTable = {"ClassRangeRecord": []} + ranges = self._getClassRanges(font) + if ranges: + startGlyph = ranges[0][1] + endGlyph = ranges[-1][3] + glyphCount = endGlyph - startGlyph + 1 + if len(ranges) * 3 < glyphCount + 1: + # Format 2 is more compact + for i in range(len(ranges)): + cls, start, startName, end, endName = ranges[i] + rec = ClassRangeRecord() + rec.Start = startName + rec.End = endName + rec.Class = cls + ranges[i] = rec + format = 2 + rawTable = {"ClassRangeRecord": ranges} + else: + # Format 1 is more compact + startGlyphName = ranges[0][2] + classes = [0] * glyphCount + for cls, start, startName, end, endName in ranges: + for g in range(start - startGlyph, end - startGlyph + 1): + classes[g] = cls + format = 1 + rawTable = {"StartGlyph": startGlyphName, "ClassValueArray": classes} + self.Format = format + return rawTable - def preWrite(self, font): - format = 2 - rawTable = {"ClassRangeRecord": []} - ranges = self._getClassRanges(font) - if ranges: - startGlyph = ranges[0][1] - endGlyph = ranges[-1][3] - glyphCount = endGlyph - startGlyph + 1 - if len(ranges) * 3 < glyphCount + 1: - # Format 2 is more compact - for i in range(len(ranges)): - cls, start, startName, end, endName = ranges[i] - rec = ClassRangeRecord() - rec.Start = startName - rec.End = endName - rec.Class = cls - ranges[i] = rec - format = 2 - rawTable = {"ClassRangeRecord": ranges} - else: - # Format 1 is more compact - startGlyphName = ranges[0][2] - classes = [0] * glyphCount - for cls, start, startName, end, endName in ranges: - for g in range(start - startGlyph, end - startGlyph + 1): - classes[g] = cls - format = 1 - rawTable = {"StartGlyph": startGlyphName, "ClassValueArray": classes} - self.Format = format - return rawTable + def toXML2(self, xmlWriter, font): + items = sorted(self.classDefs.items()) + for glyphName, cls in items: + xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)]) + xmlWriter.newline() - def toXML2(self, xmlWriter, font): - items = sorted(self.classDefs.items()) - for glyphName, cls in items: - xmlWriter.simpletag("ClassDef", [("glyph", glyphName), ("class", cls)]) - xmlWriter.newline() - - def fromXML(self, name, attrs, content, font): - classDefs = getattr(self, "classDefs", None) - if classDefs is None: - classDefs = {} - self.classDefs = classDefs - classDefs[attrs["glyph"]] = int(attrs["class"]) + def fromXML(self, name, attrs, content, font): + classDefs = getattr(self, "classDefs", None) + if classDefs is None: + classDefs = {} + self.classDefs = classDefs + classDefs[attrs["glyph"]] = int(attrs["class"]) class AlternateSubst(FormatSwitchingBaseTable): + def populateDefaults(self, propagator=None): + if not hasattr(self, "alternates"): + self.alternates = {} - def populateDefaults(self, propagator=None): - if not hasattr(self, 'alternates'): - self.alternates = {} + def postRead(self, rawTable, font): + alternates = {} + if self.Format == 1: + input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) + alts = rawTable["AlternateSet"] + assert len(input) == len(alts) + for inp, alt in zip(input, alts): + alternates[inp] = alt.Alternate + else: + assert 0, "unknown format: %s" % self.Format + self.alternates = alternates + del self.Format # Don't need this anymore - def postRead(self, rawTable, font): - alternates = {} - if self.Format == 1: - input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) - alts = rawTable["AlternateSet"] - assert len(input) == len(alts) - for inp,alt in zip(input,alts): - alternates[inp] = alt.Alternate - else: - assert 0, "unknown format: %s" % self.Format - self.alternates = alternates - del self.Format # Don't need this anymore + def preWrite(self, font): + self.Format = 1 + alternates = getattr(self, "alternates", None) + if alternates is None: + alternates = self.alternates = {} + items = list(alternates.items()) + for i in range(len(items)): + glyphName, set = items[i] + items[i] = font.getGlyphID(glyphName), glyphName, set + items.sort() + cov = Coverage() + cov.glyphs = [item[1] for item in items] + alternates = [] + setList = [item[-1] for item in items] + for set in setList: + alts = AlternateSet() + alts.Alternate = set + alternates.append(alts) + # a special case to deal with the fact that several hundred Adobe Japan1-5 + # CJK fonts will overflow an offset if the coverage table isn't pushed to the end. + # Also useful in that when splitting a sub-table because of an offset overflow + # I don't need to calculate the change in the subtable offset due to the change in the coverage table size. + # Allows packing more rules in subtable. + self.sortCoverageLast = 1 + return {"Coverage": cov, "AlternateSet": alternates} - def preWrite(self, font): - self.Format = 1 - alternates = getattr(self, "alternates", None) - if alternates is None: - alternates = self.alternates = {} - items = list(alternates.items()) - for i in range(len(items)): - glyphName, set = items[i] - items[i] = font.getGlyphID(glyphName), glyphName, set - items.sort() - cov = Coverage() - cov.glyphs = [ item[1] for item in items] - alternates = [] - setList = [ item[-1] for item in items] - for set in setList: - alts = AlternateSet() - alts.Alternate = set - alternates.append(alts) - # a special case to deal with the fact that several hundred Adobe Japan1-5 - # CJK fonts will overflow an offset if the coverage table isn't pushed to the end. - # Also useful in that when splitting a sub-table because of an offset overflow - # I don't need to calculate the change in the subtable offset due to the change in the coverage table size. - # Allows packing more rules in subtable. - self.sortCoverageLast = 1 - return {"Coverage": cov, "AlternateSet": alternates} + def toXML2(self, xmlWriter, font): + items = sorted(self.alternates.items()) + for glyphName, alternates in items: + xmlWriter.begintag("AlternateSet", glyph=glyphName) + xmlWriter.newline() + for alt in alternates: + xmlWriter.simpletag("Alternate", glyph=alt) + xmlWriter.newline() + xmlWriter.endtag("AlternateSet") + xmlWriter.newline() - def toXML2(self, xmlWriter, font): - items = sorted(self.alternates.items()) - for glyphName, alternates in items: - xmlWriter.begintag("AlternateSet", glyph=glyphName) - xmlWriter.newline() - for alt in alternates: - xmlWriter.simpletag("Alternate", glyph=alt) - xmlWriter.newline() - xmlWriter.endtag("AlternateSet") - xmlWriter.newline() - - def fromXML(self, name, attrs, content, font): - alternates = getattr(self, "alternates", None) - if alternates is None: - alternates = {} - self.alternates = alternates - glyphName = attrs["glyph"] - set = [] - alternates[glyphName] = set - for element in content: - if not isinstance(element, tuple): - continue - name, attrs, content = element - set.append(attrs["glyph"]) + def fromXML(self, name, attrs, content, font): + alternates = getattr(self, "alternates", None) + if alternates is None: + alternates = {} + self.alternates = alternates + glyphName = attrs["glyph"] + set = [] + alternates[glyphName] = set + for element in content: + if not isinstance(element, tuple): + continue + name, attrs, content = element + set.append(attrs["glyph"]) class LigatureSubst(FormatSwitchingBaseTable): + def populateDefaults(self, propagator=None): + if not hasattr(self, "ligatures"): + self.ligatures = {} - def populateDefaults(self, propagator=None): - if not hasattr(self, 'ligatures'): - self.ligatures = {} + def postRead(self, rawTable, font): + ligatures = {} + if self.Format == 1: + input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) + ligSets = rawTable["LigatureSet"] + assert len(input) == len(ligSets) + for i in range(len(input)): + ligatures[input[i]] = ligSets[i].Ligature + else: + assert 0, "unknown format: %s" % self.Format + self.ligatures = ligatures + del self.Format # Don't need this anymore - def postRead(self, rawTable, font): - ligatures = {} - if self.Format == 1: - input = _getGlyphsFromCoverageTable(rawTable["Coverage"]) - ligSets = rawTable["LigatureSet"] - assert len(input) == len(ligSets) - for i in range(len(input)): - ligatures[input[i]] = ligSets[i].Ligature - else: - assert 0, "unknown format: %s" % self.Format - self.ligatures = ligatures - del self.Format # Don't need this anymore + def preWrite(self, font): + self.Format = 1 + ligatures = getattr(self, "ligatures", None) + if ligatures is None: + ligatures = self.ligatures = {} - def preWrite(self, font): - self.Format = 1 - ligatures = getattr(self, "ligatures", None) - if ligatures is None: - ligatures = self.ligatures = {} + if ligatures and isinstance(next(iter(ligatures)), tuple): + # New high-level API in v3.1 and later. Note that we just support compiling this + # for now. We don't load to this API, and don't do XML with it. - if ligatures and isinstance(next(iter(ligatures)), tuple): - # New high-level API in v3.1 and later. Note that we just support compiling this - # for now. We don't load to this API, and don't do XML with it. + # ligatures is map from components-sequence to lig-glyph + newLigatures = dict() + for comps, lig in sorted( + ligatures.items(), key=lambda item: (-len(item[0]), item[0]) + ): + ligature = Ligature() + ligature.Component = comps[1:] + ligature.CompCount = len(comps) + ligature.LigGlyph = lig + newLigatures.setdefault(comps[0], []).append(ligature) + ligatures = newLigatures - # ligatures is map from components-sequence to lig-glyph - newLigatures = dict() - for comps,lig in sorted(ligatures.items(), key=lambda item: (-len(item[0]), item[0])): - ligature = Ligature() - ligature.Component = comps[1:] - ligature.CompCount = len(comps) - ligature.LigGlyph = lig - newLigatures.setdefault(comps[0], []).append(ligature) - ligatures = newLigatures + items = list(ligatures.items()) + for i in range(len(items)): + glyphName, set = items[i] + items[i] = font.getGlyphID(glyphName), glyphName, set + items.sort() + cov = Coverage() + cov.glyphs = [item[1] for item in items] - items = list(ligatures.items()) - for i in range(len(items)): - glyphName, set = items[i] - items[i] = font.getGlyphID(glyphName), glyphName, set - items.sort() - cov = Coverage() - cov.glyphs = [ item[1] for item in items] + ligSets = [] + setList = [item[-1] for item in items] + for set in setList: + ligSet = LigatureSet() + ligs = ligSet.Ligature = [] + for lig in set: + ligs.append(lig) + ligSets.append(ligSet) + # Useful in that when splitting a sub-table because of an offset overflow + # I don't need to calculate the change in subtabl offset due to the coverage table size. + # Allows packing more rules in subtable. + self.sortCoverageLast = 1 + return {"Coverage": cov, "LigatureSet": ligSets} - ligSets = [] - setList = [ item[-1] for item in items ] - for set in setList: - ligSet = LigatureSet() - ligs = ligSet.Ligature = [] - for lig in set: - ligs.append(lig) - ligSets.append(ligSet) - # Useful in that when splitting a sub-table because of an offset overflow - # I don't need to calculate the change in subtabl offset due to the coverage table size. - # Allows packing more rules in subtable. - self.sortCoverageLast = 1 - return {"Coverage": cov, "LigatureSet": ligSets} + def toXML2(self, xmlWriter, font): + items = sorted(self.ligatures.items()) + for glyphName, ligSets in items: + xmlWriter.begintag("LigatureSet", glyph=glyphName) + xmlWriter.newline() + for lig in ligSets: + xmlWriter.simpletag( + "Ligature", glyph=lig.LigGlyph, components=",".join(lig.Component) + ) + xmlWriter.newline() + xmlWriter.endtag("LigatureSet") + xmlWriter.newline() - def toXML2(self, xmlWriter, font): - items = sorted(self.ligatures.items()) - for glyphName, ligSets in items: - xmlWriter.begintag("LigatureSet", glyph=glyphName) - xmlWriter.newline() - for lig in ligSets: - xmlWriter.simpletag("Ligature", glyph=lig.LigGlyph, - components=",".join(lig.Component)) - xmlWriter.newline() - xmlWriter.endtag("LigatureSet") - xmlWriter.newline() - - def fromXML(self, name, attrs, content, font): - ligatures = getattr(self, "ligatures", None) - if ligatures is None: - ligatures = {} - self.ligatures = ligatures - glyphName = attrs["glyph"] - ligs = [] - ligatures[glyphName] = ligs - for element in content: - if not isinstance(element, tuple): - continue - name, attrs, content = element - lig = Ligature() - lig.LigGlyph = attrs["glyph"] - components = attrs["components"] - lig.Component = components.split(",") if components else [] - lig.CompCount = len(lig.Component) - ligs.append(lig) + def fromXML(self, name, attrs, content, font): + ligatures = getattr(self, "ligatures", None) + if ligatures is None: + ligatures = {} + self.ligatures = ligatures + glyphName = attrs["glyph"] + ligs = [] + ligatures[glyphName] = ligs + for element in content: + if not isinstance(element, tuple): + continue + name, attrs, content = element + lig = Ligature() + lig.LigGlyph = attrs["glyph"] + components = attrs["components"] + lig.Component = components.split(",") if components else [] + lig.CompCount = len(lig.Component) + ligs.append(lig) class COLR(BaseTable): + def decompile(self, reader, font): + # COLRv0 is exceptional in that LayerRecordCount appears *after* the + # LayerRecordArray it counts, but the parser logic expects Count fields + # to always precede the arrays. Here we work around this by parsing the + # LayerRecordCount before the rest of the table, and storing it in + # the reader's local state. + subReader = reader.getSubReader(offset=0) + for conv in self.getConverters(): + if conv.name != "LayerRecordCount": + subReader.advance(conv.staticSize) + continue + reader[conv.name] = conv.read(subReader, font, tableDict={}) + break + else: + raise AssertionError("LayerRecordCount converter not found") + return BaseTable.decompile(self, reader, font) - def decompile(self, reader, font): - # COLRv0 is exceptional in that LayerRecordCount appears *after* the - # LayerRecordArray it counts, but the parser logic expects Count fields - # to always precede the arrays. Here we work around this by parsing the - # LayerRecordCount before the rest of the table, and storing it in - # the reader's local state. - subReader = reader.getSubReader(offset=0) - for conv in self.getConverters(): - if conv.name != "LayerRecordCount": - subReader.advance(conv.staticSize) - continue - reader[conv.name] = conv.read(subReader, font, tableDict={}) - break - else: - raise AssertionError("LayerRecordCount converter not found") - return BaseTable.decompile(self, reader, font) - - def preWrite(self, font): - # The writer similarly assumes Count values precede the things counted, - # thus here we pre-initialize a CountReference; the actual count value - # will be set to the lenght of the array by the time this is assembled. - self.LayerRecordCount = None - return { - **self.__dict__, - "LayerRecordCount": CountReference(self.__dict__, "LayerRecordCount") - } + def preWrite(self, font): + # The writer similarly assumes Count values precede the things counted, + # thus here we pre-initialize a CountReference; the actual count value + # will be set to the lenght of the array by the time this is assembled. + self.LayerRecordCount = None + return { + **self.__dict__, + "LayerRecordCount": CountReference(self.__dict__, "LayerRecordCount"), + } class LookupList(BaseTable): - @property - def table(self): - for l in self.Lookup: - for st in l.SubTable: - if type(st).__name__.endswith("Subst"): - return "GSUB" - if type(st).__name__.endswith("Pos"): - return "GPOS" - raise ValueError + @property + def table(self): + for l in self.Lookup: + for st in l.SubTable: + if type(st).__name__.endswith("Subst"): + return "GSUB" + if type(st).__name__.endswith("Pos"): + return "GPOS" + raise ValueError - def toXML2(self, xmlWriter, font): - if not font or "Debg" not in font or LOOKUP_DEBUG_INFO_KEY not in font["Debg"].data: - return super().toXML2(xmlWriter, font) - debugData = font["Debg"].data[LOOKUP_DEBUG_INFO_KEY][self.table] - for conv in self.getConverters(): - if conv.repeat: - value = getattr(self, conv.name, []) - for lookupIndex, item in enumerate(value): - if str(lookupIndex) in debugData: - info = LookupDebugInfo(*debugData[str(lookupIndex)]) - tag = info.location - if info.name: - tag = f'{info.name}: {tag}' - if info.feature: - script,language,feature = info.feature - tag = f'{tag} in {feature} ({script}/{language})' - xmlWriter.comment(tag) - xmlWriter.newline() + def toXML2(self, xmlWriter, font): + if ( + not font + or "Debg" not in font + or LOOKUP_DEBUG_INFO_KEY not in font["Debg"].data + ): + return super().toXML2(xmlWriter, font) + debugData = font["Debg"].data[LOOKUP_DEBUG_INFO_KEY][self.table] + for conv in self.getConverters(): + if conv.repeat: + value = getattr(self, conv.name, []) + for lookupIndex, item in enumerate(value): + if str(lookupIndex) in debugData: + info = LookupDebugInfo(*debugData[str(lookupIndex)]) + tag = info.location + if info.name: + tag = f"{info.name}: {tag}" + if info.feature: + script, language, feature = info.feature + tag = f"{tag} in {feature} ({script}/{language})" + xmlWriter.comment(tag) + xmlWriter.newline() + + conv.xmlWrite( + xmlWriter, font, item, conv.name, [("index", lookupIndex)] + ) + else: + if conv.aux and not eval(conv.aux, None, vars(self)): + continue + value = getattr( + self, conv.name, None + ) # TODO Handle defaults instead of defaulting to None! + conv.xmlWrite(xmlWriter, font, value, conv.name, []) - conv.xmlWrite(xmlWriter, font, item, conv.name, - [("index", lookupIndex)]) - else: - if conv.aux and not eval(conv.aux, None, vars(self)): - continue - value = getattr(self, conv.name, None) # TODO Handle defaults instead of defaulting to None! - conv.xmlWrite(xmlWriter, font, value, conv.name, []) class BaseGlyphRecordArray(BaseTable): - - def preWrite(self, font): - self.BaseGlyphRecord = sorted( - self.BaseGlyphRecord, - key=lambda rec: font.getGlyphID(rec.BaseGlyph) - ) - return self.__dict__.copy() + def preWrite(self, font): + self.BaseGlyphRecord = sorted( + self.BaseGlyphRecord, key=lambda rec: font.getGlyphID(rec.BaseGlyph) + ) + return self.__dict__.copy() class BaseGlyphList(BaseTable): - - def preWrite(self, font): - self.BaseGlyphPaintRecord = sorted( - self.BaseGlyphPaintRecord, - key=lambda rec: font.getGlyphID(rec.BaseGlyph) - ) - return self.__dict__.copy() + def preWrite(self, font): + self.BaseGlyphPaintRecord = sorted( + self.BaseGlyphPaintRecord, key=lambda rec: font.getGlyphID(rec.BaseGlyph) + ) + return self.__dict__.copy() class ClipBoxFormat(IntEnum): - Static = 1 - Variable = 2 + Static = 1 + Variable = 2 - def is_variable(self): - return self is self.Variable + def is_variable(self): + return self is self.Variable - def as_variable(self): - return self.Variable + def as_variable(self): + return self.Variable class ClipBox(getFormatSwitchingBaseTableClass("uint8")): - formatEnum = ClipBoxFormat + formatEnum = ClipBoxFormat - def as_tuple(self): - return tuple(getattr(self, conv.name) for conv in self.getConverters()) + def as_tuple(self): + return tuple(getattr(self, conv.name) for conv in self.getConverters()) - def __repr__(self): - return f"{self.__class__.__name__}{self.as_tuple()}" + def __repr__(self): + return f"{self.__class__.__name__}{self.as_tuple()}" class ClipList(getFormatSwitchingBaseTableClass("uint8")): + def populateDefaults(self, propagator=None): + if not hasattr(self, "clips"): + self.clips = {} - def populateDefaults(self, propagator=None): - if not hasattr(self, "clips"): - self.clips = {} + def postRead(self, rawTable, font): + clips = {} + glyphOrder = font.getGlyphOrder() + for i, rec in enumerate(rawTable["ClipRecord"]): + if rec.StartGlyphID > rec.EndGlyphID: + log.warning( + "invalid ClipRecord[%i].StartGlyphID (%i) > " + "EndGlyphID (%i); skipped", + i, + rec.StartGlyphID, + rec.EndGlyphID, + ) + continue + redefinedGlyphs = [] + missingGlyphs = [] + for glyphID in range(rec.StartGlyphID, rec.EndGlyphID + 1): + try: + glyph = glyphOrder[glyphID] + except IndexError: + missingGlyphs.append(glyphID) + continue + if glyph not in clips: + clips[glyph] = copy.copy(rec.ClipBox) + else: + redefinedGlyphs.append(glyphID) + if redefinedGlyphs: + log.warning( + "ClipRecord[%i] overlaps previous records; " + "ignoring redefined clip boxes for the " + "following glyph ID range: [%i-%i]", + i, + min(redefinedGlyphs), + max(redefinedGlyphs), + ) + if missingGlyphs: + log.warning( + "ClipRecord[%i] range references missing " "glyph IDs: [%i-%i]", + i, + min(missingGlyphs), + max(missingGlyphs), + ) + self.clips = clips - def postRead(self, rawTable, font): - clips = {} - glyphOrder = font.getGlyphOrder() - for i, rec in enumerate(rawTable["ClipRecord"]): - if rec.StartGlyphID > rec.EndGlyphID: - log.warning( - "invalid ClipRecord[%i].StartGlyphID (%i) > " - "EndGlyphID (%i); skipped", - i, - rec.StartGlyphID, - rec.EndGlyphID, - ) - continue - redefinedGlyphs = [] - missingGlyphs = [] - for glyphID in range(rec.StartGlyphID, rec.EndGlyphID + 1): - try: - glyph = glyphOrder[glyphID] - except IndexError: - missingGlyphs.append(glyphID) - continue - if glyph not in clips: - clips[glyph] = copy.copy(rec.ClipBox) - else: - redefinedGlyphs.append(glyphID) - if redefinedGlyphs: - log.warning( - "ClipRecord[%i] overlaps previous records; " - "ignoring redefined clip boxes for the " - "following glyph ID range: [%i-%i]", - i, - min(redefinedGlyphs), - max(redefinedGlyphs), - ) - if missingGlyphs: - log.warning( - "ClipRecord[%i] range references missing " - "glyph IDs: [%i-%i]", - i, - min(missingGlyphs), - max(missingGlyphs), - ) - self.clips = clips + def groups(self): + glyphsByClip = defaultdict(list) + uniqueClips = {} + for glyphName, clipBox in self.clips.items(): + key = clipBox.as_tuple() + glyphsByClip[key].append(glyphName) + if key not in uniqueClips: + uniqueClips[key] = clipBox + return { + frozenset(glyphs): uniqueClips[key] for key, glyphs in glyphsByClip.items() + } - def groups(self): - glyphsByClip = defaultdict(list) - uniqueClips = {} - for glyphName, clipBox in self.clips.items(): - key = clipBox.as_tuple() - glyphsByClip[key].append(glyphName) - if key not in uniqueClips: - uniqueClips[key] = clipBox - return { - frozenset(glyphs): uniqueClips[key] - for key, glyphs in glyphsByClip.items() - } + def preWrite(self, font): + if not hasattr(self, "clips"): + self.clips = {} + clipBoxRanges = {} + glyphMap = font.getReverseGlyphMap() + for glyphs, clipBox in self.groups().items(): + glyphIDs = sorted( + glyphMap[glyphName] for glyphName in glyphs if glyphName in glyphMap + ) + if not glyphIDs: + continue + last = glyphIDs[0] + ranges = [[last]] + for glyphID in glyphIDs[1:]: + if glyphID != last + 1: + ranges[-1].append(last) + ranges.append([glyphID]) + last = glyphID + ranges[-1].append(last) + for start, end in ranges: + assert (start, end) not in clipBoxRanges + clipBoxRanges[(start, end)] = clipBox - def preWrite(self, font): - if not hasattr(self, "clips"): - self.clips = {} - clipBoxRanges = {} - glyphMap = font.getReverseGlyphMap() - for glyphs, clipBox in self.groups().items(): - glyphIDs = sorted( - glyphMap[glyphName] for glyphName in glyphs - if glyphName in glyphMap - ) - if not glyphIDs: - continue - last = glyphIDs[0] - ranges = [[last]] - for glyphID in glyphIDs[1:]: - if glyphID != last + 1: - ranges[-1].append(last) - ranges.append([glyphID]) - last = glyphID - ranges[-1].append(last) - for start, end in ranges: - assert (start, end) not in clipBoxRanges - clipBoxRanges[(start, end)] = clipBox + clipRecords = [] + for (start, end), clipBox in sorted(clipBoxRanges.items()): + record = ClipRecord() + record.StartGlyphID = start + record.EndGlyphID = end + record.ClipBox = clipBox + clipRecords.append(record) + rawTable = { + "ClipCount": len(clipRecords), + "ClipRecord": clipRecords, + } + return rawTable - clipRecords = [] - for (start, end), clipBox in sorted(clipBoxRanges.items()): - record = ClipRecord() - record.StartGlyphID = start - record.EndGlyphID = end - record.ClipBox = clipBox - clipRecords.append(record) - rawTable = { - "ClipCount": len(clipRecords), - "ClipRecord": clipRecords, - } - return rawTable + def toXML(self, xmlWriter, font, attrs=None, name=None): + tableName = name if name else self.__class__.__name__ + if attrs is None: + attrs = [] + if hasattr(self, "Format"): + attrs.append(("Format", self.Format)) + xmlWriter.begintag(tableName, attrs) + xmlWriter.newline() + # sort clips alphabetically to ensure deterministic XML dump + for glyphs, clipBox in sorted( + self.groups().items(), key=lambda item: min(item[0]) + ): + xmlWriter.begintag("Clip") + xmlWriter.newline() + for glyphName in sorted(glyphs): + xmlWriter.simpletag("Glyph", value=glyphName) + xmlWriter.newline() + xmlWriter.begintag("ClipBox", [("Format", clipBox.Format)]) + xmlWriter.newline() + clipBox.toXML2(xmlWriter, font) + xmlWriter.endtag("ClipBox") + xmlWriter.newline() + xmlWriter.endtag("Clip") + xmlWriter.newline() + xmlWriter.endtag(tableName) + xmlWriter.newline() - def toXML(self, xmlWriter, font, attrs=None, name=None): - tableName = name if name else self.__class__.__name__ - if attrs is None: - attrs = [] - if hasattr(self, "Format"): - attrs.append(("Format", self.Format)) - xmlWriter.begintag(tableName, attrs) - xmlWriter.newline() - # sort clips alphabetically to ensure deterministic XML dump - for glyphs, clipBox in sorted( - self.groups().items(), key=lambda item: min(item[0]) - ): - xmlWriter.begintag("Clip") - xmlWriter.newline() - for glyphName in sorted(glyphs): - xmlWriter.simpletag("Glyph", value=glyphName) - xmlWriter.newline() - xmlWriter.begintag("ClipBox", [("Format", clipBox.Format)]) - xmlWriter.newline() - clipBox.toXML2(xmlWriter, font) - xmlWriter.endtag("ClipBox") - xmlWriter.newline() - xmlWriter.endtag("Clip") - xmlWriter.newline() - xmlWriter.endtag(tableName) - xmlWriter.newline() - - def fromXML(self, name, attrs, content, font): - clips = getattr(self, "clips", None) - if clips is None: - self.clips = clips = {} - assert name == "Clip" - glyphs = [] - clipBox = None - for elem in content: - if not isinstance(elem, tuple): - continue - name, attrs, content = elem - if name == "Glyph": - glyphs.append(attrs["value"]) - elif name == "ClipBox": - clipBox = ClipBox() - clipBox.Format = safeEval(attrs["Format"]) - for elem in content: - if not isinstance(elem, tuple): - continue - name, attrs, content = elem - clipBox.fromXML(name, attrs, content, font) - if clipBox: - for glyphName in glyphs: - clips[glyphName] = clipBox + def fromXML(self, name, attrs, content, font): + clips = getattr(self, "clips", None) + if clips is None: + self.clips = clips = {} + assert name == "Clip" + glyphs = [] + clipBox = None + for elem in content: + if not isinstance(elem, tuple): + continue + name, attrs, content = elem + if name == "Glyph": + glyphs.append(attrs["value"]) + elif name == "ClipBox": + clipBox = ClipBox() + clipBox.Format = safeEval(attrs["Format"]) + for elem in content: + if not isinstance(elem, tuple): + continue + name, attrs, content = elem + clipBox.fromXML(name, attrs, content, font) + if clipBox: + for glyphName in glyphs: + clips[glyphName] = clipBox class ExtendMode(IntEnum): - PAD = 0 - REPEAT = 1 - REFLECT = 2 + PAD = 0 + REPEAT = 1 + REFLECT = 2 # Porter-Duff modes for COLRv1 PaintComposite: # https://github.com/googlefonts/colr-gradients-spec/tree/off_sub_1#compositemode-enumeration class CompositeMode(IntEnum): - CLEAR = 0 - SRC = 1 - DEST = 2 - SRC_OVER = 3 - DEST_OVER = 4 - SRC_IN = 5 - DEST_IN = 6 - SRC_OUT = 7 - DEST_OUT = 8 - SRC_ATOP = 9 - DEST_ATOP = 10 - XOR = 11 - PLUS = 12 - SCREEN = 13 - OVERLAY = 14 - DARKEN = 15 - LIGHTEN = 16 - COLOR_DODGE = 17 - COLOR_BURN = 18 - HARD_LIGHT = 19 - SOFT_LIGHT = 20 - DIFFERENCE = 21 - EXCLUSION = 22 - MULTIPLY = 23 - HSL_HUE = 24 - HSL_SATURATION = 25 - HSL_COLOR = 26 - HSL_LUMINOSITY = 27 + CLEAR = 0 + SRC = 1 + DEST = 2 + SRC_OVER = 3 + DEST_OVER = 4 + SRC_IN = 5 + DEST_IN = 6 + SRC_OUT = 7 + DEST_OUT = 8 + SRC_ATOP = 9 + DEST_ATOP = 10 + XOR = 11 + PLUS = 12 + SCREEN = 13 + OVERLAY = 14 + DARKEN = 15 + LIGHTEN = 16 + COLOR_DODGE = 17 + COLOR_BURN = 18 + HARD_LIGHT = 19 + SOFT_LIGHT = 20 + DIFFERENCE = 21 + EXCLUSION = 22 + MULTIPLY = 23 + HSL_HUE = 24 + HSL_SATURATION = 25 + HSL_COLOR = 26 + HSL_LUMINOSITY = 27 class PaintFormat(IntEnum): - PaintColrLayers = 1 - PaintSolid = 2 - PaintVarSolid = 3, - PaintLinearGradient = 4 - PaintVarLinearGradient = 5 - PaintRadialGradient = 6 - PaintVarRadialGradient = 7 - PaintSweepGradient = 8 - PaintVarSweepGradient = 9 - PaintGlyph = 10 - PaintColrGlyph = 11 - PaintTransform = 12 - PaintVarTransform = 13 - PaintTranslate = 14 - PaintVarTranslate = 15 - PaintScale = 16 - PaintVarScale = 17 - PaintScaleAroundCenter = 18 - PaintVarScaleAroundCenter = 19 - PaintScaleUniform = 20 - PaintVarScaleUniform = 21 - PaintScaleUniformAroundCenter = 22 - PaintVarScaleUniformAroundCenter = 23 - PaintRotate = 24 - PaintVarRotate = 25 - PaintRotateAroundCenter = 26 - PaintVarRotateAroundCenter = 27 - PaintSkew = 28 - PaintVarSkew = 29 - PaintSkewAroundCenter = 30 - PaintVarSkewAroundCenter = 31 - PaintComposite = 32 + PaintColrLayers = 1 + PaintSolid = 2 + PaintVarSolid = (3,) + PaintLinearGradient = 4 + PaintVarLinearGradient = 5 + PaintRadialGradient = 6 + PaintVarRadialGradient = 7 + PaintSweepGradient = 8 + PaintVarSweepGradient = 9 + PaintGlyph = 10 + PaintColrGlyph = 11 + PaintTransform = 12 + PaintVarTransform = 13 + PaintTranslate = 14 + PaintVarTranslate = 15 + PaintScale = 16 + PaintVarScale = 17 + PaintScaleAroundCenter = 18 + PaintVarScaleAroundCenter = 19 + PaintScaleUniform = 20 + PaintVarScaleUniform = 21 + PaintScaleUniformAroundCenter = 22 + PaintVarScaleUniformAroundCenter = 23 + PaintRotate = 24 + PaintVarRotate = 25 + PaintRotateAroundCenter = 26 + PaintVarRotateAroundCenter = 27 + PaintSkew = 28 + PaintVarSkew = 29 + PaintSkewAroundCenter = 30 + PaintVarSkewAroundCenter = 31 + PaintComposite = 32 - def is_variable(self): - return self.name.startswith("PaintVar") + def is_variable(self): + return self.name.startswith("PaintVar") - def as_variable(self): - if self.is_variable(): - return self - try: - return PaintFormat.__members__[f"PaintVar{self.name[5:]}"] - except KeyError: - return None + def as_variable(self): + if self.is_variable(): + return self + try: + return PaintFormat.__members__[f"PaintVar{self.name[5:]}"] + except KeyError: + return None class Paint(getFormatSwitchingBaseTableClass("uint8")): - formatEnum = PaintFormat + formatEnum = PaintFormat - def getFormatName(self): - try: - return self.formatEnum(self.Format).name - except ValueError: - raise NotImplementedError(f"Unknown Paint format: {self.Format}") + def getFormatName(self): + try: + return self.formatEnum(self.Format).name + except ValueError: + raise NotImplementedError(f"Unknown Paint format: {self.Format}") - def toXML(self, xmlWriter, font, attrs=None, name=None): - tableName = name if name else self.__class__.__name__ - if attrs is None: - attrs = [] - attrs.append(("Format", self.Format)) - xmlWriter.begintag(tableName, attrs) - xmlWriter.comment(self.getFormatName()) - xmlWriter.newline() - self.toXML2(xmlWriter, font) - xmlWriter.endtag(tableName) - xmlWriter.newline() + def toXML(self, xmlWriter, font, attrs=None, name=None): + tableName = name if name else self.__class__.__name__ + if attrs is None: + attrs = [] + attrs.append(("Format", self.Format)) + xmlWriter.begintag(tableName, attrs) + xmlWriter.comment(self.getFormatName()) + xmlWriter.newline() + self.toXML2(xmlWriter, font) + xmlWriter.endtag(tableName) + xmlWriter.newline() - def getChildren(self, colr): - if self.Format == PaintFormat.PaintColrLayers: - # https://github.com/fonttools/fonttools/issues/2438: don't die when no LayerList exists - layers = [] - if colr.LayerList is not None: - layers = colr.LayerList.Paint - return layers[ - self.FirstLayerIndex : self.FirstLayerIndex + self.NumLayers - ] + def getChildren(self, colr): + if self.Format == PaintFormat.PaintColrLayers: + # https://github.com/fonttools/fonttools/issues/2438: don't die when no LayerList exists + layers = [] + if colr.LayerList is not None: + layers = colr.LayerList.Paint + return layers[self.FirstLayerIndex : self.FirstLayerIndex + self.NumLayers] - if self.Format == PaintFormat.PaintColrGlyph: - for record in colr.BaseGlyphList.BaseGlyphPaintRecord: - if record.BaseGlyph == self.Glyph: - return [record.Paint] - else: - raise KeyError(f"{self.Glyph!r} not in colr.BaseGlyphList") + if self.Format == PaintFormat.PaintColrGlyph: + for record in colr.BaseGlyphList.BaseGlyphPaintRecord: + if record.BaseGlyph == self.Glyph: + return [record.Paint] + else: + raise KeyError(f"{self.Glyph!r} not in colr.BaseGlyphList") - children = [] - for conv in self.getConverters(): - if conv.tableClass is not None and issubclass(conv.tableClass, type(self)): - children.append(getattr(self, conv.name)) + children = [] + for conv in self.getConverters(): + if conv.tableClass is not None and issubclass(conv.tableClass, type(self)): + children.append(getattr(self, conv.name)) - return children + return children - def traverse(self, colr: COLR, callback): - """Depth-first traversal of graph rooted at self, callback on each node.""" - if not callable(callback): - raise TypeError("callback must be callable") - stack = [self] - visited = set() - while stack: - current = stack.pop() - if id(current) in visited: - continue - callback(current) - visited.add(id(current)) - stack.extend(reversed(current.getChildren(colr))) + def traverse(self, colr: COLR, callback): + """Depth-first traversal of graph rooted at self, callback on each node.""" + if not callable(callback): + raise TypeError("callback must be callable") + stack = [self] + visited = set() + while stack: + current = stack.pop() + if id(current) in visited: + continue + callback(current) + visited.add(id(current)) + stack.extend(reversed(current.getChildren(colr))) # For each subtable format there is a class. However, we don't really distinguish @@ -1596,30 +1602,82 @@ class Paint(getFormatSwitchingBaseTableClass("uint8")): # subclass for each alternate field name. # _equivalents = { - 'MarkArray': ("Mark1Array",), - 'LangSys': ('DefaultLangSys',), - 'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage', - 'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage', - 'LookAheadCoverage', 'VertGlyphCoverage', 'HorizGlyphCoverage', - 'TopAccentCoverage', 'ExtendedShapeCoverage', 'MathKernCoverage'), - 'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef', - 'LookAheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'), - 'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor', - 'Mark2Anchor', 'MarkAnchor'), - 'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice', - 'XDeviceTable', 'YDeviceTable', 'DeviceTable'), - 'Axis': ('HorizAxis', 'VertAxis',), - 'MinMax': ('DefaultMinMax',), - 'BaseCoord': ('MinCoord', 'MaxCoord',), - 'JstfLangSys': ('DefJstfLangSys',), - 'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB', - 'ExtensionDisableGSUB',), - 'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS', - 'ExtensionDisableGPOS',), - 'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',), - 'MathKern': ('TopRightMathKern', 'TopLeftMathKern', 'BottomRightMathKern', - 'BottomLeftMathKern'), - 'MathGlyphConstruction': ('VertGlyphConstruction', 'HorizGlyphConstruction'), + "MarkArray": ("Mark1Array",), + "LangSys": ("DefaultLangSys",), + "Coverage": ( + "MarkCoverage", + "BaseCoverage", + "LigatureCoverage", + "Mark1Coverage", + "Mark2Coverage", + "BacktrackCoverage", + "InputCoverage", + "LookAheadCoverage", + "VertGlyphCoverage", + "HorizGlyphCoverage", + "TopAccentCoverage", + "ExtendedShapeCoverage", + "MathKernCoverage", + ), + "ClassDef": ( + "ClassDef1", + "ClassDef2", + "BacktrackClassDef", + "InputClassDef", + "LookAheadClassDef", + "GlyphClassDef", + "MarkAttachClassDef", + ), + "Anchor": ( + "EntryAnchor", + "ExitAnchor", + "BaseAnchor", + "LigatureAnchor", + "Mark2Anchor", + "MarkAnchor", + ), + "Device": ( + "XPlaDevice", + "YPlaDevice", + "XAdvDevice", + "YAdvDevice", + "XDeviceTable", + "YDeviceTable", + "DeviceTable", + ), + "Axis": ( + "HorizAxis", + "VertAxis", + ), + "MinMax": ("DefaultMinMax",), + "BaseCoord": ( + "MinCoord", + "MaxCoord", + ), + "JstfLangSys": ("DefJstfLangSys",), + "JstfGSUBModList": ( + "ShrinkageEnableGSUB", + "ShrinkageDisableGSUB", + "ExtensionEnableGSUB", + "ExtensionDisableGSUB", + ), + "JstfGPOSModList": ( + "ShrinkageEnableGPOS", + "ShrinkageDisableGPOS", + "ExtensionEnableGPOS", + "ExtensionDisableGPOS", + ), + "JstfMax": ( + "ShrinkageJstfMax", + "ExtensionJstfMax", + ), + "MathKern": ( + "TopRightMathKern", + "TopLeftMathKern", + "BottomRightMathKern", + "BottomLeftMathKern", + ), + "MathGlyphConstruction": ("VertGlyphConstruction", "HorizGlyphConstruction"), } # @@ -1627,468 +1685,479 @@ _equivalents = { # XXX This should probably move to otBase.py # + def fixLookupOverFlows(ttf, overflowRecord): - """ Either the offset from the LookupList to a lookup overflowed, or - an offset from a lookup to a subtable overflowed. - The table layout is: - GPSO/GUSB - Script List - Feature List - LookUpList - Lookup[0] and contents - SubTable offset list - SubTable[0] and contents - ... - SubTable[n] and contents - ... - Lookup[n] and contents - SubTable offset list - SubTable[0] and contents - ... - SubTable[n] and contents - If the offset to a lookup overflowed (SubTableIndex is None) - we must promote the *previous* lookup to an Extension type. - If the offset from a lookup to subtable overflowed, then we must promote it - to an Extension Lookup type. - """ - ok = 0 - lookupIndex = overflowRecord.LookupListIndex - if (overflowRecord.SubTableIndex is None): - lookupIndex = lookupIndex - 1 - if lookupIndex < 0: - return ok - if overflowRecord.tableType == 'GSUB': - extType = 7 - elif overflowRecord.tableType == 'GPOS': - extType = 9 + """Either the offset from the LookupList to a lookup overflowed, or + an offset from a lookup to a subtable overflowed. + The table layout is: + GPSO/GUSB + Script List + Feature List + LookUpList + Lookup[0] and contents + SubTable offset list + SubTable[0] and contents + ... + SubTable[n] and contents + ... + Lookup[n] and contents + SubTable offset list + SubTable[0] and contents + ... + SubTable[n] and contents + If the offset to a lookup overflowed (SubTableIndex is None) + we must promote the *previous* lookup to an Extension type. + If the offset from a lookup to subtable overflowed, then we must promote it + to an Extension Lookup type. + """ + ok = 0 + lookupIndex = overflowRecord.LookupListIndex + if overflowRecord.SubTableIndex is None: + lookupIndex = lookupIndex - 1 + if lookupIndex < 0: + return ok + if overflowRecord.tableType == "GSUB": + extType = 7 + elif overflowRecord.tableType == "GPOS": + extType = 9 - lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup - lookup = lookups[lookupIndex] - # If the previous lookup is an extType, look further back. Very unlikely, but possible. - while lookup.SubTable[0].__class__.LookupType == extType: - lookupIndex = lookupIndex -1 - if lookupIndex < 0: - return ok - lookup = lookups[lookupIndex] + lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup + lookup = lookups[lookupIndex] + # If the previous lookup is an extType, look further back. Very unlikely, but possible. + while lookup.SubTable[0].__class__.LookupType == extType: + lookupIndex = lookupIndex - 1 + if lookupIndex < 0: + return ok + lookup = lookups[lookupIndex] + + for lookupIndex in range(lookupIndex, len(lookups)): + lookup = lookups[lookupIndex] + if lookup.LookupType != extType: + lookup.LookupType = extType + for si in range(len(lookup.SubTable)): + subTable = lookup.SubTable[si] + extSubTableClass = lookupTypes[overflowRecord.tableType][extType] + extSubTable = extSubTableClass() + extSubTable.Format = 1 + extSubTable.ExtSubTable = subTable + lookup.SubTable[si] = extSubTable + ok = 1 + return ok - for lookupIndex in range(lookupIndex, len(lookups)): - lookup = lookups[lookupIndex] - if lookup.LookupType != extType: - lookup.LookupType = extType - for si in range(len(lookup.SubTable)): - subTable = lookup.SubTable[si] - extSubTableClass = lookupTypes[overflowRecord.tableType][extType] - extSubTable = extSubTableClass() - extSubTable.Format = 1 - extSubTable.ExtSubTable = subTable - lookup.SubTable[si] = extSubTable - ok = 1 - return ok def splitMultipleSubst(oldSubTable, newSubTable, overflowRecord): - ok = 1 - oldMapping = sorted(oldSubTable.mapping.items()) - oldLen = len(oldMapping) + ok = 1 + oldMapping = sorted(oldSubTable.mapping.items()) + oldLen = len(oldMapping) - if overflowRecord.itemName in ['Coverage', 'RangeRecord']: - # Coverage table is written last. Overflow is to or within the - # the coverage table. We will just cut the subtable in half. - newLen = oldLen // 2 + if overflowRecord.itemName in ["Coverage", "RangeRecord"]: + # Coverage table is written last. Overflow is to or within the + # the coverage table. We will just cut the subtable in half. + newLen = oldLen // 2 - elif overflowRecord.itemName == 'Sequence': - # We just need to back up by two items from the overflowed - # Sequence index to make sure the offset to the Coverage table - # doesn't overflow. - newLen = overflowRecord.itemIndex - 1 + elif overflowRecord.itemName == "Sequence": + # We just need to back up by two items from the overflowed + # Sequence index to make sure the offset to the Coverage table + # doesn't overflow. + newLen = overflowRecord.itemIndex - 1 - newSubTable.mapping = {} - for i in range(newLen, oldLen): - item = oldMapping[i] - key = item[0] - newSubTable.mapping[key] = item[1] - del oldSubTable.mapping[key] + newSubTable.mapping = {} + for i in range(newLen, oldLen): + item = oldMapping[i] + key = item[0] + newSubTable.mapping[key] = item[1] + del oldSubTable.mapping[key] + + return ok - return ok def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord): - ok = 1 - if hasattr(oldSubTable, 'sortCoverageLast'): - newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast + ok = 1 + if hasattr(oldSubTable, "sortCoverageLast"): + newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast - oldAlts = sorted(oldSubTable.alternates.items()) - oldLen = len(oldAlts) + oldAlts = sorted(oldSubTable.alternates.items()) + oldLen = len(oldAlts) - if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']: - # Coverage table is written last. overflow is to or within the - # the coverage table. We will just cut the subtable in half. - newLen = oldLen//2 + if overflowRecord.itemName in ["Coverage", "RangeRecord"]: + # Coverage table is written last. overflow is to or within the + # the coverage table. We will just cut the subtable in half. + newLen = oldLen // 2 - elif overflowRecord.itemName == 'AlternateSet': - # We just need to back up by two items - # from the overflowed AlternateSet index to make sure the offset - # to the Coverage table doesn't overflow. - newLen = overflowRecord.itemIndex - 1 + elif overflowRecord.itemName == "AlternateSet": + # We just need to back up by two items + # from the overflowed AlternateSet index to make sure the offset + # to the Coverage table doesn't overflow. + newLen = overflowRecord.itemIndex - 1 - newSubTable.alternates = {} - for i in range(newLen, oldLen): - item = oldAlts[i] - key = item[0] - newSubTable.alternates[key] = item[1] - del oldSubTable.alternates[key] + newSubTable.alternates = {} + for i in range(newLen, oldLen): + item = oldAlts[i] + key = item[0] + newSubTable.alternates[key] = item[1] + del oldSubTable.alternates[key] - return ok + return ok def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord): - ok = 1 - oldLigs = sorted(oldSubTable.ligatures.items()) - oldLen = len(oldLigs) + ok = 1 + oldLigs = sorted(oldSubTable.ligatures.items()) + oldLen = len(oldLigs) - if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']: - # Coverage table is written last. overflow is to or within the - # the coverage table. We will just cut the subtable in half. - newLen = oldLen//2 + if overflowRecord.itemName in ["Coverage", "RangeRecord"]: + # Coverage table is written last. overflow is to or within the + # the coverage table. We will just cut the subtable in half. + newLen = oldLen // 2 - elif overflowRecord.itemName == 'LigatureSet': - # We just need to back up by two items - # from the overflowed AlternateSet index to make sure the offset - # to the Coverage table doesn't overflow. - newLen = overflowRecord.itemIndex - 1 + elif overflowRecord.itemName == "LigatureSet": + # We just need to back up by two items + # from the overflowed AlternateSet index to make sure the offset + # to the Coverage table doesn't overflow. + newLen = overflowRecord.itemIndex - 1 - newSubTable.ligatures = {} - for i in range(newLen, oldLen): - item = oldLigs[i] - key = item[0] - newSubTable.ligatures[key] = item[1] - del oldSubTable.ligatures[key] + newSubTable.ligatures = {} + for i in range(newLen, oldLen): + item = oldLigs[i] + key = item[0] + newSubTable.ligatures[key] = item[1] + del oldSubTable.ligatures[key] - return ok + return ok def splitPairPos(oldSubTable, newSubTable, overflowRecord): - st = oldSubTable - ok = False - newSubTable.Format = oldSubTable.Format - if oldSubTable.Format == 1 and len(oldSubTable.PairSet) > 1: - for name in 'ValueFormat1', 'ValueFormat2': - setattr(newSubTable, name, getattr(oldSubTable, name)) + st = oldSubTable + ok = False + newSubTable.Format = oldSubTable.Format + if oldSubTable.Format == 1 and len(oldSubTable.PairSet) > 1: + for name in "ValueFormat1", "ValueFormat2": + setattr(newSubTable, name, getattr(oldSubTable, name)) - # Move top half of coverage to new subtable + # Move top half of coverage to new subtable - newSubTable.Coverage = oldSubTable.Coverage.__class__() + newSubTable.Coverage = oldSubTable.Coverage.__class__() - coverage = oldSubTable.Coverage.glyphs - records = oldSubTable.PairSet + coverage = oldSubTable.Coverage.glyphs + records = oldSubTable.PairSet - oldCount = len(oldSubTable.PairSet) // 2 + oldCount = len(oldSubTable.PairSet) // 2 - oldSubTable.Coverage.glyphs = coverage[:oldCount] - oldSubTable.PairSet = records[:oldCount] + oldSubTable.Coverage.glyphs = coverage[:oldCount] + oldSubTable.PairSet = records[:oldCount] - newSubTable.Coverage.glyphs = coverage[oldCount:] - newSubTable.PairSet = records[oldCount:] + newSubTable.Coverage.glyphs = coverage[oldCount:] + newSubTable.PairSet = records[oldCount:] - oldSubTable.PairSetCount = len(oldSubTable.PairSet) - newSubTable.PairSetCount = len(newSubTable.PairSet) + oldSubTable.PairSetCount = len(oldSubTable.PairSet) + newSubTable.PairSetCount = len(newSubTable.PairSet) - ok = True + ok = True - elif oldSubTable.Format == 2 and len(oldSubTable.Class1Record) > 1: - if not hasattr(oldSubTable, 'Class2Count'): - oldSubTable.Class2Count = len(oldSubTable.Class1Record[0].Class2Record) - for name in 'Class2Count', 'ClassDef2', 'ValueFormat1', 'ValueFormat2': - setattr(newSubTable, name, getattr(oldSubTable, name)) + elif oldSubTable.Format == 2 and len(oldSubTable.Class1Record) > 1: + if not hasattr(oldSubTable, "Class2Count"): + oldSubTable.Class2Count = len(oldSubTable.Class1Record[0].Class2Record) + for name in "Class2Count", "ClassDef2", "ValueFormat1", "ValueFormat2": + setattr(newSubTable, name, getattr(oldSubTable, name)) - # The two subtables will still have the same ClassDef2 and the table - # sharing will still cause the sharing to overflow. As such, disable - # sharing on the one that is serialized second (that's oldSubTable). - oldSubTable.DontShare = True + # The two subtables will still have the same ClassDef2 and the table + # sharing will still cause the sharing to overflow. As such, disable + # sharing on the one that is serialized second (that's oldSubTable). + oldSubTable.DontShare = True - # Move top half of class numbers to new subtable + # Move top half of class numbers to new subtable - newSubTable.Coverage = oldSubTable.Coverage.__class__() - newSubTable.ClassDef1 = oldSubTable.ClassDef1.__class__() + newSubTable.Coverage = oldSubTable.Coverage.__class__() + newSubTable.ClassDef1 = oldSubTable.ClassDef1.__class__() - coverage = oldSubTable.Coverage.glyphs - classDefs = oldSubTable.ClassDef1.classDefs - records = oldSubTable.Class1Record + coverage = oldSubTable.Coverage.glyphs + classDefs = oldSubTable.ClassDef1.classDefs + records = oldSubTable.Class1Record - oldCount = len(oldSubTable.Class1Record) // 2 - newGlyphs = set(k for k,v in classDefs.items() if v >= oldCount) + oldCount = len(oldSubTable.Class1Record) // 2 + newGlyphs = set(k for k, v in classDefs.items() if v >= oldCount) - oldSubTable.Coverage.glyphs = [g for g in coverage if g not in newGlyphs] - oldSubTable.ClassDef1.classDefs = {k:v for k,v in classDefs.items() if v < oldCount} - oldSubTable.Class1Record = records[:oldCount] + oldSubTable.Coverage.glyphs = [g for g in coverage if g not in newGlyphs] + oldSubTable.ClassDef1.classDefs = { + k: v for k, v in classDefs.items() if v < oldCount + } + oldSubTable.Class1Record = records[:oldCount] - newSubTable.Coverage.glyphs = [g for g in coverage if g in newGlyphs] - newSubTable.ClassDef1.classDefs = {k:(v-oldCount) for k,v in classDefs.items() if v > oldCount} - newSubTable.Class1Record = records[oldCount:] + newSubTable.Coverage.glyphs = [g for g in coverage if g in newGlyphs] + newSubTable.ClassDef1.classDefs = { + k: (v - oldCount) for k, v in classDefs.items() if v > oldCount + } + newSubTable.Class1Record = records[oldCount:] - oldSubTable.Class1Count = len(oldSubTable.Class1Record) - newSubTable.Class1Count = len(newSubTable.Class1Record) + oldSubTable.Class1Count = len(oldSubTable.Class1Record) + newSubTable.Class1Count = len(newSubTable.Class1Record) - ok = True + ok = True - return ok + return ok def splitMarkBasePos(oldSubTable, newSubTable, overflowRecord): - # split half of the mark classes to the new subtable - classCount = oldSubTable.ClassCount - if classCount < 2: - # oh well, not much left to split... - return False + # split half of the mark classes to the new subtable + classCount = oldSubTable.ClassCount + if classCount < 2: + # oh well, not much left to split... + return False - oldClassCount = classCount // 2 - newClassCount = classCount - oldClassCount + oldClassCount = classCount // 2 + newClassCount = classCount - oldClassCount - oldMarkCoverage, oldMarkRecords = [], [] - newMarkCoverage, newMarkRecords = [], [] - for glyphName, markRecord in zip( - oldSubTable.MarkCoverage.glyphs, - oldSubTable.MarkArray.MarkRecord - ): - if markRecord.Class < oldClassCount: - oldMarkCoverage.append(glyphName) - oldMarkRecords.append(markRecord) - else: - markRecord.Class -= oldClassCount - newMarkCoverage.append(glyphName) - newMarkRecords.append(markRecord) + oldMarkCoverage, oldMarkRecords = [], [] + newMarkCoverage, newMarkRecords = [], [] + for glyphName, markRecord in zip( + oldSubTable.MarkCoverage.glyphs, oldSubTable.MarkArray.MarkRecord + ): + if markRecord.Class < oldClassCount: + oldMarkCoverage.append(glyphName) + oldMarkRecords.append(markRecord) + else: + markRecord.Class -= oldClassCount + newMarkCoverage.append(glyphName) + newMarkRecords.append(markRecord) - oldBaseRecords, newBaseRecords = [], [] - for rec in oldSubTable.BaseArray.BaseRecord: - oldBaseRecord, newBaseRecord = rec.__class__(), rec.__class__() - oldBaseRecord.BaseAnchor = rec.BaseAnchor[:oldClassCount] - newBaseRecord.BaseAnchor = rec.BaseAnchor[oldClassCount:] - oldBaseRecords.append(oldBaseRecord) - newBaseRecords.append(newBaseRecord) + oldBaseRecords, newBaseRecords = [], [] + for rec in oldSubTable.BaseArray.BaseRecord: + oldBaseRecord, newBaseRecord = rec.__class__(), rec.__class__() + oldBaseRecord.BaseAnchor = rec.BaseAnchor[:oldClassCount] + newBaseRecord.BaseAnchor = rec.BaseAnchor[oldClassCount:] + oldBaseRecords.append(oldBaseRecord) + newBaseRecords.append(newBaseRecord) - newSubTable.Format = oldSubTable.Format + newSubTable.Format = oldSubTable.Format - oldSubTable.MarkCoverage.glyphs = oldMarkCoverage - newSubTable.MarkCoverage = oldSubTable.MarkCoverage.__class__() - newSubTable.MarkCoverage.glyphs = newMarkCoverage + oldSubTable.MarkCoverage.glyphs = oldMarkCoverage + newSubTable.MarkCoverage = oldSubTable.MarkCoverage.__class__() + newSubTable.MarkCoverage.glyphs = newMarkCoverage - # share the same BaseCoverage in both halves - newSubTable.BaseCoverage = oldSubTable.BaseCoverage + # share the same BaseCoverage in both halves + newSubTable.BaseCoverage = oldSubTable.BaseCoverage - oldSubTable.ClassCount = oldClassCount - newSubTable.ClassCount = newClassCount + oldSubTable.ClassCount = oldClassCount + newSubTable.ClassCount = newClassCount - oldSubTable.MarkArray.MarkRecord = oldMarkRecords - newSubTable.MarkArray = oldSubTable.MarkArray.__class__() - newSubTable.MarkArray.MarkRecord = newMarkRecords + oldSubTable.MarkArray.MarkRecord = oldMarkRecords + newSubTable.MarkArray = oldSubTable.MarkArray.__class__() + newSubTable.MarkArray.MarkRecord = newMarkRecords - oldSubTable.MarkArray.MarkCount = len(oldMarkRecords) - newSubTable.MarkArray.MarkCount = len(newMarkRecords) + oldSubTable.MarkArray.MarkCount = len(oldMarkRecords) + newSubTable.MarkArray.MarkCount = len(newMarkRecords) - oldSubTable.BaseArray.BaseRecord = oldBaseRecords - newSubTable.BaseArray = oldSubTable.BaseArray.__class__() - newSubTable.BaseArray.BaseRecord = newBaseRecords + oldSubTable.BaseArray.BaseRecord = oldBaseRecords + newSubTable.BaseArray = oldSubTable.BaseArray.__class__() + newSubTable.BaseArray.BaseRecord = newBaseRecords - oldSubTable.BaseArray.BaseCount = len(oldBaseRecords) - newSubTable.BaseArray.BaseCount = len(newBaseRecords) + oldSubTable.BaseArray.BaseCount = len(oldBaseRecords) + newSubTable.BaseArray.BaseCount = len(newBaseRecords) - return True + return True -splitTable = { 'GSUB': { -# 1: splitSingleSubst, - 2: splitMultipleSubst, - 3: splitAlternateSubst, - 4: splitLigatureSubst, -# 5: splitContextSubst, -# 6: splitChainContextSubst, -# 7: splitExtensionSubst, -# 8: splitReverseChainSingleSubst, - }, - 'GPOS': { -# 1: splitSinglePos, - 2: splitPairPos, -# 3: splitCursivePos, - 4: splitMarkBasePos, -# 5: splitMarkLigPos, -# 6: splitMarkMarkPos, -# 7: splitContextPos, -# 8: splitChainContextPos, -# 9: splitExtensionPos, - } +splitTable = { + "GSUB": { + # 1: splitSingleSubst, + 2: splitMultipleSubst, + 3: splitAlternateSubst, + 4: splitLigatureSubst, + # 5: splitContextSubst, + # 6: splitChainContextSubst, + # 7: splitExtensionSubst, + # 8: splitReverseChainSingleSubst, + }, + "GPOS": { + # 1: splitSinglePos, + 2: splitPairPos, + # 3: splitCursivePos, + 4: splitMarkBasePos, + # 5: splitMarkLigPos, + # 6: splitMarkMarkPos, + # 7: splitContextPos, + # 8: splitChainContextPos, + # 9: splitExtensionPos, + }, +} - } def fixSubTableOverFlows(ttf, overflowRecord): - """ - An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts. - """ - table = ttf[overflowRecord.tableType].table - lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex] - subIndex = overflowRecord.SubTableIndex - subtable = lookup.SubTable[subIndex] + """ + An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts. + """ + table = ttf[overflowRecord.tableType].table + lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex] + subIndex = overflowRecord.SubTableIndex + subtable = lookup.SubTable[subIndex] - # First, try not sharing anything for this subtable... - if not hasattr(subtable, "DontShare"): - subtable.DontShare = True - return True + # First, try not sharing anything for this subtable... + if not hasattr(subtable, "DontShare"): + subtable.DontShare = True + return True - if hasattr(subtable, 'ExtSubTable'): - # We split the subtable of the Extension table, and add a new Extension table - # to contain the new subtable. + if hasattr(subtable, "ExtSubTable"): + # We split the subtable of the Extension table, and add a new Extension table + # to contain the new subtable. - subTableType = subtable.ExtSubTable.__class__.LookupType - extSubTable = subtable - subtable = extSubTable.ExtSubTable - newExtSubTableClass = lookupTypes[overflowRecord.tableType][extSubTable.__class__.LookupType] - newExtSubTable = newExtSubTableClass() - newExtSubTable.Format = extSubTable.Format - toInsert = newExtSubTable + subTableType = subtable.ExtSubTable.__class__.LookupType + extSubTable = subtable + subtable = extSubTable.ExtSubTable + newExtSubTableClass = lookupTypes[overflowRecord.tableType][ + extSubTable.__class__.LookupType + ] + newExtSubTable = newExtSubTableClass() + newExtSubTable.Format = extSubTable.Format + toInsert = newExtSubTable - newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] - newSubTable = newSubTableClass() - newExtSubTable.ExtSubTable = newSubTable - else: - subTableType = subtable.__class__.LookupType - newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] - newSubTable = newSubTableClass() - toInsert = newSubTable + newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] + newSubTable = newSubTableClass() + newExtSubTable.ExtSubTable = newSubTable + else: + subTableType = subtable.__class__.LookupType + newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType] + newSubTable = newSubTableClass() + toInsert = newSubTable - if hasattr(lookup, 'SubTableCount'): # may not be defined yet. - lookup.SubTableCount = lookup.SubTableCount + 1 + if hasattr(lookup, "SubTableCount"): # may not be defined yet. + lookup.SubTableCount = lookup.SubTableCount + 1 - try: - splitFunc = splitTable[overflowRecord.tableType][subTableType] - except KeyError: - log.error( - "Don't know how to split %s lookup type %s", - overflowRecord.tableType, - subTableType, - ) - return False + try: + splitFunc = splitTable[overflowRecord.tableType][subTableType] + except KeyError: + log.error( + "Don't know how to split %s lookup type %s", + overflowRecord.tableType, + subTableType, + ) + return False + + ok = splitFunc(subtable, newSubTable, overflowRecord) + if ok: + lookup.SubTable.insert(subIndex + 1, toInsert) + return ok - ok = splitFunc(subtable, newSubTable, overflowRecord) - if ok: - lookup.SubTable.insert(subIndex + 1, toInsert) - return ok # End of OverFlow logic def _buildClasses(): - import re - from .otData import otData + import re + from .otData import otData - formatPat = re.compile(r"([A-Za-z0-9]+)Format(\d+)$") - namespace = globals() + formatPat = re.compile(r"([A-Za-z0-9]+)Format(\d+)$") + namespace = globals() - # populate module with classes - for name, table in otData: - baseClass = BaseTable - m = formatPat.match(name) - if m: - # XxxFormatN subtable, we only add the "base" table - name = m.group(1) - # the first row of a format-switching otData table describes the Format; - # the first column defines the type of the Format field. - # Currently this can be either 'uint16' or 'uint8'. - formatType = table[0][0] - baseClass = getFormatSwitchingBaseTableClass(formatType) - if name not in namespace: - # the class doesn't exist yet, so the base implementation is used. - cls = type(name, (baseClass,), {}) - if name in ('GSUB', 'GPOS'): - cls.DontShare = True - namespace[name] = cls + # populate module with classes + for name, table in otData: + baseClass = BaseTable + m = formatPat.match(name) + if m: + # XxxFormatN subtable, we only add the "base" table + name = m.group(1) + # the first row of a format-switching otData table describes the Format; + # the first column defines the type of the Format field. + # Currently this can be either 'uint16' or 'uint8'. + formatType = table[0][0] + baseClass = getFormatSwitchingBaseTableClass(formatType) + if name not in namespace: + # the class doesn't exist yet, so the base implementation is used. + cls = type(name, (baseClass,), {}) + if name in ("GSUB", "GPOS"): + cls.DontShare = True + namespace[name] = cls - # link Var{Table} <-> {Table} (e.g. ColorStop <-> VarColorStop, etc.) - for name, _ in otData: - if name.startswith("Var") and len(name) > 3 and name[3:] in namespace: - varType = namespace[name] - noVarType = namespace[name[3:]] - varType.NoVarType = noVarType - noVarType.VarType = varType + # link Var{Table} <-> {Table} (e.g. ColorStop <-> VarColorStop, etc.) + for name, _ in otData: + if name.startswith("Var") and len(name) > 3 and name[3:] in namespace: + varType = namespace[name] + noVarType = namespace[name[3:]] + varType.NoVarType = noVarType + noVarType.VarType = varType - for base, alts in _equivalents.items(): - base = namespace[base] - for alt in alts: - namespace[alt] = base + for base, alts in _equivalents.items(): + base = namespace[base] + for alt in alts: + namespace[alt] = base - global lookupTypes - lookupTypes = { - 'GSUB': { - 1: SingleSubst, - 2: MultipleSubst, - 3: AlternateSubst, - 4: LigatureSubst, - 5: ContextSubst, - 6: ChainContextSubst, - 7: ExtensionSubst, - 8: ReverseChainSingleSubst, - }, - 'GPOS': { - 1: SinglePos, - 2: PairPos, - 3: CursivePos, - 4: MarkBasePos, - 5: MarkLigPos, - 6: MarkMarkPos, - 7: ContextPos, - 8: ChainContextPos, - 9: ExtensionPos, - }, - 'mort': { - 4: NoncontextualMorph, - }, - 'morx': { - 0: RearrangementMorph, - 1: ContextualMorph, - 2: LigatureMorph, - # 3: Reserved, - 4: NoncontextualMorph, - 5: InsertionMorph, - }, - } - lookupTypes['JSTF'] = lookupTypes['GPOS'] # JSTF contains GPOS - for lookupEnum in lookupTypes.values(): - for enum, cls in lookupEnum.items(): - cls.LookupType = enum + global lookupTypes + lookupTypes = { + "GSUB": { + 1: SingleSubst, + 2: MultipleSubst, + 3: AlternateSubst, + 4: LigatureSubst, + 5: ContextSubst, + 6: ChainContextSubst, + 7: ExtensionSubst, + 8: ReverseChainSingleSubst, + }, + "GPOS": { + 1: SinglePos, + 2: PairPos, + 3: CursivePos, + 4: MarkBasePos, + 5: MarkLigPos, + 6: MarkMarkPos, + 7: ContextPos, + 8: ChainContextPos, + 9: ExtensionPos, + }, + "mort": { + 4: NoncontextualMorph, + }, + "morx": { + 0: RearrangementMorph, + 1: ContextualMorph, + 2: LigatureMorph, + # 3: Reserved, + 4: NoncontextualMorph, + 5: InsertionMorph, + }, + } + lookupTypes["JSTF"] = lookupTypes["GPOS"] # JSTF contains GPOS + for lookupEnum in lookupTypes.values(): + for enum, cls in lookupEnum.items(): + cls.LookupType = enum - global featureParamTypes - featureParamTypes = { - 'size': FeatureParamsSize, - } - for i in range(1, 20+1): - featureParamTypes['ss%02d' % i] = FeatureParamsStylisticSet - for i in range(1, 99+1): - featureParamTypes['cv%02d' % i] = FeatureParamsCharacterVariants + global featureParamTypes + featureParamTypes = { + "size": FeatureParamsSize, + } + for i in range(1, 20 + 1): + featureParamTypes["ss%02d" % i] = FeatureParamsStylisticSet + for i in range(1, 99 + 1): + featureParamTypes["cv%02d" % i] = FeatureParamsCharacterVariants - # add converters to classes - from .otConverters import buildConverters - for name, table in otData: - m = formatPat.match(name) - if m: - # XxxFormatN subtable, add converter to "base" table - name, format = m.groups() - format = int(format) - cls = namespace[name] - if not hasattr(cls, "converters"): - cls.converters = {} - cls.convertersByName = {} - converters, convertersByName = buildConverters(table[1:], namespace) - cls.converters[format] = converters - cls.convertersByName[format] = convertersByName - # XXX Add staticSize? - else: - cls = namespace[name] - cls.converters, cls.convertersByName = buildConverters(table, namespace) - # XXX Add staticSize? + # add converters to classes + from .otConverters import buildConverters + + for name, table in otData: + m = formatPat.match(name) + if m: + # XxxFormatN subtable, add converter to "base" table + name, format = m.groups() + format = int(format) + cls = namespace[name] + if not hasattr(cls, "converters"): + cls.converters = {} + cls.convertersByName = {} + converters, convertersByName = buildConverters(table[1:], namespace) + cls.converters[format] = converters + cls.convertersByName[format] = convertersByName + # XXX Add staticSize? + else: + cls = namespace[name] + cls.converters, cls.convertersByName = buildConverters(table, namespace) + # XXX Add staticSize? _buildClasses() def _getGlyphsFromCoverageTable(coverage): - if coverage is None: - # empty coverage table - return [] - else: - return coverage.glyphs + if coverage is None: + # empty coverage table + return [] + else: + return coverage.glyphs From 2b5686205ecf183a207f367d9bcd325a059bd7a5 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Tue, 7 Mar 2023 12:10:31 -0700 Subject: [PATCH 05/18] [avar] Support previous API in compile/decompile Using the otData mechanism with handcoded shim. --- Lib/fontTools/ttLib/tables/_a_v_a_r.py | 58 ++++++++++++-------------- Tests/ttLib/tables/_a_v_a_r_test.py | 13 ++---- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py index e3f981400..bf1964134 100644 --- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py +++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py @@ -8,6 +8,7 @@ from fontTools.misc.fixedTools import ( from fontTools.misc.textTools import bytesjoin from fontTools.ttLib import TTLibError from . import DefaultTable +from . import otTables import struct import logging @@ -44,45 +45,41 @@ class table__a_v_a_r(BaseTTXConverter): dependencies = ["fvar"] def __init__(self, tag=None): - DefaultTable.DefaultTable.__init__(self, tag) - # self.segments = {} + super().__init__(tag) + self.segments = {} - """ def compile(self, ttFont): axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] - header = { - "majorVersion": 1, - "minorVersion": 0, - "reserved": 0, - "axisCount": len(axisTags), - } - result = [sstruct.pack(AVAR_HEADER_FORMAT, header)] + if not hasattr(self, "table"): + self.table = otTables.avar() + self.table.Version = 0x00010000 + self.table.Reserved = 0 + self.table.AxisCount = len(axisTags) + self.table.AxisSegmentMap = [] for axis in axisTags: - mappings = sorted(self.segments[axis].items()) - result.append(struct.pack(">H", len(mappings))) - for key, value in mappings: - fixedKey = fl2fi(key, 14) - fixedValue = fl2fi(value, 14) - result.append(struct.pack(">hh", fixedKey, fixedValue)) - return bytesjoin(result) + mappings = self.segments[axis] + segmentMap = otTables.AxisSegmentMap() + segmentMap.PositionMapCount = len(mappings) + segmentMap.AxisValueMap = [] + for key, value in sorted(mappings.items()): + valueMap = otTables.AxisValueMap() + valueMap.FromCoordinate = key + valueMap.ToCoordinate = value + segmentMap.AxisValueMap.append(valueMap) + self.table.AxisSegmentMap.append(segmentMap) + return super().compile(ttFont) def decompile(self, data, ttFont): + super().decompile(data, ttFont) + assert self.table.Version >= 0x00010000 + self.majorVersion = self.table.Version >> 16 axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] - header = {} - headerSize = sstruct.calcsize(AVAR_HEADER_FORMAT) - header = sstruct.unpack(AVAR_HEADER_FORMAT, data[0:headerSize]) - majorVersion = header["majorVersion"] - if majorVersion != 1: - raise TTLibError("unsupported 'avar' version %d" % majorVersion) - pos = headerSize for axis in axisTags: + self.segments[axis] = {} + for axis, segmentMap in zip(axisTags, self.table.AxisSegmentMap): segments = self.segments[axis] = {} - numPairs = struct.unpack(">H", data[pos : pos + 2])[0] - pos = pos + 2 - for _ in range(numPairs): - fromValue, toValue = struct.unpack(">hh", data[pos : pos + 4]) - segments[fi2fl(fromValue, 14)] = fi2fl(toValue, 14) - pos = pos + 4 + for segment in segmentMap.AxisValueMap: + segments[segment.FromCoordinate] = segment.ToCoordinate def toXML(self, writer, ttFont): axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] @@ -112,4 +109,3 @@ class table__a_v_a_r(BaseTTXConverter): "duplicate entry for %s in axis '%s'", fromValue, axis ) segment[fromValue] = toValue - """ diff --git a/Tests/ttLib/tables/_a_v_a_r_test.py b/Tests/ttLib/tables/_a_v_a_r_test.py index f39bfd38f..f2ffbfa72 100644 --- a/Tests/ttLib/tables/_a_v_a_r_test.py +++ b/Tests/ttLib/tables/_a_v_a_r_test.py @@ -1,7 +1,7 @@ from fontTools.misc.testTools import parseXML from fontTools.misc.textTools import deHexStr from fontTools.misc.xmlWriter import XMLWriter -from fontTools.ttLib import TTLibError +from fontTools.ttLib import TTFont, TTLibError from fontTools.ttLib.tables._a_v_a_r import table__a_v_a_r from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis from io import BytesIO @@ -44,13 +44,6 @@ class AxisVariationTableTest(unittest.TestCase): avar.segments, ) - def test_decompile_unsupportedVersion(self): - avar = table__a_v_a_r() - font = self.makeFont(["wdth", "wght"]) - self.assertRaises( - TTLibError, avar.decompile, deHexStr("02 01 03 06 00 00 00 00"), font - ) - def test_toXML(self): avar = table__a_v_a_r() avar.segments["opsz"] = {-1.0: -1.0, 0.0: 0.0, 0.2999878: 0.7999878, 1.0: 1.0} @@ -91,7 +84,9 @@ class AxisVariationTableTest(unittest.TestCase): axis = Axis() axis.axisTag = tag fvar.axes.append(axis) - return {"fvar": fvar} + font = TTFont() + font["fvar"] = fvar + return font @staticmethod def xml_lines(writer): From 8dfad13288d57af296f0169bd0d153ff7c1d6fa9 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 8 Mar 2023 09:38:07 -0700 Subject: [PATCH 06/18] [avar2] Implement toXML --- Lib/fontTools/ttLib/tables/_a_v_a_r.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py index bf1964134..78a799b69 100644 --- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py +++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py @@ -5,7 +5,7 @@ from fontTools.misc.fixedTools import ( floatToFixedToStr as fl2str, strToFixedToFloat as str2fl, ) -from fontTools.misc.textTools import bytesjoin +from fontTools.misc.textTools import bytesjoin, safeEval from fontTools.ttLib import TTLibError from . import DefaultTable from . import otTables @@ -52,7 +52,7 @@ class table__a_v_a_r(BaseTTXConverter): axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] if not hasattr(self, "table"): self.table = otTables.avar() - self.table.Version = 0x00010000 + self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr(self, "minorVersion", 0) self.table.Reserved = 0 self.table.AxisCount = len(axisTags) self.table.AxisSegmentMap = [] @@ -73,6 +73,7 @@ class table__a_v_a_r(BaseTTXConverter): super().decompile(data, ttFont) assert self.table.Version >= 0x00010000 self.majorVersion = self.table.Version >> 16 + self.minorVersion = self.table.Version & 0xFFFF axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] for axis in axisTags: self.segments[axis] = {} @@ -82,6 +83,8 @@ class table__a_v_a_r(BaseTTXConverter): segments[segment.FromCoordinate] = segment.ToCoordinate def toXML(self, writer, ttFont): + writer.simpletag("version", major=self.majorVersion, minor=self.minorVersion) + writer.newline() axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] for axis in axisTags: writer.begintag("segment", axis=axis) @@ -93,9 +96,20 @@ class table__a_v_a_r(BaseTTXConverter): writer.newline() writer.endtag("segment") writer.newline() + if self.majorVersion >= 2: + if self.table.VarIdxMap: + self.table.VarIdxMap.toXML(writer, ttFont) + if self.table.VarStore: + self.table.VarStore.toXML(writer, ttFont) def fromXML(self, name, attrs, content, ttFont): - if name == "segment": + if name == "version": + self.majorVersion = safeEval(attrs["major"]) + self.minorVersion = safeEval(attrs["minor"]) + if not hasattr(self, "table"): + self.table = otTables.avar() + self.table.Version = (self.majorVersion << 16) | self.minorVersion + elif name == "segment": axis = attrs["axis"] segment = self.segments[axis] = {} for element in content: @@ -109,3 +123,5 @@ class table__a_v_a_r(BaseTTXConverter): "duplicate entry for %s in axis '%s'", fromValue, axis ) segment[fromValue] = toValue + #else: + # super().fromXML(name, attrs, content, ttFont) From c8d32f2bb121de608ac86f44fa682bf8c773fbe8 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 8 Mar 2023 10:56:07 -0700 Subject: [PATCH 07/18] [avar2] Use DeltaSetIndexMap instead of VarIdxMap --- Lib/fontTools/ttLib/tables/otData.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py index f34619fa0..635fec668 100755 --- a/Lib/fontTools/ttLib/tables/otData.py +++ b/Lib/fontTools/ttLib/tables/otData.py @@ -6223,7 +6223,7 @@ otData = [ 0, 'The segment maps array — one segment map for each axis, in the order of axes specified in the "fvar" table', ), - ("LOffset", "VarIdxMap", None, "Version >= 0x00020000", ""), + ("LOffsetTo(DeltaSetIndexMap)", "VarIdxMap", None, "Version >= 0x00020000", ""), ("LOffset", "VarStore", None, "Version >= 0x00020000", ""), ], ), From 564dddced29dc4eda241dff200150a3438c5c3f7 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 8 Mar 2023 10:56:45 -0700 Subject: [PATCH 08/18] [avar2] Fix fromXML crash --- Lib/fontTools/ttLib/tables/_a_v_a_r.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py index 78a799b69..7a0c78e49 100644 --- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py +++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py @@ -52,7 +52,9 @@ class table__a_v_a_r(BaseTTXConverter): axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] if not hasattr(self, "table"): self.table = otTables.avar() - self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr(self, "minorVersion", 0) + self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr( + self, "minorVersion", 0 + ) self.table.Reserved = 0 self.table.AxisCount = len(axisTags) self.table.AxisSegmentMap = [] @@ -108,7 +110,9 @@ class table__a_v_a_r(BaseTTXConverter): self.minorVersion = safeEval(attrs["minor"]) if not hasattr(self, "table"): self.table = otTables.avar() - self.table.Version = (self.majorVersion << 16) | self.minorVersion + self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr( + self, "minorVersion", 0 + ) elif name == "segment": axis = attrs["axis"] segment = self.segments[axis] = {} @@ -123,5 +127,5 @@ class table__a_v_a_r(BaseTTXConverter): "duplicate entry for %s in axis '%s'", fromValue, axis ) segment[fromValue] = toValue - #else: + # else: # super().fromXML(name, attrs, content, ttFont) From 06c725388addd817c4f175fe08795014b64f7780 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 8 Mar 2023 11:02:52 -0700 Subject: [PATCH 09/18] [avar] Fix a few tests --- Lib/fontTools/ttLib/tables/_a_v_a_r.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py index 7a0c78e49..b77b0aafb 100644 --- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py +++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py @@ -85,7 +85,7 @@ class table__a_v_a_r(BaseTTXConverter): segments[segment.FromCoordinate] = segment.ToCoordinate def toXML(self, writer, ttFont): - writer.simpletag("version", major=self.majorVersion, minor=self.minorVersion) + writer.simpletag("version", major=getattr(self, "majorVersion", 1), minor=getattr(self, "minorVersion", 0)) writer.newline() axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] for axis in axisTags: @@ -98,18 +98,19 @@ class table__a_v_a_r(BaseTTXConverter): writer.newline() writer.endtag("segment") writer.newline() - if self.majorVersion >= 2: + if getattr(self, "majorVersion", 1) >= 2: if self.table.VarIdxMap: self.table.VarIdxMap.toXML(writer, ttFont) if self.table.VarStore: self.table.VarStore.toXML(writer, ttFont) def fromXML(self, name, attrs, content, ttFont): + if not hasattr(self, "table"): + self.table = otTables.avar() + self.table.Reserved = 0 if name == "version": self.majorVersion = safeEval(attrs["major"]) self.minorVersion = safeEval(attrs["minor"]) - if not hasattr(self, "table"): - self.table = otTables.avar() self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr( self, "minorVersion", 0 ) From 6c35a5b65c04a83034f16197e75b668ae881ee92 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 8 Mar 2023 11:04:52 -0700 Subject: [PATCH 10/18] [avar2] Always set Version during compile --- Lib/fontTools/ttLib/tables/_a_v_a_r.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py index b77b0aafb..6aceeb8b7 100644 --- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py +++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py @@ -52,10 +52,10 @@ class table__a_v_a_r(BaseTTXConverter): axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] if not hasattr(self, "table"): self.table = otTables.avar() - self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr( - self, "minorVersion", 0 - ) self.table.Reserved = 0 + self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr( + self, "minorVersion", 0 + ) self.table.AxisCount = len(axisTags) self.table.AxisSegmentMap = [] for axis in axisTags: From 43b16df8db1afe08c6afb4fb75f4bcd21ae8c894 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 8 Mar 2023 11:07:06 -0700 Subject: [PATCH 11/18] [avar] Update test expectations --- Lib/fontTools/ttLib/tables/_a_v_a_r.py | 6 +++++- Tests/cffLib/data/TestCFF2Widths.ttx | 1 + Tests/cffLib/data/TestSparseCFF2VF.ttx | 1 + Tests/subset/data/TestGVAR.ttx | 1 + Tests/subset/data/TestHVVAR.ttx | 1 + Tests/subset/data/expect_HVVAR.ttx | 1 + Tests/subset/data/expect_HVVAR_retain_gids.ttx | 1 + Tests/subset/data/expect_keep_gvar.ttx | 1 + Tests/subset/data/expect_keep_gvar_notdef_outline.ttx | 1 + Tests/ttLib/data/I-512upem.ttx | 1 + Tests/ttLib/data/TestTTF_normalizeLocation.ttx | 1 + Tests/ttLib/data/varc-ac00-ac01-500upem.ttx | 1 + Tests/ttLib/tables/_a_v_a_r_test.py | 1 + Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx | 1 + Tests/varLib/data/test_results/BuildAvarEmptyAxis.ttx | 1 + Tests/varLib/data/test_results/BuildAvarIdentityMaps.ttx | 1 + Tests/varLib/data/test_results/BuildAvarSingleAxis.ttx | 1 + Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx | 1 + Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx | 1 + Tests/varLib/instancer/data/STATInstancerTest.ttx | 1 + Tests/varLib/instancer/data/SinglePos.ttx | 1 + 21 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py index 6aceeb8b7..386f6d189 100644 --- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py +++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py @@ -85,7 +85,11 @@ class table__a_v_a_r(BaseTTXConverter): segments[segment.FromCoordinate] = segment.ToCoordinate def toXML(self, writer, ttFont): - writer.simpletag("version", major=getattr(self, "majorVersion", 1), minor=getattr(self, "minorVersion", 0)) + writer.simpletag( + "version", + major=getattr(self, "majorVersion", 1), + minor=getattr(self, "minorVersion", 0), + ) writer.newline() axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] for axis in axisTags: diff --git a/Tests/cffLib/data/TestCFF2Widths.ttx b/Tests/cffLib/data/TestCFF2Widths.ttx index e3a3c9c1b..eba2c20ce 100644 --- a/Tests/cffLib/data/TestCFF2Widths.ttx +++ b/Tests/cffLib/data/TestCFF2Widths.ttx @@ -637,6 +637,7 @@ + diff --git a/Tests/cffLib/data/TestSparseCFF2VF.ttx b/Tests/cffLib/data/TestSparseCFF2VF.ttx index f1ae063b3..3dbf014af 100644 --- a/Tests/cffLib/data/TestSparseCFF2VF.ttx +++ b/Tests/cffLib/data/TestSparseCFF2VF.ttx @@ -1809,6 +1809,7 @@ + diff --git a/Tests/subset/data/TestGVAR.ttx b/Tests/subset/data/TestGVAR.ttx index b14466da2..2d2ee1e78 100644 --- a/Tests/subset/data/TestGVAR.ttx +++ b/Tests/subset/data/TestGVAR.ttx @@ -378,6 +378,7 @@ + diff --git a/Tests/subset/data/TestHVVAR.ttx b/Tests/subset/data/TestHVVAR.ttx index 3e746527e..5906988ea 100644 --- a/Tests/subset/data/TestHVVAR.ttx +++ b/Tests/subset/data/TestHVVAR.ttx @@ -406,6 +406,7 @@ + diff --git a/Tests/subset/data/expect_HVVAR.ttx b/Tests/subset/data/expect_HVVAR.ttx index 5fbc1770d..2bd862217 100644 --- a/Tests/subset/data/expect_HVVAR.ttx +++ b/Tests/subset/data/expect_HVVAR.ttx @@ -129,6 +129,7 @@ + diff --git a/Tests/subset/data/expect_HVVAR_retain_gids.ttx b/Tests/subset/data/expect_HVVAR_retain_gids.ttx index 8e51ca7f0..e54761709 100644 --- a/Tests/subset/data/expect_HVVAR_retain_gids.ttx +++ b/Tests/subset/data/expect_HVVAR_retain_gids.ttx @@ -140,6 +140,7 @@ + diff --git a/Tests/subset/data/expect_keep_gvar.ttx b/Tests/subset/data/expect_keep_gvar.ttx index 43e4a34a8..09c066be5 100644 --- a/Tests/subset/data/expect_keep_gvar.ttx +++ b/Tests/subset/data/expect_keep_gvar.ttx @@ -9,6 +9,7 @@ + diff --git a/Tests/subset/data/expect_keep_gvar_notdef_outline.ttx b/Tests/subset/data/expect_keep_gvar_notdef_outline.ttx index d4cc79f3e..7811b1bb7 100644 --- a/Tests/subset/data/expect_keep_gvar_notdef_outline.ttx +++ b/Tests/subset/data/expect_keep_gvar_notdef_outline.ttx @@ -8,6 +8,7 @@ + diff --git a/Tests/ttLib/data/I-512upem.ttx b/Tests/ttLib/data/I-512upem.ttx index 2cae5f123..400685e39 100644 --- a/Tests/ttLib/data/I-512upem.ttx +++ b/Tests/ttLib/data/I-512upem.ttx @@ -2668,6 +2668,7 @@ + diff --git a/Tests/ttLib/data/TestTTF_normalizeLocation.ttx b/Tests/ttLib/data/TestTTF_normalizeLocation.ttx index 389ba0dfe..0cb99593d 100644 --- a/Tests/ttLib/data/TestTTF_normalizeLocation.ttx +++ b/Tests/ttLib/data/TestTTF_normalizeLocation.ttx @@ -2,6 +2,7 @@ + diff --git a/Tests/ttLib/data/varc-ac00-ac01-500upem.ttx b/Tests/ttLib/data/varc-ac00-ac01-500upem.ttx index 3205d8d74..db32c06eb 100644 --- a/Tests/ttLib/data/varc-ac00-ac01-500upem.ttx +++ b/Tests/ttLib/data/varc-ac00-ac01-500upem.ttx @@ -417,6 +417,7 @@ + diff --git a/Tests/ttLib/tables/_a_v_a_r_test.py b/Tests/ttLib/tables/_a_v_a_r_test.py index f2ffbfa72..bf7871f88 100644 --- a/Tests/ttLib/tables/_a_v_a_r_test.py +++ b/Tests/ttLib/tables/_a_v_a_r_test.py @@ -51,6 +51,7 @@ class AxisVariationTableTest(unittest.TestCase): avar.toXML(writer, self.makeFont(["opsz"])) self.assertEqual( [ + '', '', '', '', diff --git a/Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx b/Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx index 29c5bb31f..5de731158 100644 --- a/Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx +++ b/Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx @@ -761,6 +761,7 @@ + diff --git a/Tests/varLib/data/test_results/BuildAvarEmptyAxis.ttx b/Tests/varLib/data/test_results/BuildAvarEmptyAxis.ttx index aee6f5aec..bff0993c7 100644 --- a/Tests/varLib/data/test_results/BuildAvarEmptyAxis.ttx +++ b/Tests/varLib/data/test_results/BuildAvarEmptyAxis.ttx @@ -2,6 +2,7 @@ + diff --git a/Tests/varLib/data/test_results/BuildAvarIdentityMaps.ttx b/Tests/varLib/data/test_results/BuildAvarIdentityMaps.ttx index 799d68f1d..f348a5b70 100644 --- a/Tests/varLib/data/test_results/BuildAvarIdentityMaps.ttx +++ b/Tests/varLib/data/test_results/BuildAvarIdentityMaps.ttx @@ -2,6 +2,7 @@ + diff --git a/Tests/varLib/data/test_results/BuildAvarSingleAxis.ttx b/Tests/varLib/data/test_results/BuildAvarSingleAxis.ttx index 9daa330f5..aacd28880 100644 --- a/Tests/varLib/data/test_results/BuildAvarSingleAxis.ttx +++ b/Tests/varLib/data/test_results/BuildAvarSingleAxis.ttx @@ -2,6 +2,7 @@ + diff --git a/Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx b/Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx index 781e106de..2f1754b05 100644 --- a/Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx +++ b/Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx @@ -787,6 +787,7 @@ + diff --git a/Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx b/Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx index cd7ffa05e..3acbf56d7 100644 --- a/Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx +++ b/Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx @@ -1139,6 +1139,7 @@ + diff --git a/Tests/varLib/instancer/data/STATInstancerTest.ttx b/Tests/varLib/instancer/data/STATInstancerTest.ttx index eee24d821..e4506cec8 100644 --- a/Tests/varLib/instancer/data/STATInstancerTest.ttx +++ b/Tests/varLib/instancer/data/STATInstancerTest.ttx @@ -1336,6 +1336,7 @@ + diff --git a/Tests/varLib/instancer/data/SinglePos.ttx b/Tests/varLib/instancer/data/SinglePos.ttx index 64ffd9f51..dda441e32 100644 --- a/Tests/varLib/instancer/data/SinglePos.ttx +++ b/Tests/varLib/instancer/data/SinglePos.ttx @@ -213,6 +213,7 @@ + From 90dd0685a46c851a5a3f504b7cb8950a4510dc31 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 8 Mar 2023 11:24:26 -0700 Subject: [PATCH 12/18] [avar] black --- Lib/fontTools/ttLib/tables/otData.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py index 635fec668..56716824e 100755 --- a/Lib/fontTools/ttLib/tables/otData.py +++ b/Lib/fontTools/ttLib/tables/otData.py @@ -6223,7 +6223,13 @@ otData = [ 0, 'The segment maps array — one segment map for each axis, in the order of axes specified in the "fvar" table', ), - ("LOffsetTo(DeltaSetIndexMap)", "VarIdxMap", None, "Version >= 0x00020000", ""), + ( + "LOffsetTo(DeltaSetIndexMap)", + "VarIdxMap", + None, + "Version >= 0x00020000", + "", + ), ("LOffset", "VarStore", None, "Version >= 0x00020000", ""), ], ), From b6acb839200e79a20b5c3bb151f36c5d1a047fb5 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 8 Mar 2023 11:44:23 -0700 Subject: [PATCH 13/18] [avar2] Fix fromXML a bit --- Lib/fontTools/ttLib/tables/_a_v_a_r.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py index 386f6d189..087a552d5 100644 --- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py +++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py @@ -104,7 +104,7 @@ class table__a_v_a_r(BaseTTXConverter): writer.newline() if getattr(self, "majorVersion", 1) >= 2: if self.table.VarIdxMap: - self.table.VarIdxMap.toXML(writer, ttFont) + self.table.VarIdxMap.toXML(writer, ttFont, name="VarIdxMap") if self.table.VarStore: self.table.VarStore.toXML(writer, ttFont) @@ -132,5 +132,5 @@ class table__a_v_a_r(BaseTTXConverter): "duplicate entry for %s in axis '%s'", fromValue, axis ) segment[fromValue] = toValue - # else: - # super().fromXML(name, attrs, content, ttFont) + else: + super().fromXML(name, attrs, content, ttFont) From e9e55b29f71e955e12a09d02ecbb10f4515c92f5 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 8 Mar 2023 13:19:51 -0700 Subject: [PATCH 14/18] chmod otData.py --- Lib/fontTools/ttLib/tables/otData.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Lib/fontTools/ttLib/tables/otData.py diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py old mode 100755 new mode 100644 From 85036d5d8338cca73cacd37fc7eeed310ed2c6c5 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 10 Mar 2023 11:36:31 -0700 Subject: [PATCH 15/18] [avar2] Revert unneeded change to VarIdxMap We don't use VarIdxMap anymore. --- Lib/fontTools/ttLib/tables/otTables.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py index 9a27a810d..1200ad0d7 100644 --- a/Lib/fontTools/ttLib/tables/otTables.py +++ b/Lib/fontTools/ttLib/tables/otTables.py @@ -711,9 +711,8 @@ class VarIdxMap(BaseTable): if mapping is None: mapping = self.mapping = {} - if type(mapping) == dict: - glyphOrder = font.getGlyphOrder() - mapping = [mapping[g] for g in glyphOrder] + glyphOrder = font.getGlyphOrder() + mapping = [mapping[g] for g in glyphOrder] while len(mapping) > 1 and mapping[-2] == mapping[-1]: del mapping[-1] From 1deed38b8d191303b9163e424c99e4aa3cd9fd03 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Tue, 14 Mar 2023 13:46:39 -0600 Subject: [PATCH 16/18] [avar2] Add compile test --- Lib/fontTools/ttLib/tables/_a_v_a_r.py | 2 + Tests/ttLib/tables/_a_v_a_r_test.py | 79 ++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py index 087a552d5..39039cf73 100644 --- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py +++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py @@ -52,6 +52,7 @@ class table__a_v_a_r(BaseTTXConverter): axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] if not hasattr(self, "table"): self.table = otTables.avar() + if not hasattr(self.table, "Reserved"): self.table.Reserved = 0 self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr( self, "minorVersion", 0 @@ -111,6 +112,7 @@ class table__a_v_a_r(BaseTTXConverter): def fromXML(self, name, attrs, content, ttFont): if not hasattr(self, "table"): self.table = otTables.avar() + if not hasattr(self.table, "Reserved"): self.table.Reserved = 0 if name == "version": self.majorVersion = safeEval(attrs["major"]) diff --git a/Tests/ttLib/tables/_a_v_a_r_test.py b/Tests/ttLib/tables/_a_v_a_r_test.py index bf7871f88..76528d18f 100644 --- a/Tests/ttLib/tables/_a_v_a_r_test.py +++ b/Tests/ttLib/tables/_a_v_a_r_test.py @@ -2,8 +2,11 @@ from fontTools.misc.testTools import parseXML from fontTools.misc.textTools import deHexStr from fontTools.misc.xmlWriter import XMLWriter from fontTools.ttLib import TTFont, TTLibError +import fontTools.ttLib.tables.otTables as otTables from fontTools.ttLib.tables._a_v_a_r import table__a_v_a_r from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis +import fontTools.varLib.models as models +import fontTools.varLib.varStore as varStore from io import BytesIO import unittest @@ -95,6 +98,82 @@ class AxisVariationTableTest(unittest.TestCase): return [line.strip() for line in content.splitlines()][1:] +class Avar2Test(unittest.TestCase): + def test(self): + + axisTags = ["wght", "wdth"] + fvar = table__f_v_a_r() + for tag in axisTags: + axis = Axis() + axis.axisTag = tag + fvar.axes.append(axis) + + master_locations_normalized = [ + {}, + {"wght": 1, "wdth": -1}, + ] + data = [ + {}, + {"wdth": -0.8}, + ] + + model = models.VariationModel(master_locations_normalized, axisTags) + store_builder = varStore.OnlineVarStoreBuilder(axisTags) + store_builder.setModel(model) + varIdxes = {} + for axis in axisTags: + masters = [m.get(axis, 0) * (1 << 14) for m in data] + varIdxes[axis] = store_builder.storeMasters(masters)[1] + store = store_builder.finish() + mapping = store.optimize() + varIdxes = {axis: mapping[value] for axis, value in varIdxes.items()} + del model, store_builder, mapping + + varIdxMap = otTables.DeltaSetIndexMap() + varIdxMap.mapping = [] + for tag in axisTags: + varIdxMap.mapping.append(varIdxes[tag]) + + avar = table__a_v_a_r() + avar.segments["wght"] = {} + avar.segments["wdth"] = {-1.0: -1.0, 0.0: 0.0, 0.4: 0.5, 1.0: 1.0} + + avar.majorVersion = 2 + avar.table = otTables.avar() + avar.table.VarIdxMap = varIdxMap + avar.table.VarStore = store + + font = TTFont() + font["fvar"] = fvar + font["avar"] = avar + + b = BytesIO() + font.save(b) + b.seek(0) + font2 = TTFont(b) + + assert font2["avar"].table.VarStore.VarRegionList.RegionAxisCount == 2 + assert font2["avar"].table.VarStore.VarRegionList.RegionCount == 1 + + xml1 = BytesIO() + writer = XMLWriter(xml1) + font["avar"].toXML(writer, font) + + xml2 = BytesIO() + writer = XMLWriter(xml2) + font2["avar"].toXML(writer, font2) + + # We can't compare because VarStore goes through transformation during compile :( + # assert xml1.getvalue() == xml2.getvalue(), (xml1.getvalue(), xml2.getvalue()) + + avar = table__a_v_a_r() + xml = b"".join(xml2.getvalue().splitlines()[1:]) + for name, attrs, content in parseXML(xml): + avar.fromXML(name, attrs, content, ttFont=TTFont()) + assert avar.table.VarStore.VarRegionList.RegionAxisCount == 2 + assert avar.table.VarStore.VarRegionList.RegionCount == 1 + + if __name__ == "__main__": import sys From b009e637333f3b606b03f7701be25aaf6501a5f8 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 15 Mar 2023 11:40:24 -0600 Subject: [PATCH 17/18] [avar2] Use fl2fi in test --- Tests/ttLib/tables/_a_v_a_r_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/ttLib/tables/_a_v_a_r_test.py b/Tests/ttLib/tables/_a_v_a_r_test.py index 76528d18f..cce4fb16c 100644 --- a/Tests/ttLib/tables/_a_v_a_r_test.py +++ b/Tests/ttLib/tables/_a_v_a_r_test.py @@ -1,6 +1,7 @@ from fontTools.misc.testTools import parseXML from fontTools.misc.textTools import deHexStr from fontTools.misc.xmlWriter import XMLWriter +from fontTools.misc.fixedTools import floatToFixed as fl2fi from fontTools.ttLib import TTFont, TTLibError import fontTools.ttLib.tables.otTables as otTables from fontTools.ttLib.tables._a_v_a_r import table__a_v_a_r @@ -122,7 +123,7 @@ class Avar2Test(unittest.TestCase): store_builder.setModel(model) varIdxes = {} for axis in axisTags: - masters = [m.get(axis, 0) * (1 << 14) for m in data] + masters = [fl2fi(m.get(axis, 0), 14) for m in data] varIdxes[axis] = store_builder.storeMasters(masters)[1] store = store_builder.finish() mapping = store.optimize() From 2edbbc1b32e83c0e44db8d29f21279a25ead51f0 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 15 Mar 2023 11:46:57 -0600 Subject: [PATCH 18/18] [avar2] Test xml for equality --- Tests/ttLib/tables/_a_v_a_r_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/ttLib/tables/_a_v_a_r_test.py b/Tests/ttLib/tables/_a_v_a_r_test.py index cce4fb16c..6ae8d0a9e 100644 --- a/Tests/ttLib/tables/_a_v_a_r_test.py +++ b/Tests/ttLib/tables/_a_v_a_r_test.py @@ -131,6 +131,7 @@ class Avar2Test(unittest.TestCase): del model, store_builder, mapping varIdxMap = otTables.DeltaSetIndexMap() + varIdxMap.Format = 1 varIdxMap.mapping = [] for tag in axisTags: varIdxMap.mapping.append(varIdxes[tag]) @@ -164,8 +165,7 @@ class Avar2Test(unittest.TestCase): writer = XMLWriter(xml2) font2["avar"].toXML(writer, font2) - # We can't compare because VarStore goes through transformation during compile :( - # assert xml1.getvalue() == xml2.getvalue(), (xml1.getvalue(), xml2.getvalue()) + assert xml1.getvalue() == xml2.getvalue(), (xml1.getvalue(), xml2.getvalue()) avar = table__a_v_a_r() xml = b"".join(xml2.getvalue().splitlines()[1:])