Clean up code
This commit is contained in:
		
							parent
							
								
									adfd9b1ba4
								
							
						
					
					
						commit
						9ed1bfcadb
					
				
							
								
								
									
										109
									
								
								OpenFontFormat/OFF_io_utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								OpenFontFormat/OFF_io_utils.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					from datetime import datetime, timedelta, timezone
 | 
				
			||||||
 | 
					from types import TracebackType
 | 
				
			||||||
 | 
					from typing import BinaryIO, Callable, List, Optional, Tuple, Type, TypeVar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from io_utils import Parser, read_fixed_point, read_u16, read_u64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def read_fixed(f: BinaryIO) -> float: # 16.16
 | 
				
			||||||
 | 
						return read_fixed_point(f, 16, 16)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def read_fixed_version(f: BinaryIO) -> float: # Not the same as parse_fixed
 | 
				
			||||||
 | 
						majorVersion = read_u16(f)
 | 
				
			||||||
 | 
						minorVersion = read_u16(f)
 | 
				
			||||||
 | 
						assert minorVersion in [0x0000, 0x1000, 0x5000], f"Invalid fixed minorVersion: {hex(minorVersion)}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return majorVersion + minorVersion/0xa000 # will need to change if there are ever any versions with 2 decimal digits
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def read_F2DOT14(f: BinaryIO) -> float: # F2DOT14 (2.14)
 | 
				
			||||||
 | 
						return read_fixed_point(f, 2, 14)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EPOCH = datetime(1904, 1, 1, tzinfo=timezone.utc)
 | 
				
			||||||
 | 
					def read_long_datetime(f: BinaryIO) -> datetime:
 | 
				
			||||||
 | 
						return EPOCH+timedelta(seconds=read_u64(f))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					T = TypeVar('T')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					The following `parse_at_...` functions all modify the tell of f, so it is recommended that you use `SaveTell` to wrap calling these.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_at_offset(f: BinaryIO, start_tell: int, offset: int, parser: Parser[T], *, zero_is_null:bool=True) -> T:
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						Parses a `T` using `parser` at an offset of `offset` from `start_tell`. 
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						If `zero_is_null` is True, then `offset` cannot be 0.
 | 
				
			||||||
 | 
						Only set `zero_is_null` to False when you are sure that 0 is a valid offset
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						if zero_is_null: assert offset != 0, f"Offset was NULL"
 | 
				
			||||||
 | 
						f.seek(start_tell+offset)
 | 
				
			||||||
 | 
						return parser(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_at_optional_offset(f: BinaryIO, start_tell: int, offset: int, parser: Parser[T]) -> Optional[T]:
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						Same as `parse_at_offset`, however if the offset is NULL (0), then None is returned.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Should not be used when 0 is a valid offset to something.
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						if offset == 0: return None
 | 
				
			||||||
 | 
						return parse_at_offset(f, start_tell, offset, parser, zero_is_null=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_at_offsets(f: BinaryIO, start_tell: int, offsets: List[int], parser: Parser[T], *, zero_is_null:bool=True) -> List[T]:
 | 
				
			||||||
 | 
						return [parse_at_offset(f, start_tell, offset, parser, zero_is_null=zero_is_null) for offset in offsets]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_at_optional_offsets(f: BinaryIO, start_tell: int, offsets: List[int], parser: Parser[T]) -> List[Optional[T]]:
 | 
				
			||||||
 | 
						return [parse_at_optional_offset(f, start_tell, offset, parser) for offset in offsets]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_at_offsets_using_length(f: BinaryIO, start_tell: int, offsets: List[int], parser: Callable[[BinaryIO, int, int], T], *, zero_is_null:bool=True) -> List[Optional[T]]:
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						The length of the returned list will be one less than that of `offsets`, as the last offset is used to calculate the length of the final element.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						`parser` is of the form `(f: BinaryIO, index: int, length: int) -> T`
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						elements: List[Optional[T]] = []
 | 
				
			||||||
 | 
						for i, offset in enumerate(offsets[:-1]):
 | 
				
			||||||
 | 
							length = offsets[i+1]-offset
 | 
				
			||||||
 | 
							if length == 0:
 | 
				
			||||||
 | 
								elements.append(None)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							elements.append(parse_at_offset(f, start_tell, offset, lambda f: parser(f, i, length), zero_is_null=zero_is_null))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return elements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_list_at_offset(f: BinaryIO, start_tell: int, offset: int, count: int, parser: Parser[T], *, zero_is_null:bool=True) -> List[T]:
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						Parses a continuous list of `T`s with `count` elements.
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						return parse_at_offset(f, start_tell, offset, lambda f: [parser(f) for _ in range(count)], zero_is_null=zero_is_null)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_list_and_use_offsets_into(f: BinaryIO, start_tell: int, offsets: list[int], count: int, parser: Parser[T]) -> Tuple[List[T], List[T]]:
 | 
				
			||||||
 | 
						elements = [(f.tell(), parser(f)) for _ in range(count)]
 | 
				
			||||||
 | 
						elements_by_offset_into: List[T] = []
 | 
				
			||||||
 | 
						for offset in offsets:
 | 
				
			||||||
 | 
							for (offset_into, element) in elements:
 | 
				
			||||||
 | 
								if offset + start_tell == offset_into:
 | 
				
			||||||
 | 
									elements_by_offset_into.append(element)
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
							else:
 | 
				
			||||||
 | 
								assert False, (f"No element with offset {offset} into this list of elements", start_tell, offsets, elements)
 | 
				
			||||||
 | 
						return elements_by_offset_into, [element for (_, element) in elements]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SaveTell:
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						A context manager that allows operations to be done on a BinaryIO without affecting the tell.
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						def __init__(self, f: BinaryIO):
 | 
				
			||||||
 | 
							self.f = f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def __enter__(self) -> int:
 | 
				
			||||||
 | 
							self.tell = self.f.tell()
 | 
				
			||||||
 | 
							return self.tell
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], exc_tb: Optional[TracebackType]):
 | 
				
			||||||
 | 
							self.f.seek(self.tell)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def null_if_zero(n: int) -> Optional[int]: return n if n else None
 | 
				
			||||||
 | 
					def nulls_if_zero(ns: List[int]) -> List[Optional[int]]: return list(map(null_if_zero, ns))
 | 
				
			||||||
@ -1,29 +1,15 @@
 | 
				
			|||||||
from dataclasses import dataclass
 | 
					from dataclasses import dataclass
 | 
				
			||||||
from datetime import datetime, timedelta, timezone
 | 
					from datetime import datetime
 | 
				
			||||||
from enum import Enum, EnumMeta
 | 
					from enum import Enum, EnumMeta
 | 
				
			||||||
from io import BytesIO
 | 
					from io import BytesIO
 | 
				
			||||||
from math import floor, log2
 | 
					from math import floor, log2
 | 
				
			||||||
from types import TracebackType
 | 
					from typing import  Callable, Generic, List, Optional, Tuple, TypeVar, BinaryIO
 | 
				
			||||||
from typing import  Callable, Generic, List, Optional, Tuple, Type, TypeVar, BinaryIO, Self
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
from io_utils import read_ascii, read_fixed_point, 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def read_fixed(f: BinaryIO) -> float: # 16.16
 | 
					T = TypeVar('T')
 | 
				
			||||||
	return read_fixed_point(f, 16, 16)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def read_fixed_version(f: BinaryIO) -> float: # Not the same as parse_fixed
 | 
					 | 
				
			||||||
	majorVersion = read_u16(f)
 | 
					 | 
				
			||||||
	minorVersion = read_u16(f)
 | 
					 | 
				
			||||||
	assert minorVersion in [0x0000, 0x1000, 0x5000], f"Invalid fixed minorVersion: {hex(minorVersion)}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return majorVersion + minorVersion/0x1000/10 # will need to change if there are ever any versions with 2 decimal digits
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def read_F2DOT14(f: BinaryIO) -> float: # F2DOT14 (2.14)
 | 
					 | 
				
			||||||
	return read_fixed_point(f, 2, 14)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def read_long_datetime(f: BinaryIO) -> datetime:
 | 
					 | 
				
			||||||
	return datetime(1904, 1, 1, tzinfo=timezone.utc)+timedelta(seconds=read_u64(f))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Tag_ = TypeVar('Tag_')
 | 
					Tag_ = TypeVar('Tag_')
 | 
				
			||||||
SomeTag = Callable[[str], Tag_] # If SomeTag is not an EnumMeta, it should throw a ValueError to indicate an invalid tag
 | 
					SomeTag = Callable[[str], Tag_] # If SomeTag is not an EnumMeta, it should throw a ValueError to indicate an invalid tag
 | 
				
			||||||
