Add support for CmapSubtable_Format_0, KernTable, LTSHTable, VDMXTable, ContextPosSubtable, and AxisValueTable_Format_2

This commit is contained in:
germax26 2024-09-15 16:17:20 +10:00
parent 9ed1bfcadb
commit 61aef094df
Signed by: germax26
SSH Key Fingerprint: SHA256:N3w+8798IMWBt7SYH8G1C0iJlIa2HIIcRCXwILT5FvM
2 changed files with 268 additions and 9 deletions

View File

@ -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)

View File

@ -1,4 +1,4 @@
from abc import ABC from abc import ABC, abstractmethod
class ABD(ABC): class ABD(ABC):
""" """