Add support for CmapSubtable_Format_0, KernTable, LTSHTable, VDMXTable, ContextPosSubtable, and AxisValueTable_Format_2
This commit is contained in:
parent
9ed1bfcadb
commit
61aef094df
@ -6,7 +6,7 @@ from math import floor, log2
|
|||||||
from typing import Callable, Generic, List, Optional, Tuple, TypeVar, BinaryIO
|
from typing import Callable, Generic, List, Optional, Tuple, TypeVar, BinaryIO
|
||||||
|
|
||||||
from OFF_io_utils import SaveTell, null_if_zero, parse_at_offset, parse_at_offsets, parse_at_offsets_using_length, parse_at_optional_offset, parse_at_optional_offsets, parse_list_and_use_offsets_into, parse_list_at_offset, read_F2DOT14, read_fixed, read_fixed_version, read_long_datetime
|
from OFF_io_utils import SaveTell, null_if_zero, parse_at_offset, parse_at_offsets, parse_at_offsets_using_length, parse_at_optional_offset, parse_at_optional_offsets, parse_list_and_use_offsets_into, parse_list_at_offset, read_F2DOT14, read_fixed, read_fixed_version, read_long_datetime
|
||||||
from abcde import ABD, ABE
|
from abcde import ABD, ABE, abstractmethod
|
||||||
from io_utils import Parser, is_at_end, len_to_end, read_ascii, read_i16, read_i32, read_i8, read_int, read_pascal_string, read_u16, read_u24, read_u32, read_u64, read_u8
|
from io_utils import Parser, is_at_end, len_to_end, read_ascii, read_i16, read_i32, read_i8, read_int, read_pascal_string, read_u16, read_u24, read_u32, read_u64, read_u8
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
@ -165,6 +165,13 @@ class TableTag(Enum):
|
|||||||
STAT = 'STAT'
|
STAT = 'STAT'
|
||||||
VVAR = 'VVAR'
|
VVAR = 'VVAR'
|
||||||
|
|
||||||
|
# Tables as recognised by [FontTools](https://fonttools.readthedocs.io/en/latest/ttLib/tables.html)
|
||||||
|
Mort = 'mort'
|
||||||
|
FFTM = 'FFTM'
|
||||||
|
CBDT = 'CBDT'
|
||||||
|
CBLC = 'CBLC'
|
||||||
|
Meta = 'meta'
|
||||||
|
|
||||||
def __str__(self) -> str: return self._value_
|
def __str__(self) -> str: return self._value_
|
||||||
|
|
||||||
def parse_table_tag(f: BinaryIO) -> TableTag:
|
def parse_table_tag(f: BinaryIO) -> TableTag:
|
||||||
@ -360,6 +367,13 @@ def parse_cmap_subtable(f: BinaryIO, platformID: PlatformID) -> CmapSubtable:
|
|||||||
assert format in [0, 2, 4, 6, 8, 10, 12, 13, 14], f"Invalid format: {format}"
|
assert format in [0, 2, 4, 6, 8, 10, 12, 13, 14], f"Invalid format: {format}"
|
||||||
|
|
||||||
match format:
|
match format:
|
||||||
|
case 0:
|
||||||
|
length = read_u16(f)
|
||||||
|
language = read_u16(f)
|
||||||
|
if platformID != PlatformID.Macintosh: assert language == 0
|
||||||
|
glyphIdArray = [read_u8(f) for _ in range(256)]
|
||||||
|
|
||||||
|
return CmapSubtable_Format_0(format, length, language, glyphIdArray)
|
||||||
case 4:
|
case 4:
|
||||||
length = read_u16(f)
|
length = read_u16(f)
|
||||||
language = read_u16(f)
|
language = read_u16(f)
|
||||||
@ -1432,19 +1446,108 @@ def parse_hdmx_table(f: BinaryIO, numGlyphs: int) -> HdmxTable:
|
|||||||
|
|
||||||
return HdmxTable(version, numRecords, sizeDeviceRecord, records)
|
return HdmxTable(version, numRecords, sizeDeviceRecord, records)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class KernSubtableCoverage:
|
||||||
|
bytes: int
|
||||||
|
def horizontal(self) -> int: return self.bytes & 1
|
||||||
|
def minimum(self) -> int: return self.bytes >> 1 & 1
|
||||||
|
def crossstream(self) -> int: return self.bytes >> 2 & 1
|
||||||
|
def override(self) -> int: return self.bytes >> 3 & 1
|
||||||
|
def format(self) -> int: return self.bytes >> 8 & 0x1000
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
assert self.bytes >> 4 & 0x10 == 0, self.bytes
|
||||||
|
assert self.format() in [0, 2]
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class KernSubtable(Table, ABD):
|
||||||
|
version: int
|
||||||
|
length: int
|
||||||
|
coverage: KernSubtableCoverage
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class KerningPairValue:
|
||||||
|
left: int
|
||||||
|
right: int
|
||||||
|
value: float
|
||||||
|
|
||||||
|
def parse_kerning_pair_value(f: BinaryIO) -> KerningPairValue:
|
||||||
|
left = read_u16(f)
|
||||||
|
right = read_u16(f)
|
||||||
|
value = read_i16(f)
|
||||||
|
|
||||||
|
return KerningPairValue(left, right, value)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class KernSubtable_Format_0(KernSubtable):
|
||||||
|
nPairs: int
|
||||||
|
searchRange: int
|
||||||
|
entrySelector: int
|
||||||
|
rangeShift: int
|
||||||
|
pairs: List[KerningPairValue]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_kern_subtable(f: BinaryIO) -> KernSubtable:
|
||||||
|
start_tell = f.tell()
|
||||||
|
|
||||||
|
version = read_u16(f)
|
||||||
|
assert version in [0]
|
||||||
|
|
||||||
|
length = read_u16(f)
|
||||||
|
coverage = KernSubtableCoverage(read_u16(f))
|
||||||
|
|
||||||
|
match coverage.format():
|
||||||
|
case 0:
|
||||||
|
nPairs = read_u16(f)
|
||||||
|
searchRange = read_u16(f)
|
||||||
|
entrySelector = read_u16(f)
|
||||||
|
rangeShift = read_u16(f)
|
||||||
|
pairs = [parse_kerning_pair_value(f) for _ in range(nPairs)]
|
||||||
|
|
||||||
|
assert f.tell() - start_tell == length, (f.tell(), start_tell, length)
|
||||||
|
return KernSubtable_Format_0(version, length, coverage, nPairs, searchRange, entrySelector, rangeShift, pairs)
|
||||||
|
case 2:
|
||||||
|
assert False
|
||||||
|
case _:
|
||||||
|
assert False, f"Unimplemented: format: {coverage.format()}"
|
||||||
|
|
||||||
|
assert False, (version, length, coverage)
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class KernTable(Table):
|
class KernTable(Table):
|
||||||
pass
|
version: int
|
||||||
|
nTables: int
|
||||||
|
subtables: List[KernSubtable]
|
||||||
|
|
||||||
def parse_Kern_table(f: BinaryIO) -> KernTable:
|
def parse_Kern_table(f: BinaryIO) -> KernTable:
|
||||||
assert False
|
version = read_u16(f)
|
||||||
|
assert version in [0]
|
||||||
|
|
||||||
|
nTables = read_u16(f)
|
||||||
|
subtables = [parse_kern_subtable(f) for _ in range(nTables)]
|
||||||
|
|
||||||
|
return KernTable(version, nTables, subtables)
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LTSHTable(Table):
|
class LTSHTable(Table):
|
||||||
pass
|
version: int
|
||||||
|
numGlyphs: int
|
||||||
|
yPels: List[int]
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LTSHTable_Ver_0(LTSHTable): pass
|
||||||
|
|
||||||
def parse_LTSH_table(f: BinaryIO) -> LTSHTable:
|
def parse_LTSH_table(f: BinaryIO) -> LTSHTable:
|
||||||
assert False
|
version = read_u16(f)
|
||||||
|
assert version in [0]
|
||||||
|
|
||||||
|
numGlyphs = read_u16(f)
|
||||||
|
yPels = [read_u8(f) for _ in range(numGlyphs)]
|
||||||
|
|
||||||
|
if version == 0:
|
||||||
|
return LTSHTable_Ver_0(version, numGlyphs, yPels)
|
||||||
|
|
||||||
|
assert False, f"Unimplemented: version: {version}"
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PCLTTable(Table):
|
class PCLTTable(Table):
|
||||||
@ -1453,12 +1556,74 @@ class PCLTTable(Table):
|
|||||||
def parse_PCLT_table(f: BinaryIO) -> PCLTTable:
|
def parse_PCLT_table(f: BinaryIO) -> PCLTTable:
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RatioRangeRecord(Record):
|
||||||
|
bCharSet: int
|
||||||
|
xRatio: int
|
||||||
|
yStartRatio: int
|
||||||
|
yEndRatio: int
|
||||||
|
|
||||||
|
def parse_ratio_range_record(f: BinaryIO) -> RatioRangeRecord:
|
||||||
|
bCharSet = read_u8(f)
|
||||||
|
xRatio = read_u8(f)
|
||||||
|
yStartRatio = read_u8(f)
|
||||||
|
yEndRatio = read_u8(f)
|
||||||
|
|
||||||
|
return RatioRangeRecord(bCharSet, xRatio, yStartRatio, yEndRatio)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VTableRecord(Record):
|
||||||
|
yPelHeight: int
|
||||||
|
yMax: int
|
||||||
|
yMin: int
|
||||||
|
|
||||||
|
def parse_v_table_record(f: BinaryIO) -> VTableRecord:
|
||||||
|
yPelHeight = read_u16(f)
|
||||||
|
yMax = read_i16(f)
|
||||||
|
yMin = read_i16(f)
|
||||||
|
|
||||||
|
return VTableRecord(yPelHeight, yMax, yMin)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VDMXGroup:
|
||||||
|
recs: int
|
||||||
|
startsz: int
|
||||||
|
endsz: int
|
||||||
|
entry: List[VTableRecord]
|
||||||
|
|
||||||
|
def parse_vdmx_group(f: BinaryIO) -> VDMXGroup:
|
||||||
|
recs = read_u16(f)
|
||||||
|
startsz = read_u8(f)
|
||||||
|
endsz = read_u8(f)
|
||||||
|
entry = [parse_v_table_record(f) for _ in range(recs)]
|
||||||
|
|
||||||
|
return VDMXGroup(recs, startsz, endsz, entry)
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class VDMXTable(Table):
|
class VDMXTable(Table):
|
||||||
pass
|
version: int
|
||||||
|
numRecs: int
|
||||||
|
numRatios: int
|
||||||
|
ratRange: List[RatioRangeRecord]
|
||||||
|
groups_by_ratio: List[VDMXGroup] # same objects as in groups, but ordered by which ratio references them
|
||||||
|
groups: List[VDMXGroup]
|
||||||
|
|
||||||
|
|
||||||
def parse_VDMX_table(f: BinaryIO) -> VDMXTable:
|
def parse_VDMX_table(f: BinaryIO) -> VDMXTable:
|
||||||
assert False
|
start_tell = f.tell()
|
||||||
|
|
||||||
|
version = read_u16(f)
|
||||||
|
assert version in [0, 1]
|
||||||
|
|
||||||
|
numRecs = read_u16(f)
|
||||||
|
numRatios = read_u16(f)
|
||||||
|
|
||||||
|
ratRange = [parse_ratio_range_record(f) for _ in range(numRecs)]
|
||||||
|
offsets = [read_u16(f) for _ in range(numRatios)]
|
||||||
|
|
||||||
|
groups_by_ratio, groups = parse_list_and_use_offsets_into(f, start_tell, offsets, numRecs, parse_vdmx_group)
|
||||||
|
|
||||||
|
return VDMXTable(version, numRecs, numRatios, ratRange, groups_by_ratio, groups)
|
||||||
|
|
||||||
# NOTE: Different versions of VheaTable are not backwards compatible due to differences in three fields
|
# NOTE: Different versions of VheaTable are not backwards compatible due to differences in three fields
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -3828,7 +3993,7 @@ class MarkMarkPosSubtable_Format_1(MarkMarkPosSubtable):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ContextPosSubtable(GPOSLookupSubtable, ABD):
|
class ContextPosSubtable(GPOSLookupSubtable, ABD):
|
||||||
pass
|
posFormat: int
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PosLookupRecord:
|
class PosLookupRecord:
|
||||||
@ -3841,6 +4006,60 @@ def parse_pos_lookup_record(f: BinaryIO) -> PosLookupRecord:
|
|||||||
|
|
||||||
return PosLookupRecord(sequenceIndex, lookupListIndex)
|
return PosLookupRecord(sequenceIndex, lookupListIndex)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PosRuleTable(Table):
|
||||||
|
glyphCount: int
|
||||||
|
posCount: int
|
||||||
|
inputSequence: List[int]
|
||||||
|
posLookupRecords: List[PosLookupRecord]
|
||||||
|
|
||||||
|
def parse_pos_rule_table(f: BinaryIO) -> PosRuleTable:
|
||||||
|
glyphCount = read_u16(f)
|
||||||
|
posCount = read_u16(f)
|
||||||
|
inputSequence = [read_u16(f) for _ in range(glyphCount-1)]
|
||||||
|
posLookupRecords = [parse_pos_lookup_record(f) for _ in range(posCount)]
|
||||||
|
|
||||||
|
return PosRuleTable(glyphCount, posCount, inputSequence, posLookupRecords)
|
||||||
|
|
||||||
|
PosRuleSetTable = SetTable[PosRuleTable]
|
||||||
|
|
||||||
|
def parse_pos_rule_set_table(f: BinaryIO) -> PosRuleSetTable:
|
||||||
|
return parse_set_table(f, parse_pos_rule_table)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ContextPosSubtable_Format_1(ContextPosSubtable):
|
||||||
|
coverage: CoverageTable
|
||||||
|
posRuleSetCount: int
|
||||||
|
posRuleSets: List[PosRuleSetTable]
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PosClassRuleTable(Table):
|
||||||
|
glyphCount: int
|
||||||
|
posCount: int
|
||||||
|
classes: List[int]
|
||||||
|
posLookupRecords: List[PosLookupRecord]
|
||||||
|
|
||||||
|
def parse_pos_class_rule_table(f: BinaryIO) -> PosClassRuleTable:
|
||||||
|
glyphCount = read_u16(f)
|
||||||
|
posCount = read_u16(f)
|
||||||
|
classes = [read_u16(f) for _ in range(glyphCount-1)]
|
||||||
|
posLookupRecords = [parse_pos_lookup_record(f) for _ in range(posCount)]
|
||||||
|
|
||||||
|
return PosClassRuleTable(glyphCount, posCount, classes, posLookupRecords)
|
||||||
|
|
||||||
|
PosClassSetTable = SetTable[PosClassRuleTable]
|
||||||
|
|
||||||
|
def parse_pos_class_set_table(f: BinaryIO) -> PosClassSetTable:
|
||||||
|
return parse_set_table(f, parse_pos_class_rule_table)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ContextPosSubtable_Format_2(ContextPosSubtable):
|
||||||
|
coverage: CoverageTable
|
||||||
|
classDef: ClassDefTable
|
||||||
|
posClassSetCount: int
|
||||||
|
posClassSets: List[Optional[PosClassSetTable]]
|
||||||
|
|
||||||
# 8
|
# 8
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -4099,6 +4318,37 @@ def parse_GPOS_lookup_subtable(f: BinaryIO, lookupType: GPOSLookupType) -> GPOSL
|
|||||||
case _:
|
case _:
|
||||||
assert False, f"Unimplemented: posFormat: {posFormat}"
|
assert False, f"Unimplemented: posFormat: {posFormat}"
|
||||||
|
|
||||||
|
assert False, posFormat
|
||||||
|
case GPOSLookupType.ContextPos:
|
||||||
|
start_tell = f.tell()
|
||||||
|
|
||||||
|
posFormat = read_u16(f)
|
||||||
|
assert posFormat in [1, 2, 3]
|
||||||
|
|
||||||
|
match posFormat:
|
||||||
|
case 1:
|
||||||
|
coverageOffset = read_u16(f)
|
||||||
|
posRuleSetCount = read_u16(f)
|
||||||
|
posRuleSetOffsets = [read_u16(f) for _ in range(posRuleSetCount)]
|
||||||
|
with SaveTell(f):
|
||||||
|
coverage = parse_at_offset(f, start_tell, coverageOffset, parse_coverage_table)
|
||||||
|
posRuleSets = parse_at_offsets(f, start_tell, posRuleSetOffsets, parse_pos_rule_set_table)
|
||||||
|
|
||||||
|
return ContextPosSubtable_Format_1(posFormat, coverage, posRuleSetCount, posRuleSets)
|
||||||
|
case 2:
|
||||||
|
coverageOffset = read_u16(f)
|
||||||
|
classDefOffset = read_u16(f)
|
||||||
|
posClassSetCount = read_u16(f)
|
||||||
|
posClassSetOffsets = [read_u16(f) for _ in range(posClassSetCount)]
|
||||||
|
with SaveTell(f):
|
||||||
|
coverage = parse_at_offset(f, start_tell, coverageOffset, parse_coverage_table)
|
||||||
|
classDef = parse_at_offset(f, start_tell, classDefOffset, parse_class_def_table)
|
||||||
|
posClassSets = parse_at_optional_offsets(f, start_tell, posClassSetOffsets, parse_pos_class_set_table)
|
||||||
|
|
||||||
|
return ContextPosSubtable_Format_2(posFormat, coverage, classDef, posClassSetCount, posClassSets)
|
||||||
|
case _:
|
||||||
|
assert False, f"Unimplemented: posFormat: {posFormat}"
|
||||||
|
|
||||||
assert False, posFormat
|
assert False, posFormat
|
||||||
case GPOSLookupType.ChainContextPos:
|
case GPOSLookupType.ChainContextPos:
|
||||||
start_tell = f.tell()
|
start_tell = f.tell()
|
||||||
@ -5787,6 +6037,15 @@ def parse_axis_value_table(f: BinaryIO) -> AxisValueTable:
|
|||||||
value = read_fixed(f)
|
value = read_fixed(f)
|
||||||
|
|
||||||
return AxisValueTable_Format_1(format, axisIndex, flags, valueNameID, value)
|
return AxisValueTable_Format_1(format, axisIndex, flags, valueNameID, value)
|
||||||
|
case 2:
|
||||||
|
axisIndex = read_u16(f)
|
||||||
|
flags = parse_axis_value_table_flags(f)
|
||||||
|
valueNameID = read_u16(f)
|
||||||
|
nominalValue = read_fixed(f)
|
||||||
|
rangeMinValue = read_fixed(f)
|
||||||
|
rangeMaxValue = read_fixed(f)
|
||||||
|
|
||||||
|
return AxisValueTable_Format_2(format, axisIndex, flags, valueNameID, nominalValue, rangeMinValue, rangeMaxValue)
|
||||||
case 3:
|
case 3:
|
||||||
axisIndex = read_u16(f)
|
axisIndex = read_u16(f)
|
||||||
flags = parse_axis_value_table_flags(f)
|
flags = parse_axis_value_table_flags(f)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from abc import ABC
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
class ABD(ABC):
|
class ABD(ABC):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user