@ -38,11 +24,12 @@ def read_tag_with_conditions(f: BinaryIO, *conditions: Tuple[Callable[[str], boo
 | 
				
			|||||||
	else:
 | 
						else:
 | 
				
			||||||
		assert False, f"Invalid {umbrellaTagCls.__name__}: '{tag}'"
 | 
							assert False, f"Invalid {umbrellaTagCls.__name__}: '{tag}'"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					always: Callable[[str], bool] = lambda _: True
 | 
				
			||||||
def read_tag_from_tags(f: BinaryIO, *tagClss: SomeTag[Tag_], umbrellaTagCls: type | SomeTag[Tag_], strict:bool=True) -> Tag_:
 | 
					def read_tag_from_tags(f: BinaryIO, *tagClss: SomeTag[Tag_], umbrellaTagCls: type | SomeTag[Tag_], strict:bool=True) -> Tag_:
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	This is meant to be used for when some instances of an Enum are just CC01, CC02, CC03, ...
 | 
						This is meant to be used for when some instances of an Enum are just CC01, CC02, CC03, ...
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	return read_tag_with_conditions(f, *[(lambda _: True, tagCls) for tagCls in tagClss], umbrellaTagCls=umbrellaTagCls, strict=strict)
 | 
						return read_tag_with_conditions(f, *[(always, tagCls) for tagCls in tagClss], umbrellaTagCls=umbrellaTagCls, strict=strict)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def read_tag(f: BinaryIO, tagCls: SomeTag[Tag_], *, strict:bool=True) -> Tag_:
 | 
					def read_tag(f: BinaryIO, tagCls: SomeTag[Tag_], *, strict:bool=True) -> Tag_:
 | 
				
			||||||
	return read_tag_from_tags(f, tagCls, umbrellaTagCls=tagCls, strict=strict)
 | 
						return read_tag_from_tags(f, tagCls, umbrellaTagCls=tagCls, strict=strict)
 | 
				
			||||||
@ -50,9 +37,6 @@ def read_tag(f: BinaryIO, tagCls: SomeTag[Tag_], *, strict:bool=True) -> Tag_:
 | 
				
			|||||||
ID_ = TypeVar('ID_')
 | 
					ID_ = TypeVar('ID_')
 | 
				
			||||||
SomeID = Callable[[int], ID_]
 | 
					SomeID = Callable[[int], ID_]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
T = TypeVar('T')
 | 
					 | 
				
			||||||
Parser = Callable[[BinaryIO], T]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def read_id_from_ranges(f: BinaryIO, *ranges: Tuple[Optional[int], SomeID[ID_]], umbrellaIdCls: SomeID[ID_], reader: Parser[int]=read_u16) -> ID_: # must be in ascending order
 | 
					def read_id_from_ranges(f: BinaryIO, *ranges: Tuple[Optional[int], SomeID[ID_]], umbrellaIdCls: SomeID[ID_], reader: Parser[int]=read_u16) -> ID_: # must be in ascending order
 | 
				
			||||||
	assert len(ranges) > 0, f"Must have at least one range"
 | 
						assert len(ranges) > 0, f"Must have at least one range"
 | 
				
			||||||
	id = reader(f)
 | 
						id = reader(f)
 | 
				
			||||||
@ -60,67 +44,11 @@ def read_id_from_ranges(f: BinaryIO, *ranges: Tuple[Optional[int], SomeID[ID_]],
 | 
				
			|||||||
		if num is not None and id > num: continue
 | 
							if num is not None and id > num: continue
 | 
				
			||||||
		try: return idCls(id)
 | 
							try: return idCls(id)
 | 
				
			||||||
		except ValueError: pass
 | 
							except ValueError: pass
 | 
				
			||||||
	assert False, f"Invalid {umbrellaIdCls.__name__}: {id}"
 | 
						assert False, f"Invalid {umbrellaIdCls.__name__}: {id} (hex: {repr_hex(id, 2)})"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def read_id(f: BinaryIO, idCls: SomeID[ID_], *, reader: Parser[int]=read_u16) -> ID_:
 | 
					def read_id(f: BinaryIO, idCls: SomeID[ID_], *, reader: Parser[int]=read_u16) -> ID_:
 | 
				
			||||||
	return read_id_from_ranges(f, (None, idCls), umbrellaIdCls=idCls, reader=reader)
 | 
						return read_id_from_ranges(f, (None, idCls), umbrellaIdCls=idCls, reader=reader)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SaveTell:
 | 
					 | 
				
			||||||
	def __init__(self, f: BinaryIO):
 | 
					 | 
				
			||||||
		self.f = f
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	def __enter__(self) -> int:
 | 
					 | 
				
			||||||
		self.tell = self.f.tell()
 | 
					 | 
				
			||||||
		return self.tell
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], exc_tb: Optional[TracebackType]) -> None:
 | 
					 | 
				
			||||||
		self.f.seek(self.tell)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# The following `parse_at_...` functions all move the BinaryIO away from wherever it was, so use `with SaveTell(f): ...` to save the tell
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def parse_at_offset(f: BinaryIO, start_tell: int, offset: int, parser: Parser[T], *, zero_is_null:bool=True) -> T:
 | 
					 | 
				
			||||||
	if zero_is_null: # Some tables have 0 being a valid offset
 | 
					 | 
				
			||||||
		assert offset, f"Offset was NULL"
 | 
					 | 
				
			||||||
	f.seek(start_tell+offset)
 | 
					 | 
				
			||||||
	return parser(f)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def parse_at_optional_offset(f: BinaryIO, start_tell: int, offset: Optional[int], parser: Parser[T], *, zero_is_null:bool=True) -> Optional[T]:
 | 
					 | 
				
			||||||
	if zero_is_null:
 | 
					 | 
				
			||||||
		if not offset: return None
 | 
					 | 
				
			||||||
	else:
 | 
					 | 
				
			||||||
		if offset is None: return None
 | 
					 | 
				
			||||||
	return parse_at_offset(f, start_tell, offset, parser, zero_is_null=zero_is_null)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def parse_at_offsets(f: BinaryIO, start_tell: int, offsets: List[int], parser: Parser[T], *, zero_is_null:bool=True) -> List[T]:
 | 
					 | 
				
			||||||
	return [parse_at_offset(f, start_tell, offset, parser, zero_is_null=zero_is_null) for offset in offsets]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def parse_at_optional_offsets(f: BinaryIO, start_tell: int, offsets: List[int], parser: Parser[T], *, zero_is_null:bool=True) -> List[Optional[T]]:
 | 
					 | 
				
			||||||
	return [parse_at_optional_offset(f, start_tell, offset, parser, zero_is_null=zero_is_null) for offset in offsets]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def parse_at_offsets_using_length(f: BinaryIO, start_tell: int, offsets: List[int], parser: Callable[[BinaryIO, int, int], T], *, zero_is_null:bool=True) -> List[Optional[T]]:
 | 
					 | 
				
			||||||
	"""
 | 
					 | 
				
			||||||
	The length of the returned list will be one less than that of `offsets`, as the last offset is used to calculate the length of the final element.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	`parser` is of the form `(f: BinaryIO, index: int, length: int) -> T`
 | 
					 | 
				
			||||||
	"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	elements: List[Optional[T]] = []
 | 
					 | 
				
			||||||
	for i, offset in enumerate(offsets[:-1]):
 | 
					 | 
				
			||||||
		length = offsets[i+1]-offset
 | 
					 | 
				
			||||||
		if length == 0:
 | 
					 | 
				
			||||||
			elements.append(None)
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		elements.append(parse_at_offset(f, start_tell, offset, lambda f: parser(f, i, length), zero_is_null=zero_is_null))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return elements
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def parse_list_at_offset(f: BinaryIO, start_tell: int, offset: int, count: int, parser: Parser[T], *, zero_is_null:bool=True) -> List[T]:
 | 
					 | 
				
			||||||
	return parse_at_offset(f, start_tell, offset, lambda f: [parser(f) for _ in range(count)], zero_is_null=zero_is_null)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def null_if_zero(n: int) -> Optional[int]: return n if n else None
 | 
					 | 
				
			||||||
def nulls_if_zero(ns: List[int]) -> List[Optional[int]]: return list(map(null_if_zero, ns))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class Table(ABD): pass
 | 
					class Table(ABD): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -165,9 +93,12 @@ class OffsetTable(Table):
 | 
				
			|||||||
	entrySelector: int
 | 
						entrySelector: int
 | 
				
			||||||
	rangeShift: int
 | 
						rangeShift: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def repr_hex(value: int, length: int=8) -> str:
 | 
				
			||||||
 | 
						return f"0x{hex(value)[2:]:0>{length}}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_offset_table(f: BinaryIO) -> OffsetTable:
 | 
					def parse_offset_table(f: BinaryIO) -> OffsetTable:
 | 
				
			||||||
	sfntVersion = read_u32(f)
 | 
						sfntVersion = read_u32(f)
 | 
				
			||||||
	assert sfntVersion in [0x00010000, 0x4F54544F], f"Invalid sfntVersion: 0x{hex(sfntVersion)[2:]:0>8}. Expected 0x00010000 or 0x4F54544F."
 | 
						assert sfntVersion in [0x00010000, 0x4F54544F], f"Invalid sfntVersion: {repr_hex(sfntVersion)}. Expected 0x00010000 or 0x4F54544F."
 | 
				
			||||||
	numTables = read_u16(f)
 | 
						numTables = read_u16(f)
 | 
				
			||||||
	searchRange = read_u16(f)
 | 
						searchRange = read_u16(f)
 | 
				
			||||||
	entrySelector = read_u16(f)
 | 
						entrySelector = read_u16(f)
 | 
				
			||||||
@ -175,7 +106,7 @@ def parse_offset_table(f: BinaryIO) -> OffsetTable:
 | 
				
			|||||||
	assert bin(searchRange).count('1') == 1 # ensure searchRange is a power of 2
 | 
						assert bin(searchRange).count('1') == 1 # ensure searchRange is a power of 2
 | 
				
			||||||
	assert searchRange//16 <= numTables < searchRange//8 # ensure searchRange//16 is largest power of two less than num_tables
 | 
						assert searchRange//16 <= numTables < searchRange//8 # ensure searchRange//16 is largest power of two less than num_tables
 | 
				
			||||||
	assert entrySelector == len(bin(searchRange))-7 # ensure entrySelector is the logarithm of searchRange//16
 | 
						assert entrySelector == len(bin(searchRange))-7 # ensure entrySelector is the logarithm of searchRange//16
 | 
				
			||||||
 | 
						assert rangeShift == numTables*16-searchRange
 | 
				
			||||||
	return OffsetTable(sfntVersion, numTables, searchRange, entrySelector, rangeShift)
 | 
						return OffsetTable(sfntVersion, numTables, searchRange, entrySelector, rangeShift)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TableTag(Enum):
 | 
					class TableTag(Enum):
 | 
				
			||||||
@ -197,7 +128,6 @@ class TableTag(Enum):
 | 
				
			|||||||
	Prep = 'prep'
 | 
						Prep = 'prep'
 | 
				
			||||||
	Gasp = 'gasp' # :O
 | 
						Gasp = 'gasp' # :O
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
	# 5.4 (CFF)
 | 
						# 5.4 (CFF)
 | 
				
			||||||
	...
 | 
						...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -208,7 +138,7 @@ class TableTag(Enum):
 | 
				
			|||||||
	# 5.6 (Optional)
 | 
						# 5.6 (Optional)
 | 
				
			||||||
	DSIG = 'DSIG'
 | 
						DSIG = 'DSIG'
 | 
				
			||||||
	Hdmx = 'hdmx'
 | 
						Hdmx = 'hdmx'
 | 
				
			||||||
	Kern = 'Kern'
 | 
						Kern = 'kern'
 | 
				
			||||||
	LTSH = 'LTSH'
 | 
						LTSH = 'LTSH'
 | 
				
			||||||
	PCLT = 'PCLT'
 | 
						PCLT = 'PCLT'
 | 
				
			||||||
	VDMX = 'VDMX'
 | 
						VDMX = 'VDMX'
 | 
				
			||||||
@ -254,119 +184,25 @@ def parse_table_directory_entry(f: BinaryIO) -> TableDirectoryEntry:
 | 
				
			|||||||
	length = read_u32(f)
 | 
						length = read_u32(f)
 | 
				
			||||||
	return TableDirectoryEntry(tableTag, checkSum, offset, length)
 | 
						return TableDirectoryEntry(tableTag, checkSum, offset, length)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO: This should just be a dict[TableTag, TableDirectoryEntry]
 | 
					 | 
				
			||||||
@dataclass
 | 
					 | 
				
			||||||
class FontDirectoryByTable:
 | 
					 | 
				
			||||||
	cmap: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	head: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	hhea: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	hmtx: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	maxp: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	name: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	os2 : Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	post: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cvt : Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	fpgm: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	glyf: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	loca: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	prep: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	gasp: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	svg : Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	DSIG: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	hdmx: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	Kern: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	LTSH: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	PCLT: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	VDMX: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	vhea: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	vmtx: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	COLR: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	CPAL: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	BASE: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	GDEF: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	GPOS: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	GSUB: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	JSTF: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	MATH: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	avar: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	cvar: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	fvar: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	gvar: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	HVAR: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	MVAR: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	STAT: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
	VVAR: Optional[TableDirectoryEntry] = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class FontDirectory:
 | 
					class FontDirectory:
 | 
				
			||||||
	offset_table: OffsetTable
 | 
						offset_table: OffsetTable
 | 
				
			||||||
	table_directory: List[TableDirectoryEntry]
 | 
						table_directory: List[TableDirectoryEntry]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	by_table: FontDirectoryByTable
 | 
						def get_entry(self, table_tag: TableTag) -> Optional[TableDirectoryEntry]:
 | 
				
			||||||
 | 
							for entry in self.table_directory:
 | 
				
			||||||
 | 
								if entry.tableTag == table_tag:
 | 
				
			||||||
 | 
									return entry
 | 
				
			||||||
 | 
							return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def has_entry(self, table_tag: TableTag) -> bool:
 | 
				
			||||||
 | 
							return self.get_entry(table_tag) is not None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_font_directory(f: BinaryIO) -> FontDirectory:
 | 
					def parse_font_directory(f: BinaryIO) -> FontDirectory:
 | 
				
			||||||
	offset_table = parse_offset_table(f)
 | 
						offset_table = parse_offset_table(f)
 | 
				
			||||||
	table_directory_entries = [parse_table_directory_entry(f) for _ in range(offset_table.numTables)]
 | 
						table_directory_entries = [parse_table_directory_entry(f) for _ in range(offset_table.numTables)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	by_table = FontDirectoryByTable()
 | 
						return FontDirectory(offset_table, table_directory_entries)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	for table_directory_entry in table_directory_entries:
 | 
					 | 
				
			||||||
		match table_directory_entry.tableTag:
 | 
					 | 
				
			||||||
			case TableTag.Cmap: by_table.cmap = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Head: by_table.head = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Hhea: by_table.hhea = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Hmtx: by_table.hmtx = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Maxp: by_table.maxp = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Name: by_table.name = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.OS2 : by_table.os2  = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Post: by_table.post = table_directory_entry
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			case TableTag.Cvt : by_table.cvt  = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Fpgm: by_table.fpgm = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Glyf: by_table.glyf = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Loca: by_table.loca = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Prep: by_table.prep = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Gasp: by_table.gasp = table_directory_entry
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			case TableTag.Svg : by_table.svg  = table_directory_entry
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			case TableTag.DSIG: by_table.DSIG = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Hdmx: by_table.hdmx = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Kern: by_table.Kern = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.LTSH: by_table.LTSH = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.PCLT: by_table.PCLT = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.VDMX: by_table.VDMX = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Vhea: by_table.vhea = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Vmtx: by_table.vmtx = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.COLR: by_table.COLR = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.CPAL: by_table.CPAL = table_directory_entry
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			case TableTag.BASE: by_table.BASE = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.GDEF: by_table.GDEF = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.GPOS: by_table.GPOS = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.GSUB: by_table.GSUB = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.JSTF: by_table.JSTF = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.MATH: by_table.MATH = table_directory_entry
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			case TableTag.Avar: by_table.avar = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Cvar: by_table.cvar = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Fvar: by_table.fvar = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.Gvar: by_table.gvar = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.HVAR: by_table.HVAR = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.MVAR: by_table.MVAR = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.STAT: by_table.STAT = table_directory_entry
 | 
					 | 
				
			||||||
			case TableTag.VVAR: by_table.VVAR = table_directory_entry
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			case _:
 | 
					 | 
				
			||||||
				assert False, f"Unimplemented: tableTag: {table_directory_entry.tableTag}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return FontDirectory(offset_table, table_directory_entries, by_table)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class CmapSubtable(Table, ABD):
 | 
					class CmapSubtable(Table, ABD):
 | 
				
			||||||
@ -390,7 +226,7 @@ def parse_sub_header(f: BinaryIO) -> SubHeader:
 | 
				
			|||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class CmapSubtable_Format_2(CmapSubtable):
 | 
					class CmapSubtable_Format_2(CmapSubtable):
 | 
				
			||||||
	length: int
 | 
						length: int
 | 
				
			||||||
	language: int
 | 
						language: int # TODO: Make this an optional int
 | 
				
			||||||
	subHeaderKeys: List[int] # 256 elements
 | 
						subHeaderKeys: List[int] # 256 elements
 | 
				
			||||||
	subHeaders: List[SubHeader]
 | 
						subHeaders: List[SubHeader]
 | 
				
			||||||
	glyphIndexArray: List[int]
 | 
						glyphIndexArray: List[int]
 | 
				
			||||||
@ -566,7 +402,9 @@ def parse_cmap_subtable(f: BinaryIO, platformID: PlatformID) -> CmapSubtable:
 | 
				
			|||||||
			entryCount = read_u16(f)
 | 
								entryCount = read_u16(f)
 | 
				
			||||||
			glyphIdArray = [read_u16(f) for _ in range(entryCount)]
 | 
								glyphIdArray = [read_u16(f) for _ in range(entryCount)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			assert f.tell()-start_tell == length, (f.tell()-start_tell, length)
 | 
								assert length-4<f.tell()-start_tell<=length, (f.tell()-start_tell, length)
 | 
				
			||||||
 | 
								f.seek(length-(f.tell()-start_tell))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return CmapSubtable_Format_6(format, length, language, firstCode, entryCount, glyphIdArray)
 | 
								return CmapSubtable_Format_6(format, length, language, firstCode, entryCount, glyphIdArray)
 | 
				
			||||||
		case 12:
 | 
							case 12:
 | 
				
			||||||
			assert read_u16(f) == 0, "Reserved"
 | 
								assert read_u16(f) == 0, "Reserved"
 | 
				
			||||||
@ -643,6 +481,7 @@ def encoding_ID_cls_from_platform_ID(platformID: PlatformID) -> Callable[[int],
 | 
				
			|||||||
def parse_encoding_ID(f: BinaryIO, platformID: PlatformID) -> EncodingID:
 | 
					def parse_encoding_ID(f: BinaryIO, platformID: PlatformID) -> EncodingID:
 | 
				
			||||||
	return read_id(f, encoding_ID_cls_from_platform_ID(platformID))
 | 
						return read_id(f, encoding_ID_cls_from_platform_ID(platformID))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TODO: Finish this
 | 
				
			||||||
def parse_string_with_encoding_ID(f: BinaryIO, length: int, encodingID: EncodingID) -> str:
 | 
					def parse_string_with_encoding_ID(f: BinaryIO, length: int, encodingID: EncodingID) -> str:
 | 
				
			||||||
	bytes = f.read(length)
 | 
						bytes = f.read(length)
 | 
				
			||||||
	match encodingID:
 | 
						match encodingID:
 | 
				
			||||||
@ -687,6 +526,7 @@ def parse_cmap_table(f: BinaryIO) -> CmapTable:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return CmapTable(version, numTables, encodingRecords)
 | 
						return CmapTable(version, numTables, encodingRecords)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HEAD_TABLE_MAGIC = 0x5F0F3CF5
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class HeadTable(Table):
 | 
					class HeadTable(Table):
 | 
				
			||||||
	majorVersion: int
 | 
						majorVersion: int
 | 
				
			||||||
@ -715,7 +555,7 @@ def parse_head_table(f: BinaryIO) -> HeadTable:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	fontRevision = read_fixed(f)
 | 
						fontRevision = read_fixed(f)
 | 
				
			||||||
	checkSumAdjustment = read_u32(f)
 | 
						checkSumAdjustment = read_u32(f)
 | 
				
			||||||
	assert read_u32(f) == 0x5F0F3CF5, "magicNumber"
 | 
						assert read_u32(f) == HEAD_TABLE_MAGIC, "magicNumber"
 | 
				
			||||||
	flags = read_u16(f)
 | 
						flags = read_u16(f)
 | 
				
			||||||
	unitsPerEm = read_u16(f)
 | 
						unitsPerEm = read_u16(f)
 | 
				
			||||||
	created = read_long_datetime(f)
 | 
						created = read_long_datetime(f)
 | 
				
			||||||
@ -867,6 +707,123 @@ class LanguageID(ABE): pass
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class MacintoshLanguageID(LanguageID, Enum):
 | 
					class MacintoshLanguageID(LanguageID, Enum):
 | 
				
			||||||
	English = 0
 | 
						English = 0
 | 
				
			||||||
 | 
						French = 1
 | 
				
			||||||
 | 
						German = 2
 | 
				
			||||||
 | 
						Italian = 3
 | 
				
			||||||
 | 
						Dutch = 4
 | 
				
			||||||
 | 
						Swedish = 5
 | 
				
			||||||
 | 
						Spanish = 6
 | 
				
			||||||
 | 
						Danish = 7
 | 
				
			||||||
 | 
						Portuguese = 8
 | 
				
			||||||
 | 
						Norwegian = 9
 | 
				
			||||||
 | 
						Hebrew = 10
 | 
				
			||||||
 | 
						Japanese = 11
 | 
				
			||||||
 | 
						Arabic = 12
 | 
				
			||||||
 | 
						Finnish = 13
 | 
				
			||||||
 | 
						Greek = 14
 | 
				
			||||||
 | 
						Icelandic = 15
 | 
				
			||||||
 | 
						Maltese = 16
 | 
				
			||||||
 | 
						Turkish = 17
 | 
				
			||||||
 | 
						Croatian = 18
 | 
				
			||||||
 | 
						Chinese_Traditional = 19
 | 
				
			||||||
 | 
						Urdu = 20
 | 
				
			||||||
 | 
						Hindi = 21
 | 
				
			||||||
 | 
						Thai = 22
 | 
				
			||||||
 | 
						Korean = 23
 | 
				
			||||||
 | 
						Lithuanian = 24
 | 
				
			||||||
 | 
						Polish = 25
 | 
				
			||||||
 | 
						Hungarian = 26
 | 
				
			||||||
 | 
						Estonian = 27
 | 
				
			||||||
 | 
						Latvian = 28
 | 
				
			||||||
 | 
						Sami = 29
 | 
				
			||||||
 | 
						Faroese = 30
 | 
				
			||||||
 | 
						FarsiPersian  = 31
 | 
				
			||||||
 | 
						Russian = 32
 | 
				
			||||||
 | 
						Chinese_Simplified = 33
 | 
				
			||||||
 | 
						Flemish = 34
 | 
				
			||||||
 | 
						IrishGaelic = 35
 | 
				
			||||||
 | 
						Albanian = 36
 | 
				
			||||||
 | 
						Romanian = 37
 | 
				
			||||||
 | 
						Czech = 38
 | 
				
			||||||
 | 
						Slovak = 39
 | 
				
			||||||
 | 
						Slovenian = 40
 | 
				
			||||||
 | 
						Yiddish = 41
 | 
				
			||||||
 | 
						Serbian = 42
 | 
				
			||||||
 | 
						Macedonian = 43
 | 
				
			||||||
 | 
						Bulgarian = 44
 | 
				
			||||||
 | 
						Ukrainian = 45
 | 
				
			||||||
 | 
						Byelorussian = 46
 | 
				
			||||||
 | 
						Uzbek = 47
 | 
				
			||||||
 | 
						Kazakh = 48
 | 
				
			||||||
 | 
						Azerbaijani_CyrillicScript = 49
 | 
				
			||||||
 | 
						Azerbaijani_ArabicScript = 50
 | 
				
			||||||
 | 
						Armenian = 51
 | 
				
			||||||
 | 
						Georgian = 52
 | 
				
			||||||
 | 
						Moldavian = 53
 | 
				
			||||||
 | 
						Kirghiz = 54
 | 
				
			||||||
 | 
						Tajiki = 55
 | 
				
			||||||
 | 
						Turkmen = 56
 | 
				
			||||||
 | 
						Mongolian_MongolianScript = 57
 | 
				
			||||||
 | 
						Mongolian_CyrillicScript = 58
 | 
				
			||||||
 | 
						Pashto = 59
 | 
				
			||||||
 | 
						Kurdish = 60
 | 
				
			||||||
 | 
						Kashmiri = 61
 | 
				
			||||||
 | 
						Sindhi = 62
 | 
				
			||||||
 | 
						Tibetan = 63
 | 
				
			||||||
 | 
						Nepali = 64
 | 
				
			||||||
 | 
						Sanskrit = 65
 | 
				
			||||||
 | 
						Marathi = 66
 | 
				
			||||||
 | 
						Bengali = 67
 | 
				
			||||||
 | 
						Assamese = 68
 | 
				
			||||||
 | 
						Gujarati = 69
 | 
				
			||||||
 | 
						Punjabi = 70
 | 
				
			||||||
 | 
						Oriya = 71
 | 
				
			||||||
 | 
						Malayalam = 72
 | 
				
			||||||
 | 
						Kannada = 73
 | 
				
			||||||
 | 
						Tamil = 74
 | 
				
			||||||
 | 
						Telugu = 75
 | 
				
			||||||
 | 
						Sinhalese = 76
 | 
				
			||||||
 | 
						Burmese = 77
 | 
				
			||||||
 | 
						Khmer = 78
 | 
				
			||||||
 | 
						Lao = 79
 | 
				
			||||||
 | 
						Vietnamese = 80
 | 
				
			||||||
 | 
						Indonesian = 81
 | 
				
			||||||
 | 
						Tagalong = 82
 | 
				
			||||||
 | 
						Malay_RomanScript = 83
 | 
				
			||||||
 | 
						Malay_ArabicScript = 84
 | 
				
			||||||
 | 
						Amharic = 85
 | 
				
			||||||
 | 
						Tigrinya = 86
 | 
				
			||||||
 | 
						Galla = 87
 | 
				
			||||||
 | 
						Somali = 88
 | 
				
			||||||
 | 
						Swahili = 89
 | 
				
			||||||
 | 
						Rundi = 91
 | 
				
			||||||
 | 
						KinyarwandaRuanda = 90
 | 
				
			||||||
 | 
						NyanjaChewa = 92
 | 
				
			||||||
 | 
						Malagasy = 93
 | 
				
			||||||
 | 
						Esperanto = 94
 | 
				
			||||||
 | 
						Welsh = 128
 | 
				
			||||||
 | 
						Basque = 129
 | 
				
			||||||
 | 
						Catalan = 130
 | 
				
			||||||
 | 
						Latin = 131
 | 
				
			||||||
 | 
						Quenchua = 132
 | 
				
			||||||
 | 
						Guarani = 133
 | 
				
			||||||
 | 
						Aymara = 134
 | 
				
			||||||
 | 
						Tatar = 135
 | 
				
			||||||
 | 
						Uighur = 136
 | 
				
			||||||
 | 
						Dzongkha = 137
 | 
				
			||||||
 | 
						Javanese_RomanScript = 138
 | 
				
			||||||
 | 
						Sundanese_RomanScript = 139
 | 
				
			||||||
 | 
						Galician = 140
 | 
				
			||||||
 | 
						Afrikaans = 141
 | 
				
			||||||
 | 
						Breton = 142
 | 
				
			||||||
 | 
						Inuktitut = 143
 | 
				
			||||||
 | 
						ScottishGaelic = 144
 | 
				
			||||||
 | 
						ManxGaelic = 145
 | 
				
			||||||
 | 
						IrishGaelic_WithDotAbove = 146
 | 
				
			||||||
 | 
						Tongan = 147
 | 
				
			||||||
 | 
						Greek_Polytonic = 148
 | 
				
			||||||
 | 
						Greenlandic = 149
 | 
				
			||||||
 | 
						Azerbaijani_RomanScript = 150
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def __str__(self) -> str: return self._name_
 | 
						def __str__(self) -> str: return self._name_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1175,7 +1132,7 @@ def parse_name_table(f: BinaryIO, length: int) -> NameTable:
 | 
				
			|||||||
		case _:
 | 
							case _:
 | 
				
			||||||
			assert False, f"Unimplemented: format: {format}"
 | 
								assert False, f"Unimplemented: format: {format}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert False
 | 
						assert False, format
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class VendorTag:
 | 
					class VendorTag:
 | 
				
			||||||
@ -1218,6 +1175,10 @@ class OS2Table(Table, ABD):
 | 
				
			|||||||
	usWinAscent: int
 | 
						usWinAscent: int
 | 
				
			||||||
	usWinDescent: int
 | 
						usWinDescent: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							if self.fsSelection & 0x40: assert self.fsSelection & (0b100001) == 0 # bit 6 indicates that the font is regular, and therefore cannot be bold or italic.
 | 
				
			||||||
 | 
							assert self.fsSelection & 0xfc00 == 0, "reserved"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class OS2Table_Ver_0(OS2Table): pass
 | 
					class OS2Table_Ver_0(OS2Table): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1263,9 +1224,6 @@ def parse_OS2_table(f: BinaryIO) -> OS2Table:
 | 
				
			|||||||
	sTypoAscender, sTypoDescender, sTypoLineGap = read_i16(f), read_i16(f), read_i16(f)
 | 
						sTypoAscender, sTypoDescender, sTypoLineGap = read_i16(f), read_i16(f), read_i16(f)
 | 
				
			||||||
	usWinAscent, usWinDescent = read_u16(f), read_u16(f)
 | 
						usWinAscent, usWinDescent = read_u16(f), read_u16(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if fsSelection & 0x40: assert fsSelection & (0b100001) == 0 # bit 6 indicates that the font is regular, and therefore cannot be bold or italic.
 | 
					 | 
				
			||||||
	assert fsSelection & 0xfc00 == 0, "reserved"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if version == 0:
 | 
						if version == 0:
 | 
				
			||||||
		return OS2Table_Ver_0(version, xAvgCharWidth, usWeightClass, usWidthClass, fsType, ySubscriptXSize, ySubscriptYSize, ySubscriptXOffset, ySubscriptYOffset, ySuperscriptXSize, ySuperscriptYSize, ySuperscriptXOffset, ySuperscriptYOffset, yStrikeoutSize, yStrikeoutPosition, sFamilyClass, panose, ulUnicodeRange1, ulUnicodeRange2, ulUnicodeRange3, ulUnicodeRange4, achVendID, fsSelection, usFirstCharIndex, usLastCharIndex, sTypoAscender, sTypoDescender, sTypoLineGap, usWinAscent, usWinDescent)
 | 
							return OS2Table_Ver_0(version, xAvgCharWidth, usWeightClass, usWidthClass, fsType, ySubscriptXSize, ySubscriptYSize, ySubscriptXOffset, ySubscriptYOffset, ySuperscriptXSize, ySuperscriptYSize, ySuperscriptXOffset, ySuperscriptYOffset, yStrikeoutSize, yStrikeoutPosition, sFamilyClass, panose, ulUnicodeRange1, ulUnicodeRange2, ulUnicodeRange3, ulUnicodeRange4, achVendID, fsSelection, usFirstCharIndex, usLastCharIndex, sTypoAscender, sTypoDescender, sTypoLineGap, usWinAscent, usWinDescent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1346,6 +1304,8 @@ def parse_post_table(f: BinaryIO, length: int) -> PostTable:
 | 
				
			|||||||
			while length - (f.tell() - start_tell) > 0: # kinda dangerous, but I think it's the only way to make it work? number of strings is not necessarily equal to numGlyphs. I think that you could probably figure out the number of strings by filtering elements in the glyphNameIndex array
 | 
								while length - (f.tell() - start_tell) > 0: # kinda dangerous, but I think it's the only way to make it work? number of strings is not necessarily equal to numGlyphs. I think that you could probably figure out the number of strings by filtering elements in the glyphNameIndex array
 | 
				
			||||||
				names.append(read_pascal_string(f))
 | 
									names.append(read_pascal_string(f))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								assert f.tell()-start_tell == length
 | 
				
			||||||
 | 
								
 | 
				
			||||||
			return PostTable_Ver_2_0(version, italicAngle, underlinePosition, underlineThickness, isFixedPitch, minMemType42, maxMemType42, minMemType1, maxMemType1, numGlyphs, glyphNameIndex, names)
 | 
								return PostTable_Ver_2_0(version, italicAngle, underlinePosition, underlineThickness, isFixedPitch, minMemType42, maxMemType42, minMemType1, maxMemType1, numGlyphs, glyphNameIndex, names)
 | 
				
			||||||
		case 3.0:
 | 
							case 3.0:
 | 
				
			||||||
			return PostTable_Ver_3_0(version, italicAngle, underlinePosition, underlineThickness, isFixedPitch, minMemType42, maxMemType42, minMemType1, maxMemType1)
 | 
								return PostTable_Ver_3_0(version, italicAngle, underlinePosition, underlineThickness, isFixedPitch, minMemType42, maxMemType42, minMemType1, maxMemType1)
 | 
				
			||||||
@ -1414,6 +1374,9 @@ class DSIGTable(Table, ABD):
 | 
				
			|||||||
	flags: int # It's u16 but only bits 0-7 are documented?
 | 
						flags: int # It's u16 but only bits 0-7 are documented?
 | 
				
			||||||
	signatureRecords: List[SignatureRecord] # there's a typo in the ISO documentation.
 | 
						signatureRecords: List[SignatureRecord] # there's a typo in the ISO documentation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							assert self.flags & 0xfe == 0, "Reserved"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class DSIGTable_Ver_1(DSIGTable): pass
 | 
					class DSIGTable_Ver_1(DSIGTable): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1425,7 +1388,6 @@ def parse_DSIG_table(f: BinaryIO) -> DSIGTable:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	numSignatures = read_u16(f)
 | 
						numSignatures = read_u16(f)
 | 
				
			||||||
	flags = read_u16(f)
 | 
						flags = read_u16(f)
 | 
				
			||||||
	assert flags & 0xfe == 0, "Reserved"
 | 
					 | 
				
			||||||
	signatureRecords = [parse_signature_record(f, start_tell) for _ in range(numSignatures)]
 | 
						signatureRecords = [parse_signature_record(f, start_tell) for _ in range(numSignatures)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if version == 1:
 | 
						if version == 1:
 | 
				
			||||||
@ -1678,14 +1640,18 @@ class CCXXTag(ABE):
 | 
				
			|||||||
		self.num = int(tag[2:])
 | 
							self.num = int(tag[2:])
 | 
				
			||||||
		if not (self.__range__[0] <= self.num <= self.__range__[1]): raise ValueError(f"Invalid {self.__class__.__name__}: {self.num}. Expected number between {self.__range__[0]} and {self.__range__[1]}.")
 | 
							if not (self.__range__[0] <= self.num <= self.__range__[1]): raise ValueError(f"Invalid {self.__class__.__name__}: {self.num}. Expected number between {self.__range__[0]} and {self.__range__[1]}.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@classmethod
 | 
						# @classmethod
 | 
				
			||||||
	def from_num(cls, num: int) -> Self:
 | 
						# def from_num(cls, num: int) -> Self:
 | 
				
			||||||
		assert 0 <= num <= 99, f"Invalid num: {num}. Must be two digits"
 | 
						# 	assert 0 <= num <= 99, f"Invalid num: {num}. Must be two digits"
 | 
				
			||||||
		return cls(f"{cls.__CC__}{num:0>2}") # don't need to check the range because the __init__ will check
 | 
						# 	return cls(f"{cls.__CC__}{num:0>2}") # don't need to check the range because the __init__ will check
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	def __str__(self) -> str:
 | 
						def __str__(self) -> str:
 | 
				
			||||||
		return f"'{self.__CC__}{self.num:0>2}'"
 | 
							return f"'{self.__CC__}{self.num:0>2}'"
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						@property
 | 
				
			||||||
 | 
						def name(self) -> str:
 | 
				
			||||||
 | 
							return str(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ScriptTag(Enum):
 | 
					class ScriptTag(Enum):
 | 
				
			||||||
	Adlam = 'adlm'
 | 
						Adlam = 'adlm'
 | 
				
			||||||
	Ahom = 'ahom'
 | 
						Ahom = 'ahom'
 | 
				
			||||||
@ -2475,8 +2441,12 @@ class ValidLangSysTag(LangSysTag, Enum):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	def __str__(self) -> str: return self._name_
 | 
						def __str__(self) -> str: return self._name_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@dataclass
 | 
				
			||||||
 | 
					class InvalidLangSysTag(LangSysTag):
 | 
				
			||||||
 | 
						tag: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_lang_sys_tag(f: BinaryIO) -> LangSysTag:
 | 
					def parse_lang_sys_tag(f: BinaryIO) -> LangSysTag:
 | 
				
			||||||
	return read_tag_from_tags(f, ValidLangSysTag, MS_VOLT_Tag, umbrellaTagCls=LangSysTag)
 | 
						return read_tag_from_tags(f, ValidLangSysTag, InvalidLangSysTag, MS_VOLT_Tag, umbrellaTagCls=LangSysTag)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class LangSysRecord:
 | 
					class LangSysRecord:
 | 
				
			||||||
@ -2682,8 +2652,18 @@ class MS_VOLT_Tag(CCXXTag, LangSysTag, FeatureTag):
 | 
				
			|||||||
	__CC__ = 'zz'
 | 
						__CC__ = 'zz'
 | 
				
			||||||
	__range__ = (0, 99) # I don't know if zz00 is valid or not, but I am letting it be, so that it can be caught, because zzXX is not a valid tag for anything
 | 
						__range__ = (0, 99) # I don't know if zz00 is valid or not, but I am letting it be, so that it can be caught, because zzXX is not a valid tag for anything
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def is_vendor_feature_tag(tag: str) -> bool:
 | 
				
			||||||
 | 
						return all(map(is_upper, tag))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@dataclass
 | 
				
			||||||
 | 
					class VendorFeatureTag(FeatureTag):
 | 
				
			||||||
 | 
						tag: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							if not is_vendor_feature_tag(self.tag): raise ValueError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_feature_tag(f: BinaryIO) -> FeatureTag:
 | 
					def parse_feature_tag(f: BinaryIO) -> FeatureTag:
 | 
				
			||||||
	return read_tag_from_tags(f, SimpleFeatureTag, CvXXFeatureTag, SsXXFeatureTag, MS_VOLT_Tag, umbrellaTagCls=FeatureTag)
 | 
						return read_tag_with_conditions(f, (always, SimpleFeatureTag), (always, CvXXFeatureTag), (always, SsXXFeatureTag), (is_vendor_feature_tag, VendorFeatureTag), (lambda s: is_CCXX('zz', s), MS_VOLT_Tag), umbrellaTagCls=FeatureTag)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class FeatureParamsTable(Table, ABD): pass
 | 
					class FeatureParamsTable(Table, ABD): pass
 | 
				
			||||||
@ -2828,7 +2808,7 @@ class LookupFlag: # TODO: Do this like the other flags
 | 
				
			|||||||
def parse_lookup_flag(f: BinaryIO) -> LookupFlag:
 | 
					def parse_lookup_flag(f: BinaryIO) -> LookupFlag:
 | 
				
			||||||
	lookupFlag = read_u16(f)
 | 
						lookupFlag = read_u16(f)
 | 
				
			||||||
	rightToLeft, ignoreBaseGlyphs, ignoreLigatures, ignoreMarks, useMarkFilteringSet = [bool(lookupFlag&(1<<i)) for i in range(5)]
 | 
						rightToLeft, ignoreBaseGlyphs, ignoreLigatures, ignoreMarks, useMarkFilteringSet = [bool(lookupFlag&(1<<i)) for i in range(5)]
 | 
				
			||||||
	assert lookupFlag & 0x00e0 == 0, "Reserved"
 | 
						assert lookupFlag & 0x00e0 == 0, "Reserved" # TODO: Once you do this like the other flags, put this in the __post_init__
 | 
				
			||||||
	markAttachmentType = (lookupFlag & 0xff00) >> 8
 | 
						markAttachmentType = (lookupFlag & 0xff00) >> 8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return LookupFlag(rightToLeft, ignoreBaseGlyphs, ignoreLigatures, ignoreMarks, useMarkFilteringSet, markAttachmentType)
 | 
						return LookupFlag(rightToLeft, ignoreBaseGlyphs, ignoreLigatures, ignoreMarks, useMarkFilteringSet, markAttachmentType)
 | 
				
			||||||
@ -3091,9 +3071,11 @@ class ValueFormatFlags:
 | 
				
			|||||||
	def x_advance_device(self) -> bool: 	return (self.bytes & 0x0040)!=0
 | 
						def x_advance_device(self) -> bool: 	return (self.bytes & 0x0040)!=0
 | 
				
			||||||
	def y_advance_device(self) -> bool: 	return (self.bytes & 0x0080)!=0
 | 
						def y_advance_device(self) -> bool: 	return (self.bytes & 0x0080)!=0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							assert self.bytes & 0xFF00 == 0, "Reserved"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_value_format(f: BinaryIO) -> ValueFormatFlags:
 | 
					def parse_value_format(f: BinaryIO) -> ValueFormatFlags:
 | 
				
			||||||
	valueFormat = read_u16(f)
 | 
						valueFormat = read_u16(f)
 | 
				
			||||||
	assert valueFormat & 0xFF00 == 0, "Reserved"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return ValueFormatFlags(valueFormat)
 | 
						return ValueFormatFlags(valueFormat)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -3104,12 +3086,18 @@ class DeviceTable(Table):
 | 
				
			|||||||
	deltaFormat: int
 | 
						deltaFormat: int
 | 
				
			||||||
	deltaValue: List[int]
 | 
						deltaValue: List[int]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							assert self.deltaFormat & 0x7ffc == 0, "Reserved"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class VariationIndexTable(Table):
 | 
					class VariationIndexTable(Table):
 | 
				
			||||||
	deltaSetOuterIndex: int
 | 
						deltaSetOuterIndex: int
 | 
				
			||||||
	deltaSetInnerIndex: int
 | 
						deltaSetInnerIndex: int
 | 
				
			||||||
	deltaFormat: int
 | 
						deltaFormat: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							assert self.deltaFormat == 0x8000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DeviceTable_ = DeviceTable | VariationIndexTable
 | 
					DeviceTable_ = DeviceTable | VariationIndexTable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_device_table(f: BinaryIO) -> DeviceTable_:
 | 
					def parse_device_table(f: BinaryIO) -> DeviceTable_:
 | 
				
			||||||
@ -3117,7 +3105,6 @@ def parse_device_table(f: BinaryIO) -> DeviceTable_:
 | 
				
			|||||||
	second = read_u16(f)
 | 
						second = read_u16(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deltaFormat = read_u16(f)
 | 
						deltaFormat = read_u16(f)
 | 
				
			||||||
	assert deltaFormat & 0x7ffc == 0, "Reserved"
 | 
					 | 
				
			||||||
	assert deltaFormat in [1, 2, 3, 0x8000], f"Invalid deltaFormat: {deltaFormat}"
 | 
						assert deltaFormat in [1, 2, 3, 0x8000], f"Invalid deltaFormat: {deltaFormat}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	match deltaFormat:
 | 
						match deltaFormat:
 | 
				
			||||||
@ -3476,10 +3463,10 @@ def parse_value_record(f: BinaryIO, start_tell: int, valueFormat: ValueFormatFla
 | 
				
			|||||||
	xAdvance = read_i16(f) if valueFormat.x_advance() else None
 | 
						xAdvance = read_i16(f) if valueFormat.x_advance() else None
 | 
				
			||||||
	yAdvance = read_i16(f) if valueFormat.y_advance() else None
 | 
						yAdvance = read_i16(f) if valueFormat.y_advance() else None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	xPlaDeviceOffset = read_u16(f) if valueFormat.x_placement_device() else None
 | 
						xPlaDeviceOffset = read_u16(f) if valueFormat.x_placement_device() else 0
 | 
				
			||||||
	yPlaDeviceOffset = read_u16(f) if valueFormat.y_placement_device() else None
 | 
						yPlaDeviceOffset = read_u16(f) if valueFormat.y_placement_device() else 0
 | 
				
			||||||
	xAdvDeviceOffset = read_u16(f) if valueFormat.x_advance_device() else None
 | 
						xAdvDeviceOffset = read_u16(f) if valueFormat.x_advance_device() else 0
 | 
				
			||||||
	yAdvDeviceOffset = read_u16(f) if valueFormat.y_advance_device() else None
 | 
						yAdvDeviceOffset = read_u16(f) if valueFormat.y_advance_device() else 0
 | 
				
			||||||
	with SaveTell(f):
 | 
						with SaveTell(f):
 | 
				
			||||||
		xPlaDevice = parse_at_optional_offset(f, start_tell, xPlaDeviceOffset, parse_device_table)
 | 
							xPlaDevice = parse_at_optional_offset(f, start_tell, xPlaDeviceOffset, parse_device_table)
 | 
				
			||||||
		yPlaDevice = parse_at_optional_offset(f, start_tell, yPlaDeviceOffset, parse_device_table)
 | 
							yPlaDevice = parse_at_optional_offset(f, start_tell, yPlaDeviceOffset, parse_device_table)
 | 
				
			||||||
@ -4222,7 +4209,7 @@ def parse_GPOS_table(f: BinaryIO) -> GPOSTable:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	featureVariationsOffset = read_u32(f)
 | 
						featureVariationsOffset = read_u32(f)
 | 
				
			||||||
	with SaveTell(f):
 | 
						with SaveTell(f):
 | 
				
			||||||
		featureVariations = parse_at_optional_offset(f, start_tell, featureVariationsOffset, lambda f: parse_feature_variations_table(f, featureList))
 | 
							featureVariations = parse_at_offset(f, start_tell, featureVariationsOffset, lambda f: parse_feature_variations_table(f, featureList))
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	if minorVersion == 1:
 | 
						if minorVersion == 1:
 | 
				
			||||||
		return GPOSTable_Ver_1_1(majorVersion, minorVersion, scriptList, featureList, lookupList, featureVariations)
 | 
							return GPOSTable_Ver_1_1(majorVersion, minorVersion, scriptList, featureList, lookupList, featureVariations)
 | 
				
			||||||
@ -4616,7 +4603,7 @@ def parse_GSUB_lookup_subtable(f: BinaryIO, lookupType: GSUBLookupType) -> GSUBL
 | 
				
			|||||||
					ligatureSetOffsets = [read_u16(f) for _ in range(ligatureSetCount)]
 | 
										ligatureSetOffsets = [read_u16(f) for _ in range(ligatureSetCount)]
 | 
				
			||||||
					with SaveTell(f):
 | 
										with SaveTell(f):
 | 
				
			||||||
						coverage = parse_at_offset(f, start_tell, coverageOffset, parse_coverage_table)
 | 
											coverage = parse_at_offset(f, start_tell, coverageOffset, parse_coverage_table)
 | 
				
			||||||
						ligatureSets = parse_at_offsets(f, start_tell, ligatureSetOffsets, lambda f: parse_set_table(f, parse_ligature_table))
 | 
											ligatureSets = parse_at_offsets(f, start_tell, ligatureSetOffsets, parse_ligature_set_table)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					return LigatureSubstSubtable_Format_1(substFormat, coverage, ligatureSetCount, ligatureSets)
 | 
										return LigatureSubstSubtable_Format_1(substFormat, coverage, ligatureSetCount, ligatureSets)
 | 
				
			||||||
				case _:
 | 
									case _:
 | 
				
			||||||
@ -4764,7 +4751,7 @@ def parse_GSUB_lookup_subtable(f: BinaryIO, lookupType: GSUBLookupType) -> GSUBL
 | 
				
			|||||||
	assert False, lookupType
 | 
						assert False, lookupType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class GSUBTable(Table, ABD):
 | 
					class GSUBTable(Table, ABD): # TODO: Maybe make a generic class for this, because this is the same as GPOSTable
 | 
				
			||||||
	majorVersion: int
 | 
						majorVersion: int
 | 
				
			||||||
	minorVersion: int
 | 
						minorVersion: int
 | 
				
			||||||
	# See: https://github.com/MicrosoftDocs/typography-issues/issues/79
 | 
						# See: https://github.com/MicrosoftDocs/typography-issues/issues/79
 | 
				
			||||||
@ -4869,12 +4856,15 @@ class SimpleGlyphFlag:
 | 
				
			|||||||
	def y_is_same_or_positive_short(self) -> bool: 	return (self.byte & 0x20)!=0
 | 
						def y_is_same_or_positive_short(self) -> bool: 	return (self.byte & 0x20)!=0
 | 
				
			||||||
	def overlap_simple(self) -> bool: 				return (self.byte & 0x40)!=0
 | 
						def overlap_simple(self) -> bool: 				return (self.byte & 0x40)!=0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def __str__(self) -> str:
 | 
						def __repr__(self) -> str:
 | 
				
			||||||
		return '0x'+hex(self.byte)[2:].rjust(2, '0')
 | 
							return repr_hex(self.byte, 2)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							assert self.byte & 0x80 == 0, self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_simple_glyph_flag(f: BinaryIO) -> SimpleGlyphFlag:
 | 
					def parse_simple_glyph_flag(f: BinaryIO) -> SimpleGlyphFlag:
 | 
				
			||||||
	flags = read_u8(f)
 | 
						flags = read_u8(f)
 | 
				
			||||||
	assert flags & 0x80 == 0, "reserved"
 | 
					
 | 
				
			||||||
	return SimpleGlyphFlag(flags)
 | 
						return SimpleGlyphFlag(flags)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
@ -4887,27 +4877,30 @@ class SimpleGlyph(Glyph):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class CompoundGlyphFlag:
 | 
					class CompoundGlyphFlag:
 | 
				
			||||||
	byte: int
 | 
						bytes: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def arg_1_and_2_are_words(self) -> bool: 		return (self.byte & 0x0001)!=0
 | 
						def arg_1_and_2_are_words(self) -> bool: 		return (self.bytes & 0x0001)!=0
 | 
				
			||||||
	def args_are_xy_values(self) -> bool: 			return (self.byte & 0x0002)!=0
 | 
						def args_are_xy_values(self) -> bool: 			return (self.bytes & 0x0002)!=0
 | 
				
			||||||
	def round_xy_to_grid(self) -> bool: 			return (self.byte & 0x0004)!=0
 | 
						def round_xy_to_grid(self) -> bool: 			return (self.bytes & 0x0004)!=0
 | 
				
			||||||
	def we_have_a_scale(self) -> bool: 				return (self.byte & 0x0008)!=0
 | 
						def we_have_a_scale(self) -> bool: 				return (self.bytes & 0x0008)!=0
 | 
				
			||||||
	def more_components(self) -> bool: 				return (self.byte & 0x0020)!=0
 | 
						def more_components(self) -> bool: 				return (self.bytes & 0x0020)!=0
 | 
				
			||||||
	def we_have_an_x_and_y_scale(self) -> bool: 	return (self.byte & 0x0040)!=0
 | 
						def we_have_an_x_and_y_scale(self) -> bool: 	return (self.bytes & 0x0040)!=0
 | 
				
			||||||
	def we_have_a_two_by_two(self) -> bool: 		return (self.byte & 0x0080)!=0
 | 
						def we_have_a_two_by_two(self) -> bool: 		return (self.bytes & 0x0080)!=0
 | 
				
			||||||
	def we_have_instructions(self) -> bool: 		return (self.byte & 0x0100)!=0
 | 
						def we_have_instructions(self) -> bool: 		return (self.bytes & 0x0100)!=0
 | 
				
			||||||
	def use_my_metrics(self) -> bool: 				return (self.byte & 0x0200)!=0
 | 
						def use_my_metrics(self) -> bool: 				return (self.bytes & 0x0200)!=0
 | 
				
			||||||
	def overlap_compound(self) -> bool: 			return (self.byte & 0x0400)!=0
 | 
						def overlap_compound(self) -> bool: 			return (self.bytes & 0x0400)!=0
 | 
				
			||||||
	def scaled_component_offset(self) -> bool: 		return (self.byte & 0x0800)!=0
 | 
						def scaled_component_offset(self) -> bool: 		return (self.bytes & 0x0800)!=0
 | 
				
			||||||
	def unscaled_component_offset(self) -> bool: 	return (self.byte & 0x1000)!=0
 | 
						def unscaled_component_offset(self) -> bool: 	return (self.bytes & 0x1000)!=0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def __repr__(self) -> str:
 | 
						def __repr__(self) -> str:
 | 
				
			||||||
		return '0x'+hex(self.byte)[2:].rjust(4, '0')
 | 
							return repr_hex(self.bytes, 4)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							assert self.bytes & 0xE010 == 0, "Reserved"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_compound_glyph_flag(f: BinaryIO) -> CompoundGlyphFlag:
 | 
					def parse_compound_glyph_flag(f: BinaryIO) -> CompoundGlyphFlag:
 | 
				
			||||||
	flags = read_u16(f)
 | 
						flags = read_u16(f)
 | 
				
			||||||
	assert flags & 0xE010 == 0, "reserved"
 | 
					
 | 
				
			||||||
	return CompoundGlyphFlag(flags)
 | 
						return CompoundGlyphFlag(flags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
@ -4977,7 +4970,6 @@ def parse_simple_glyph_flags(f: BinaryIO, total_points: int) -> Tuple[List[Simpl
 | 
				
			|||||||
	Returns the logical list (i.e., expanding repeating flags), as well as the total xCoordinates and yCoordinates lengths
 | 
						Returns the logical list (i.e., expanding repeating flags), as well as the total xCoordinates and yCoordinates lengths
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	# Mostly just ported from https://github.com/RazrFalcon/ttf-parser/blob/2192d3e496201ab2ff39d5437f88d62e70083f0e/src/tables/glyf.rs#L521
 | 
						# Mostly just ported from https://github.com/RazrFalcon/ttf-parser/blob/2192d3e496201ab2ff39d5437f88d62e70083f0e/src/tables/glyf.rs#L521
 | 
				
			||||||
 | 
					 | 
				
			||||||
	x_len = 0
 | 
						x_len = 0
 | 
				
			||||||
	y_len = 0
 | 
						y_len = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -5033,8 +5025,9 @@ def parse_glyph(f: BinaryIO, length: int) -> Glyph:
 | 
				
			|||||||
				case False, False: yDelta = read_i16(yCoordinates)
 | 
									case False, False: yDelta = read_i16(yCoordinates)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			coordinates.append((coordinates[-1][0]+xDelta, coordinates[-1][1]+yDelta))
 | 
								coordinates.append((coordinates[-1][0]+xDelta, coordinates[-1][1]+yDelta))
 | 
				
			||||||
 | 
							# TODO: Do I need to read the padding bytes?
 | 
				
			||||||
		assert length-4<f.tell()-start_tell<=length # there might be padding bytes
 | 
							assert length-4<f.tell()-start_tell<=length # there might be padding bytes
 | 
				
			||||||
 | 
							assert is_at_end(xCoordinates) and is_at_end(yCoordinates), (len_to_end(xCoordinates), len_to_end(yCoordinates))
 | 
				
			||||||
		return SimpleGlyph(numberOfContours, xMin, yMin, xMax, yMax, endPtsOfContours, instructionLength, instructions, flags, coordinates[1:])
 | 
							return SimpleGlyph(numberOfContours, xMin, yMin, xMax, yMax, endPtsOfContours, instructionLength, instructions, flags, coordinates[1:])
 | 
				
			||||||
	else:
 | 
						else:
 | 
				
			||||||
		components = [parse_component(f)]
 | 
							components = [parse_component(f)]
 | 
				
			||||||
@ -5091,12 +5084,14 @@ class GaspRange:
 | 
				
			|||||||
	rangeMaxPPEM: int
 | 
						rangeMaxPPEM: int
 | 
				
			||||||
	rangeGaspBehavior: int
 | 
						rangeGaspBehavior: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_gasp_range(f: BinaryIO, version:int) -> GaspRange:
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							assert self.rangeGaspBehavior & 0xFFF0 == 0, "Reserved"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_gasp_range(f: BinaryIO, version: int) -> GaspRange:
 | 
				
			||||||
	rangeMaxPPEM = read_u16(f)
 | 
						rangeMaxPPEM = read_u16(f)
 | 
				
			||||||
	rangeGaspBehavior = read_u16(f)
 | 
						rangeGaspBehavior = read_u16(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if version == 0: assert rangeGaspBehavior & 0x000C == 0, "Only supported in version 1"
 | 
						if version == 0: assert rangeGaspBehavior & 0x000C == 0, "Only supported in version 1"
 | 
				
			||||||
	assert rangeGaspBehavior & 0xFFF0 == 0, "Reserved"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return GaspRange(rangeMaxPPEM, rangeGaspBehavior)
 | 
						return GaspRange(rangeMaxPPEM, rangeGaspBehavior)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -5120,7 +5115,7 @@ class TrueTypeOutlines:
 | 
				
			|||||||
	control_value_table: Optional[CvtTable]
 | 
						control_value_table: Optional[CvtTable]
 | 
				
			||||||
	font_program: Optional[FpgmTable]	
 | 
						font_program: Optional[FpgmTable]	
 | 
				
			||||||
	glyph_data: GlyfTable
 | 
						glyph_data: GlyfTable
 | 
				
			||||||
	index_to_location: LocaTable
 | 
						index_to_location: LocaTable # Parsing only
 | 
				
			||||||
	CV_program: Optional[PrepTable]
 | 
						CV_program: Optional[PrepTable]
 | 
				
			||||||
	grid_fitting_and_scan_conversion: Optional[GaspTable]
 | 
						grid_fitting_and_scan_conversion: Optional[GaspTable]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -5152,9 +5147,11 @@ class TupleIndex:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	def tuple_index(self) -> int: return self.bytes & 0x0fff
 | 
						def tuple_index(self) -> int: return self.bytes & 0x0fff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							assert self.bytes & 0x1000 == 0, "Reserved"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_tuple_index(f: BinaryIO) -> TupleIndex:
 | 
					def parse_tuple_index(f: BinaryIO) -> TupleIndex:
 | 
				
			||||||
	tupleIndex = read_u16(f)
 | 
						tupleIndex = read_u16(f)
 | 
				
			||||||
	assert tupleIndex & 0x1000 == 0, "Reserved"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return TupleIndex(tupleIndex)
 | 
						return TupleIndex(tupleIndex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -5269,7 +5266,7 @@ def parse_cvar_per_tuple_variation_data(f: BinaryIO, tupleVariationHeader: Tuple
 | 
				
			|||||||
	assert f.tell()-start_tell == tupleVariationHeader.variationDataSize, (f.tell()-start_tell, tupleVariationHeader)
 | 
						assert f.tell()-start_tell == tupleVariationHeader.variationDataSize, (f.tell()-start_tell, tupleVariationHeader)
 | 
				
			||||||
	return CvarPerTupleVariationData(private_point_numbers, CVT_value_deltas)
 | 
						return CvarPerTupleVariationData(private_point_numbers, CVT_value_deltas)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_tuple_variation_count(f: BinaryIO) -> Tuple[bool, int]:
 | 
					def parse_tuple_variation_count(f: BinaryIO) -> Tuple[bool, int]: # TODO: Maybe do this like the other flags?
 | 
				
			||||||
	SHARED_POINT_NUMBERS = 0x8000
 | 
						SHARED_POINT_NUMBERS = 0x8000
 | 
				
			||||||
	COUNT_MASK = 0x0fff
 | 
						COUNT_MASK = 0x0fff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -5498,6 +5495,9 @@ class VariationAxisRecord:
 | 
				
			|||||||
	flags: int
 | 
						flags: int
 | 
				
			||||||
	axisNameID: int
 | 
						axisNameID: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							assert self.flags & 0xfffe == 0, "Reserved"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_variation_axis_record(f: BinaryIO, axisSize: int) -> VariationAxisRecord:
 | 
					def parse_variation_axis_record(f: BinaryIO, axisSize: int) -> VariationAxisRecord:
 | 
				
			||||||
	start_tell = f.tell()
 | 
						start_tell = f.tell()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -5506,7 +5506,6 @@ def parse_variation_axis_record(f: BinaryIO, axisSize: int) -> VariationAxisReco
 | 
				
			|||||||
	defaultValue = read_fixed(f)
 | 
						defaultValue = read_fixed(f)
 | 
				
			||||||
	maxValue = read_fixed(f)
 | 
						maxValue = read_fixed(f)
 | 
				
			||||||
	flags = read_u16(f)
 | 
						flags = read_u16(f)
 | 
				
			||||||
	assert flags & 0xfffe == 0, "Reserved"
 | 
					 | 
				
			||||||
	axisNameID = read_u16(f)
 | 
						axisNameID = read_u16(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert f.tell() - start_tell == axisSize, (f.tell() - start_tell, axisSize)
 | 
						assert f.tell() - start_tell == axisSize, (f.tell() - start_tell, axisSize)
 | 
				
			||||||
@ -5608,6 +5607,9 @@ class GvarTable(Table, ABD):
 | 
				
			|||||||
	flags: int
 | 
						flags: int
 | 
				
			||||||
	glyphVariationData: List[Optional[GlyphVariationDataTable]]
 | 
						glyphVariationData: List[Optional[GlyphVariationDataTable]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							assert self.flags & 0xfffe == 0, "Reserved?" # Maybe not?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class GvarTable_Ver_1_0(GvarTable): pass
 | 
					class GvarTable_Ver_1_0(GvarTable): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -5623,8 +5625,7 @@ def parse_gvar_table(f: BinaryIO, glyph_data: GlyfTable) -> GvarTable:
 | 
				
			|||||||
	sharedTupleCount = read_u16(f)
 | 
						sharedTupleCount = read_u16(f)
 | 
				
			||||||
	sharedTuplesOffset = read_u32(f)
 | 
						sharedTuplesOffset = read_u32(f)
 | 
				
			||||||
	glyphCount = read_u16(f)
 | 
						glyphCount = read_u16(f)
 | 
				
			||||||
	flags = read_u16(f)
 | 
						flags = read_u16(f) # TODO: Maybe make a method parse_gvar_flags
 | 
				
			||||||
	assert flags & 0xfffe == 0, f"Reserved?" # Maybe not?
 | 
					 | 
				
			||||||
	long = (flags & 0x0001) != 0
 | 
						long = (flags & 0x0001) != 0
 | 
				
			||||||
	glyphVariationDataArrayOffset = read_u32(f)
 | 
						glyphVariationDataArrayOffset = read_u32(f)
 | 
				
			||||||
	glyphVariationDataOffsets = [read_u16(f)*2 for _ in range(glyphCount+1)] if not long else [read_u32(f) for _ in range(glyphCount+1)] # TODO: Some of these point to the same GlyphVariationDataTables. Maybe don't reparse each one if it's the same?
 | 
						glyphVariationDataOffsets = [read_u16(f)*2 for _ in range(glyphCount+1)] if not long else [read_u32(f) for _ in range(glyphCount+1)] # TODO: Some of these point to the same GlyphVariationDataTables. Maybe don't reparse each one if it's the same?
 | 
				
			||||||
@ -5652,12 +5653,14 @@ class DeltaSetIndexMapTable(Table):
 | 
				
			|||||||
	mapCount: int
 | 
						mapCount: int
 | 
				
			||||||
	mapData: List[Tuple[int, int]] # (outerIndex, innerIndex)
 | 
						mapData: List[Tuple[int, int]] # (outerIndex, innerIndex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def __post_init__(self):
 | 
				
			||||||
 | 
							assert self.entryFormat & 0xffc0 == 0, "Reserved"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_delta_set_index_map_table(f: BinaryIO) -> DeltaSetIndexMapTable:
 | 
					def parse_delta_set_index_map_table(f: BinaryIO) -> DeltaSetIndexMapTable:
 | 
				
			||||||
	INNER_INDEX_BIT_COUNT_MASK = 0x000f
 | 
						INNER_INDEX_BIT_COUNT_MASK = 0x000f
 | 
				
			||||||
	MAP_ENTRY_SIZE_MASK = 0x0030
 | 
						MAP_ENTRY_SIZE_MASK = 0x0030
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	entryFormat = read_u16(f)
 | 
						entryFormat = read_u16(f) # TODO: Maybe make all flags like this? If something is reserved, it could just be future things
 | 
				
			||||||
	assert entryFormat & 0xffc0 == 0, "Reserved"
 | 
					 | 
				
			||||||
	map_entry_size = ((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1
 | 
						map_entry_size = ((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mapCount = read_u16(f)
 | 
						mapCount = read_u16(f)
 | 
				
			||||||
@ -5917,78 +5920,80 @@ def possibly_parse_at_table_directory_entry_with_length(f: BinaryIO, table: Opti
 | 
				
			|||||||
def parse_open_font_file(f: BinaryIO) -> OpenFontFile:
 | 
					def parse_open_font_file(f: BinaryIO) -> OpenFontFile:
 | 
				
			||||||
	font_directory = parse_font_directory(f)
 | 
						font_directory = parse_font_directory(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	font_header = parse_at_table_directory_entry(f, font_directory.by_table.head, parse_head_table)
 | 
						font_header = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Head), parse_head_table)
 | 
				
			||||||
	horizontal_header = parse_at_table_directory_entry(f, font_directory.by_table.hhea, parse_hhea_table)
 | 
						horizontal_header = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Hhea), parse_hhea_table)
 | 
				
			||||||
	maximum_profile = parse_at_table_directory_entry(f, font_directory.by_table.maxp, parse_maxp_table)
 | 
						maximum_profile = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Maxp), parse_maxp_table)
 | 
				
			||||||
	horizontal_metrics = parse_at_table_directory_entry(f, font_directory.by_table.hmtx, lambda f: parse_hmtx_table(f, horizontal_header.numberOfHMetrics, maximum_profile.numGlyphs))
 | 
						horizontal_metrics = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Hmtx), lambda f: parse_hmtx_table(f, horizontal_header.numberOfHMetrics, maximum_profile.numGlyphs))
 | 
				
			||||||
	naming_table = parse_at_table_directory_entry_with_length(f, font_directory.by_table.name, parse_name_table)
 | 
						naming_table = parse_at_table_directory_entry_with_length(f, font_directory.get_entry(TableTag.Name), parse_name_table)
 | 
				
			||||||
	OS2_and_Windows_specific_metrics = parse_at_table_directory_entry(f, font_directory.by_table.os2, parse_OS2_table)
 | 
						OS2_and_Windows_specific_metrics = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.OS2), parse_OS2_table)
 | 
				
			||||||
	character_to_glyph_mapping = parse_at_table_directory_entry(f, font_directory.by_table.cmap, parse_cmap_table)
 | 
						character_to_glyph_mapping = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Cmap), parse_cmap_table)
 | 
				
			||||||
	PostScript_information = parse_at_table_directory_entry_with_length(f, font_directory.by_table.post, parse_post_table)
 | 
						PostScript_information = parse_at_table_directory_entry_with_length(f, font_directory.get_entry(TableTag.Post), parse_post_table)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# optional
 | 
						# optional
 | 
				
			||||||
	digital_signature = possibly_parse_at_table_directory_entry(f, font_directory.by_table.DSIG, parse_DSIG_table)
 | 
						digital_signature = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.DSIG), parse_DSIG_table)
 | 
				
			||||||
	horizontal_device_metrics = possibly_parse_at_table_directory_entry(f, font_directory.by_table.hdmx, lambda f: parse_hdmx_table(f, maximum_profile.numGlyphs))
 | 
						horizontal_device_metrics = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Hdmx), lambda f: parse_hdmx_table(f, maximum_profile.numGlyphs))
 | 
				
			||||||
	kerning = possibly_parse_at_table_directory_entry(f, font_directory.by_table.Kern, parse_Kern_table)
 | 
						kerning = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Kern), parse_Kern_table)
 | 
				
			||||||
	linear_threshold_data = possibly_parse_at_table_directory_entry(f, font_directory.by_table.LTSH, parse_LTSH_table)
 | 
						linear_threshold_data = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.LTSH), parse_LTSH_table)
 | 
				
			||||||
	PCL5_data = possibly_parse_at_table_directory_entry(f, font_directory.by_table.PCLT, parse_PCLT_table)
 | 
						PCL5_data = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.PCLT), parse_PCLT_table)
 | 
				
			||||||
	vertical_device_metrics = possibly_parse_at_table_directory_entry(f, font_directory.by_table.VDMX, parse_VDMX_table)
 | 
						vertical_device_metrics = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.VDMX), parse_VDMX_table)
 | 
				
			||||||
	vertical_metrics_header = possibly_parse_at_table_directory_entry(f, font_directory.by_table.vhea, parse_vhea_table)
 | 
						vertical_metrics_header = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Vhea), parse_vhea_table)
 | 
				
			||||||
	if font_directory.by_table.vmtx:
 | 
						if font_directory.get_entry(TableTag.Vmtx):
 | 
				
			||||||
		assert vertical_metrics_header, f"Must have vertical_metrics_header to parse vertical_metrics"
 | 
							assert vertical_metrics_header, f"Must have vertical_metrics_header to parse vertical_metrics"
 | 
				
			||||||
		vertical_metrics = parse_at_table_directory_entry(f, font_directory.by_table.vmtx, lambda f: parse_vmtx_table(f, vertical_metrics_header.numOfLongVerMetris, maximum_profile.numGlyphs))
 | 
							vertical_metrics = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Vmtx), lambda f: parse_vmtx_table(f, vertical_metrics_header.numOfLongVerMetris, maximum_profile.numGlyphs))
 | 
				
			||||||
	else:
 | 
						else:
 | 
				
			||||||
		vertical_metrics = None
 | 
							vertical_metrics = None
 | 
				
			||||||
	colour_table = possibly_parse_at_table_directory_entry(f, font_directory.by_table.COLR, parse_COLR_table)
 | 
						colour_table = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.COLR), parse_COLR_table)
 | 
				
			||||||
	colour_palette_table = possibly_parse_at_table_directory_entry(f, font_directory.by_table.CPAL, parse_CPAL_table)
 | 
						colour_palette_table = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.CPAL), parse_CPAL_table)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# TTF / CFF
 | 
						# TTF / CFF
 | 
				
			||||||
	match font_directory.offset_table.sfntVersion:
 | 
						match font_directory.offset_table.sfntVersion:
 | 
				
			||||||
		case 0x00010000: # TTF
 | 
							case 0x00010000: # TTF
 | 
				
			||||||
			index_to_location = parse_at_table_directory_entry(f, font_directory.by_table.loca, lambda f: parse_loca_table(f, font_header.indexToLocFormat, maximum_profile.numGlyphs))
 | 
								index_to_location = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Loca), lambda f: parse_loca_table(f, font_header.indexToLocFormat, maximum_profile.numGlyphs))
 | 
				
			||||||
			glyph_data = parse_at_table_directory_entry(f, font_directory.by_table.glyf, lambda f: parse_glyf_table(f, index_to_location.offsets))
 | 
								glyph_data = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Glyf), lambda f: parse_glyf_table(f, index_to_location.offsets))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			control_value_table = possibly_parse_at_table_directory_entry_with_length(f, font_directory.by_table.cvt, parse_cvt_table)
 | 
								control_value_table = possibly_parse_at_table_directory_entry_with_length(f, font_directory.get_entry(TableTag.Cvt), parse_cvt_table)
 | 
				
			||||||
			font_program = possibly_parse_at_table_directory_entry_with_length(f, font_directory.by_table.fpgm, parse_fpgm_table)
 | 
								font_program = possibly_parse_at_table_directory_entry_with_length(f, font_directory.get_entry(TableTag.Fpgm), parse_fpgm_table)
 | 
				
			||||||
			CV_program = possibly_parse_at_table_directory_entry_with_length(f, font_directory.by_table.prep, parse_prep_table)
 | 
								CV_program = possibly_parse_at_table_directory_entry_with_length(f, font_directory.get_entry(TableTag.Prep), parse_prep_table)
 | 
				
			||||||
			grid_fitting_and_scan_conversion = possibly_parse_at_table_directory_entry(f, font_directory.by_table.gasp, parse_gasp_table)
 | 
								grid_fitting_and_scan_conversion = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Gasp), parse_gasp_table)
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			outlines = TrueTypeOutlines(control_value_table, font_program, glyph_data, index_to_location, CV_program, grid_fitting_and_scan_conversion)
 | 
								outlines = TrueTypeOutlines(control_value_table, font_program, glyph_data, index_to_location, CV_program, grid_fitting_and_scan_conversion)
 | 
				
			||||||
		case _:
 | 
							case _:
 | 
				
			||||||
			assert False, f"Unimplemented: sfntVersion: {hex(font_directory.offset_table.sfntVersion)}"
 | 
								assert False, f"Unimplemented: sfntVersion: {hex(font_directory.offset_table.sfntVersion)}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# SVG
 | 
						# SVG
 | 
				
			||||||
	scalar_vector_graphics = possibly_parse_at_table_directory_entry(f, font_directory.by_table.svg, parse_svg_table)
 | 
						scalar_vector_graphics = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Svg), parse_svg_table)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# Advanced
 | 
						# Advanced
 | 
				
			||||||
	baseline_data = possibly_parse_at_table_directory_entry(f, font_directory.by_table.BASE, parse_BASE_table)
 | 
						baseline_data = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.BASE), parse_BASE_table)
 | 
				
			||||||
	glyph_definition_data = possibly_parse_at_table_directory_entry(f, font_directory.by_table.GDEF, parse_GDEF_table)
 | 
						glyph_definition_data = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.GDEF), parse_GDEF_table)
 | 
				
			||||||
	glyph_positioning_data = possibly_parse_at_table_directory_entry(f, font_directory.by_table.GPOS, parse_GPOS_table)
 | 
						glyph_positioning_data = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.GPOS), parse_GPOS_table)
 | 
				
			||||||
	glyph_substitution_data = possibly_parse_at_table_directory_entry(f, font_directory.by_table.GSUB, parse_GSUB_table)
 | 
						glyph_substitution_data = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.GSUB), parse_GSUB_table)
 | 
				
			||||||
	justification_data = possibly_parse_at_table_directory_entry(f, font_directory.by_table.JSTF, parse_JSTF_table)
 | 
						justification_data = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.JSTF), parse_JSTF_table)
 | 
				
			||||||
	math_layout_data = possibly_parse_at_table_directory_entry(f, font_directory.by_table.MATH, parse_MATH_table)
 | 
						math_layout_data = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.MATH), parse_MATH_table)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	advanced_features = AdvancedFeatures(baseline_data, glyph_definition_data, glyph_positioning_data, glyph_substitution_data, justification_data, math_layout_data)
 | 
						advanced_features = AdvancedFeatures(baseline_data, glyph_definition_data, glyph_positioning_data, glyph_substitution_data, justification_data, math_layout_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	font_variations: Optional[FontVariations] = None
 | 
						font_variations: Optional[FontVariations] = None
 | 
				
			||||||
	if font_directory.by_table.fvar:
 | 
						if font_directory.has_entry(TableTag.Fvar):
 | 
				
			||||||
		font_variations_ = parse_at_table_directory_entry(f, font_directory.by_table.fvar, parse_fvar_table)
 | 
							font_variations_ = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Fvar), parse_fvar_table)
 | 
				
			||||||
		style_attributes = parse_at_table_directory_entry(f, font_directory.by_table.STAT, parse_STAT_table)
 | 
							style_attributes = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.STAT), parse_STAT_table)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		axis_variations = possibly_parse_at_table_directory_entry(f, font_directory.by_table.avar, parse_avar_table)
 | 
							axis_variations = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Avar), parse_avar_table)
 | 
				
			||||||
		if font_directory.by_table.cvar:
 | 
							if font_directory.has_entry(TableTag.Cvar):
 | 
				
			||||||
			assert control_value_table, f"Must have control_value_table in order to have CVT_variations!"
 | 
								assert isinstance(outlines, TrueTypeOutlines)
 | 
				
			||||||
			CVT_variations = parse_at_table_directory_entry(f, font_directory.by_table.cvar, lambda f: parse_cvar_table(f, font_variations_.axisCount, control_value_table))
 | 
								cvt = outlines.control_value_table
 | 
				
			||||||
 | 
								assert cvt, f"Must have control_value_table in order to have CVT_variations!"
 | 
				
			||||||
 | 
								CVT_variations = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Cvar), lambda f: parse_cvar_table(f, font_variations_.axisCount, cvt))
 | 
				
			||||||
		else:
 | 
							else:
 | 
				
			||||||
			CVT_variations = None
 | 
								CVT_variations = None
 | 
				
			||||||
		if font_directory.by_table.gvar:
 | 
							if font_directory.has_entry(TableTag.Gvar):
 | 
				
			||||||
			assert isinstance(outlines, TrueTypeOutlines)
 | 
								assert isinstance(outlines, TrueTypeOutlines)
 | 
				
			||||||
			glyph_variations = parse_at_table_directory_entry(f, font_directory.by_table.gvar, lambda f: parse_gvar_table(f, outlines.glyph_data))
 | 
								glyph_variations = parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.Gvar), lambda f: parse_gvar_table(f, outlines.glyph_data))
 | 
				
			||||||
		else:
 | 
							else:
 | 
				
			||||||
			glyph_variations = None
 | 
								glyph_variations = None
 | 
				
			||||||
		horizontal_metrics_variations = possibly_parse_at_table_directory_entry(f, font_directory.by_table.HVAR, parse_HVAR_table)
 | 
							horizontal_metrics_variations = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.HVAR), parse_HVAR_table)
 | 
				
			||||||
		metrics_variations = possibly_parse_at_table_directory_entry(f, font_directory.by_table.MVAR, parse_MVAR_table)
 | 
							metrics_variations = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.MVAR), parse_MVAR_table)
 | 
				
			||||||
		vertical_metrics_variations = possibly_parse_at_table_directory_entry(f, font_directory.by_table.VVAR, parse_VVAR_table)
 | 
							vertical_metrics_variations = possibly_parse_at_table_directory_entry(f, font_directory.get_entry(TableTag.VVAR), parse_VVAR_table)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		font_variations = FontVariations(axis_variations, CVT_variations, font_variations_, glyph_variations, horizontal_metrics_variations, metrics_variations, style_attributes, vertical_metrics_variations)
 | 
							font_variations = FontVariations(axis_variations, CVT_variations, font_variations_, glyph_variations, horizontal_metrics_variations, metrics_variations, style_attributes, vertical_metrics_variations)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,6 @@
 | 
				
			|||||||
class ABD:
 | 
					from abc import ABC
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ABD(ABC):
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	#### Abstract Base Dataclass
 | 
						#### Abstract Base Dataclass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -9,11 +11,14 @@ class ABD:
 | 
				
			|||||||
			msg = f"Cannot instantiate an Abstract Base Dataclass: {self.__class__.__name__}"
 | 
								msg = f"Cannot instantiate an Abstract Base Dataclass: {self.__class__.__name__}"
 | 
				
			||||||
			raise TypeError(msg)
 | 
								raise TypeError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TODO: Make a subclass of EnumMeta to do this
 | 
				
			||||||
class ABE:
 | 
					class ABE:
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	#### Abstract Base Enum
 | 
						#### Abstract Base Enum
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	This is for classes that will have an Enum subclass them
 | 
						This is for classes that will have an Enum subclass them.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Do not implement a __init__ method for the class directly inheriting from ABE
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	def __init__(self, *args, **kwargs):
 | 
						def __init__(self, *args, **kwargs):
 | 
				
			||||||
		if ABE in self.__class__.__bases__ or ABE == self:
 | 
							if ABE in self.__class__.__bases__ or ABE == self:
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,23 @@
 | 
				
			|||||||
from typing import BinaryIO
 | 
					from io import SEEK_END
 | 
				
			||||||
 | 
					from typing import BinaryIO, Callable, Literal, TypeVar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENDIANNESS = 'big'
 | 
					def len_to_end(f: BinaryIO) -> int:
 | 
				
			||||||
 | 
						curr_tell = f.tell()
 | 
				
			||||||
 | 
						f.seek(0, SEEK_END)
 | 
				
			||||||
 | 
						end_tell = f.tell()
 | 
				
			||||||
 | 
						f.seek(curr_tell)
 | 
				
			||||||
 | 
						return end_tell - curr_tell
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def read_int(f: BinaryIO, number: int, signed:bool=False) -> int: return int.from_bytes(f.read(number), ENDIANNESS, signed=signed)
 | 
					def is_at_end(f: BinaryIO) -> bool:
 | 
				
			||||||
def write_int(f: BinaryIO, value: int, number: int, signed:bool=False) -> int: return f.write(value.to_bytes(number, ENDIANNESS, signed=signed))
 | 
						return len_to_end(f) == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENDIANNESS: Literal['little', 'big'] = 'big'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def read_int_from_bytes(s: bytes, *, signed:bool=False) -> int: return int.from_bytes(s, ENDIANNESS, signed=signed)
 | 
				
			||||||
 | 
					def bytes_from_int(value: int, number: int, *, signed:bool=False) -> bytes: return value.to_bytes(number, ENDIANNESS, signed=signed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def read_int(f: BinaryIO, number: int, *, signed:bool=False) -> int: return read_int_from_bytes(f.read(number), signed=signed)
 | 
				
			||||||
 | 
					def write_int(f: BinaryIO, value: int, number: int, signed:bool=False) -> int: return f.write(bytes_from_int(value, number, signed=signed))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def read_u64(f: BinaryIO) -> int: return read_int(f, 8)
 | 
					def read_u64(f: BinaryIO) -> int: return read_int(f, 8)
 | 
				
			||||||
def read_u32(f: BinaryIO) -> int: return read_int(f, 4)
 | 
					def read_u32(f: BinaryIO) -> int: return read_int(f, 4)
 | 
				
			||||||
@ -11,7 +25,7 @@ def read_u24(f: BinaryIO) -> int: return read_int(f, 3)
 | 
				
			|||||||
def read_u16(f: BinaryIO) -> int: return read_int(f, 2)
 | 
					def read_u16(f: BinaryIO) -> int: return read_int(f, 2)
 | 
				
			||||||
def read_u8(f: BinaryIO) -> int: return read_int(f, 1)
 | 
					def read_u8(f: BinaryIO) -> int: return read_int(f, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def write_u16(f: BinaryIO, value: int) -> int: return f.write(value)
 | 
					def write_u16(f: BinaryIO, value: int) -> int: return write_int(f, value, 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def read_i32(f: BinaryIO) -> int: return read_int(f, 4, signed=True)
 | 
					def read_i32(f: BinaryIO) -> int: return read_int(f, 4, signed=True)
 | 
				
			||||||
def read_i16(f: BinaryIO) -> int: return read_int(f, 2, signed=True)
 | 
					def read_i16(f: BinaryIO) -> int: return read_int(f, 2, signed=True)
 | 
				
			||||||
@ -23,9 +37,12 @@ def read_ascii(f: BinaryIO, number: int) -> str: return f.read(number).decode(en
 | 
				
			|||||||
def read_fixed_point(f: BinaryIO, preradix_bits: int, postradix_bits:int, *, signed:bool=True) -> float:
 | 
					def read_fixed_point(f: BinaryIO, preradix_bits: int, postradix_bits:int, *, signed:bool=True) -> float:
 | 
				
			||||||
	assert (preradix_bits+postradix_bits)%8 == 0
 | 
						assert (preradix_bits+postradix_bits)%8 == 0
 | 
				
			||||||
	raw = read_int(f, (preradix_bits+postradix_bits)//8, signed=signed)
 | 
						raw = read_int(f, (preradix_bits+postradix_bits)//8, signed=signed)
 | 
				
			||||||
	return raw/(1<<(postradix_bits))
 | 
						return raw/(1<<postradix_bits)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def read_pascal_string(f: BinaryIO) -> str:
 | 
					def read_pascal_string(f: BinaryIO) -> str:
 | 
				
			||||||
	string_size = read_int(f, 1)
 | 
						string_size = read_u8(f)
 | 
				
			||||||
	pascal_string = read_ascii(f, string_size)
 | 
						pascal_string = read_ascii(f, string_size)
 | 
				
			||||||
	return pascal_string
 | 
						return pascal_string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					T = TypeVar('T')
 | 
				
			||||||
 | 
					Parser = Callable[[BinaryIO], T]
 | 
				
			||||||
@ -1,9 +1,9 @@
 | 
				
			|||||||
#!/usr/bin/env python3
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
assert len(sys.argv) == 2, "usage: python3 test.py <test>"
 | 
					assert len(sys.argv) >= 2, "usage: python3 test.py <test> [OPTIONS]"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from OpenFont import FontSpecificNameID, NameID, NameTable_Format_0, OpenFontFile, PredefinedNameID, TrueTypeOutlines, open_font_file, write_font_file
 | 
					from OpenFont import FontSpecificNameID, NameID, NameTable_Format_0, OpenFontFile, PredefinedNameID, TrueTypeOutlines, open_font_file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def search_names(font: OpenFontFile, nameID: NameID) -> str:
 | 
					def search_names(font: OpenFontFile, nameID: NameID) -> str:
 | 
				
			||||||
	assert isinstance(font.naming_table, NameTable_Format_0)
 | 
						assert isinstance(font.naming_table, NameTable_Format_0)
 | 
				
			||||||
@ -15,7 +15,7 @@ def search_names(font: OpenFontFile, nameID: NameID) -> str:
 | 
				
			|||||||
	assert False, f"Name not found: {nameID}"
 | 
						assert False, f"Name not found: {nameID}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_, test = sys.argv
 | 
					_, test, *options = sys.argv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
match test:
 | 
					match test:
 | 
				
			||||||
	case "names":
 | 
						case "names":
 | 
				
			||||||
@ -30,11 +30,6 @@ match test:
 | 
				
			|||||||
				axis_names = [search_names(font, FontSpecificNameID(axis.axisNameID)) for axis in font.font_variations.font_variations.axes]
 | 
									axis_names = [search_names(font, FontSpecificNameID(axis.axisNameID)) for axis in font.font_variations.font_variations.axes]
 | 
				
			||||||
				num_instances = font.font_variations.font_variations.instanceCount
 | 
									num_instances = font.font_variations.font_variations.instanceCount
 | 
				
			||||||
				print(f"\tAxes: [{', '.join(axis_names)}] ({num_instances} instances)")
 | 
									print(f"\tAxes: [{', '.join(axis_names)}] ({num_instances} instances)")
 | 
				
			||||||
	case "rewrite":
 | 
					 | 
				
			||||||
		def test_font(font: OpenFontFile):
 | 
					 | 
				
			||||||
			PATH = "out.ttf"
 | 
					 | 
				
			||||||
			write_font_file(font, PATH)
 | 
					 | 
				
			||||||
			open_font_file(PATH)
 | 
					 | 
				
			||||||
	case _:
 | 
						case _:
 | 
				
			||||||
		assert False, f"Invalid test: '{test}'"
 | 
							assert False, f"Invalid test: '{test}'"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -44,13 +39,17 @@ if not os.path.exists(COMPLETED_PATH):
 | 
				
			|||||||
with open(COMPLETED_PATH, "r") as f: completed = f.read().split('\n')
 | 
					with open(COMPLETED_PATH, "r") as f: completed = f.read().split('\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def do_font(file: str):
 | 
					def do_font(file: str):
 | 
				
			||||||
 | 
						file = file.strip()
 | 
				
			||||||
	if file in completed: return
 | 
						if file in completed: return
 | 
				
			||||||
	try:
 | 
						try:
 | 
				
			||||||
		font = open_font_file(file)
 | 
							font = open_font_file(file)
 | 
				
			||||||
		test_font(font)
 | 
							test_font(font)
 | 
				
			||||||
	except AssertionError as err:
 | 
						except AssertionError as err:
 | 
				
			||||||
		err.add_note(f"Failed: {file}")
 | 
							if '--raise' in options:
 | 
				
			||||||
		raise err
 | 
								err.add_note(f"Failed: {file}")
 | 
				
			||||||
 | 
								raise err
 | 
				
			||||||
 | 
							print(f"{file}{':' if '--no-colon' not in options else ''} {err}")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	with open(COMPLETED_PATH, 'a') as f: f.write(file+'\n')
 | 
						with open(COMPLETED_PATH, 'a') as f: f.write(file+'\n')
 | 
				
			||||||
	completed.append(file)
 | 
						completed.append(file)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@ -58,5 +57,3 @@ assert not sys.stdin.isatty(), f"Do not run this program directly, instead pipe
 | 
				
			|||||||
for line in sys.stdin:
 | 
					for line in sys.stdin:
 | 
				
			||||||
	file = line.rstrip('\n')
 | 
						file = line.rstrip('\n')
 | 
				
			||||||
	do_font(file)
 | 
						do_font(file)
 | 
				
			||||||
 | 
					 | 
				
			||||||
print("Done!")
 | 
					 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user