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 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
|
||||
|
||||
T = TypeVar('T')
|
||||
@ -165,6 +165,13 @@ class TableTag(Enum):
|
||||
STAT = 'STAT'
|
||||
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 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}"
|
||||
|
||||
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:
|
||||
length = 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)
|
||||
|
||||
@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
|
||||
class KernTable(Table):
|
||||
pass
|
||||
version: int
|
||||
nTables: int
|
||||
subtables: List[KernSubtable]
|
||||
|
||||
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
|
||||
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:
|
||||
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
|
||||
class PCLTTable(Table):
|
||||
@ -1453,12 +1556,74 @@ class PCLTTable(Table):
|
||||
def parse_PCLT_table(f: BinaryIO) -> PCLTTable:
|
||||
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
|
||||
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:
|
||||
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
|
||||
@dataclass
|
||||
@ -3828,7 +3993,7 @@ class MarkMarkPosSubtable_Format_1(MarkMarkPosSubtable):
|
||||
|
||||
@dataclass
|
||||
class ContextPosSubtable(GPOSLookupSubtable, ABD):
|
||||
pass
|
||||
posFormat: int
|
||||
|
||||
@dataclass
|
||||
class PosLookupRecord:
|
||||
@ -3841,6 +4006,60 @@ def parse_pos_lookup_record(f: BinaryIO) -> PosLookupRecord:
|
||||
|
||||
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
|
||||
|
||||
@dataclass
|
||||
@ -4099,6 +4318,37 @@ def parse_GPOS_lookup_subtable(f: BinaryIO, lookupType: GPOSLookupType) -> GPOSL
|
||||
case _:
|
||||
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
|
||||
case GPOSLookupType.ChainContextPos:
|
||||
start_tell = f.tell()
|
||||
@ -5787,6 +6037,15 @@ def parse_axis_value_table(f: BinaryIO) -> AxisValueTable:
|
||||
value = read_fixed(f)
|
||||
|
||||
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:
|
||||
axisIndex = read_u16(f)
|
||||
flags = parse_axis_value_table_flags(f)
|
||||
|
@ -1,4 +1,4 @@
|
||||
from abc import ABC
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class ABD(ABC):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user