2024-05-03 21:02:58 +10:00
from dataclasses import dataclass
2024-09-15 16:10:41 +10:00
from datetime import datetime
2024-05-03 21:02:58 +10:00
from enum import Enum , EnumMeta
from io import BytesIO
from math import floor , log2
2024-09-15 16:10:41 +10:00
from typing import Callable , Generic , List , Optional , Tuple , TypeVar , BinaryIO
2024-05-03 21:02:58 +10:00
2024-09-15 16:10:41 +10:00
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
2024-09-15 16:17:20 +10:00
from abcde import ABD , ABE , abstractmethod
2024-09-15 16:10:41 +10:00
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
2024-05-03 21:02:58 +10:00
2024-09-15 16:10:41 +10:00
T = TypeVar ( ' T ' )
2024-05-03 21:02:58 +10:00
Tag_ = TypeVar ( ' Tag_ ' )
SomeTag = Callable [ [ str ] , Tag_ ] # If SomeTag is not an EnumMeta, it should throw a ValueError to indicate an invalid tag
2024-05-04 11:34:37 +10:00
def read_tag_with_conditions ( f : BinaryIO , * conditions : Tuple [ Callable [ [ str ] , bool ] , SomeTag [ Tag_ ] ] , umbrellaTagCls : type | SomeTag [ Tag_ ] , strict : bool = True ) - > Tag_ :
2024-05-03 21:02:58 +10:00
tag = read_ascii ( f , 4 )
assert not strict or all ( [ 0x20 < = ord ( c ) < = 0x7e for c in tag ] ) , f " Invalid tag: { [ f . seek ( - 4 , 1 ) , f . read ( 4 ) ] [ 1 ] } "
for ( condition , tagCls ) in conditions :
if condition ( tag ) :
try : return tagCls ( tag )
except ValueError : pass
else :
assert False , f " Invalid { umbrellaTagCls . __name__ } : ' { tag } ' "
2024-09-15 16:10:41 +10:00
always : Callable [ [ str ] , bool ] = lambda _ : True
2024-05-04 11:34:37 +10:00
def read_tag_from_tags ( f : BinaryIO , * tagClss : SomeTag [ Tag_ ] , umbrellaTagCls : type | SomeTag [ Tag_ ] , strict : bool = True ) - > Tag_ :
2024-05-03 21:02:58 +10:00
"""
This is meant to be used for when some instances of an Enum are just CC01 , CC02 , CC03 , . . .
"""
2024-09-15 16:10:41 +10:00
return read_tag_with_conditions ( f , * [ ( always , tagCls ) for tagCls in tagClss ] , umbrellaTagCls = umbrellaTagCls , strict = strict )
2024-05-03 21:02:58 +10:00
2024-05-04 11:34:37 +10:00
def read_tag ( f : BinaryIO , tagCls : SomeTag [ Tag_ ] , * , strict : bool = True ) - > Tag_ :
return read_tag_from_tags ( f , tagCls , umbrellaTagCls = tagCls , strict = strict )
2024-05-03 21:02:58 +10:00
ID_ = TypeVar ( ' ID_ ' )
SomeID = Callable [ [ int ] , ID_ ]
2024-05-04 11:34:37 +10:00
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
2024-05-03 21:02:58 +10:00
assert len ( ranges ) > 0 , f " Must have at least one range "
id = reader ( f )
for ( num , idCls ) in ranges :
if num is not None and id > num : continue
try : return idCls ( id )
except ValueError : pass
2024-09-15 16:10:41 +10:00
assert False , f " Invalid { umbrellaIdCls . __name__ } : { id } (hex: { repr_hex ( id , 2 ) } ) "
2024-05-03 21:02:58 +10:00
2024-05-04 11:34:37 +10:00
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 )
2024-05-03 21:02:58 +10:00
@dataclass
class Table ( ABD ) : pass
SomeTable = TypeVar ( ' SomeTable ' , bound = Table )
@dataclass
class SetTable ( Table , Generic [ SomeTable ] ) :
2024-05-04 11:34:37 +10:00
tableCount : int
tables : List [ SomeTable ]
2024-05-03 21:02:58 +10:00
def parse_set_table ( f : BinaryIO , parser : Parser [ SomeTable ] , * , offset_reader : Parser [ int ] = read_u16 ) - > SetTable [ SomeTable ] :
start_tell = f . tell ( )
count = read_u16 ( f )
offsets = [ offset_reader ( f ) for _ in range ( count ) ]
with SaveTell ( f ) :
elements = parse_at_offsets ( f , start_tell , offsets , parser )
return SetTable ( count , elements )
@dataclass
class Record ( ABD ) : pass
SomeRecord = TypeVar ( ' SomeRecord ' , bound = Record )
@dataclass
class ListTable ( Table , Generic [ SomeRecord ] ) :
numRecords : int
records : List [ SomeRecord ]
def parse_list_table ( f : BinaryIO , parse_record : Parser [ SomeRecord ] ) - > ListTable [ SomeRecord ] :
numRecords = read_u16 ( f )
records = [ parse_record ( f ) for _ in range ( numRecords ) ]
return ListTable ( numRecords , records )
@dataclass
class OffsetTable ( Table ) :
sfntVersion : int
numTables : int
searchRange : int
entrySelector : int
rangeShift : int
2024-09-15 16:10:41 +10:00
def repr_hex ( value : int , length : int = 8 ) - > str :
return f " 0x { hex ( value ) [ 2 : ] : 0> { length } } "
2024-05-03 21:02:58 +10:00
def parse_offset_table ( f : BinaryIO ) - > OffsetTable :
sfntVersion = read_u32 ( f )
2024-09-15 16:10:41 +10:00
assert sfntVersion in [ 0x00010000 , 0x4F54544F ] , f " Invalid sfntVersion: { repr_hex ( sfntVersion ) } . Expected 0x00010000 or 0x4F54544F. "
2024-05-03 21:02:58 +10:00
numTables = read_u16 ( f )
searchRange = read_u16 ( f )
entrySelector = read_u16 ( f )
rangeShift = read_u16 ( f )
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 entrySelector == len ( bin ( searchRange ) ) - 7 # ensure entrySelector is the logarithm of searchRange//16
2024-09-15 16:10:41 +10:00
assert rangeShift == numTables * 16 - searchRange
2024-05-03 21:02:58 +10:00
return OffsetTable ( sfntVersion , numTables , searchRange , entrySelector , rangeShift )
class TableTag ( Enum ) :
# 5.2 (Mandatory)
Cmap = ' cmap '
Head = ' head '
Hhea = ' hhea '
Hmtx = ' hmtx '
Maxp = ' maxp '
Name = ' name '
OS2 = ' OS/2 '
Post = ' post '
# 5.3 (TTF)
Cvt = ' cvt '
Fpgm = ' fpgm '
Glyf = ' glyf '
Loca = ' loca '
Prep = ' prep '
Gasp = ' gasp ' # :O
# 5.4 (CFF)
. . .
# 5.5 (SVG)
Svg = ' SVG '
# 5.6 (Optional)
DSIG = ' DSIG '
Hdmx = ' hdmx '
2024-09-15 16:10:41 +10:00
Kern = ' kern '
2024-05-03 21:02:58 +10:00
LTSH = ' LTSH '
PCLT = ' PCLT '
VDMX = ' VDMX '
Vhea = ' vhea '
Vmtx = ' vmtx '
COLR = ' COLR '
CPAL = ' CPAL '
# 6.3 (Advanced Features)
BASE = ' BASE '
GDEF = ' GDEF '
GPOS = ' GPOS '
GSUB = ' GSUB '
JSTF = ' JSTF '
MATH = ' MATH '
# 7.3 (Variable Fonts)
Avar = ' avar '
Cvar = ' cvar '
Fvar = ' fvar '
Gvar = ' gvar '
HVAR = ' HVAR '
MVAR = ' MVAR '
STAT = ' STAT '
VVAR = ' VVAR '
2024-09-15 16:17:20 +10:00
# Tables as recognised by [FontTools](https://fonttools.readthedocs.io/en/latest/ttLib/tables.html)
Mort = ' mort '
FFTM = ' FFTM '
CBDT = ' CBDT '
CBLC = ' CBLC '
Meta = ' meta '
2024-05-03 21:02:58 +10:00
def __str__ ( self ) - > str : return self . _value_
2024-05-04 11:34:37 +10:00
def parse_table_tag ( f : BinaryIO ) - > TableTag :
return read_tag ( f , TableTag )
2024-05-03 21:02:58 +10:00
@dataclass
class TableDirectoryEntry :
tableTag : TableTag
checkSum : int
offset : int
length : int
def parse_table_directory_entry ( f : BinaryIO ) - > TableDirectoryEntry :
2024-05-04 11:34:37 +10:00
tableTag = parse_table_tag ( f )
2024-05-03 21:02:58 +10:00
checkSum = read_u32 ( f )
offset = read_u32 ( f )
length = read_u32 ( f )
return TableDirectoryEntry ( tableTag , checkSum , offset , length )
@dataclass
class FontDirectory :
offset_table : OffsetTable
table_directory : List [ TableDirectoryEntry ]
2024-09-15 16:10:41 +10:00
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
2024-05-03 21:02:58 +10:00
def parse_font_directory ( f : BinaryIO ) - > FontDirectory :
offset_table = parse_offset_table ( f )
table_directory_entries = [ parse_table_directory_entry ( f ) for _ in range ( offset_table . numTables ) ]
2024-09-15 16:10:41 +10:00
return FontDirectory ( offset_table , table_directory_entries )
2024-05-03 21:02:58 +10:00
@dataclass
class CmapSubtable ( Table , ABD ) :
format : int
@dataclass
class CmapSubtable_Format_0 ( CmapSubtable ) :
length : int
language : int # For Macintosh platform, this value is QuickDraw language ID +1 if language specific, 0 otherwise. This is true for all CmapSubtable formats
glyphIdArray : List [ int ]
# TODO: Other formats besides 4
@dataclass
class SubHeader :
pass
def parse_sub_header ( f : BinaryIO ) - > SubHeader :
assert False
@dataclass
class CmapSubtable_Format_2 ( CmapSubtable ) :
length : int
2024-09-15 16:10:41 +10:00
language : int # TODO: Make this an optional int
2024-05-03 21:02:58 +10:00
subHeaderKeys : List [ int ] # 256 elements
subHeaders : List [ SubHeader ]
glyphIndexArray : List [ int ]
@dataclass
class CmapSubtable_Format_4 ( CmapSubtable ) :
length : int
language : int
segCountX2 : int
searchRange : int
entrySelector : int
rangeShift : int
endCode : List [ int ]
startCode : List [ int ]
idDelta : List [ int ]
idRangeOffset : List [ int ]
glyphIdArray : List [ int ]
@dataclass
class CmapSubtable_Format_6 ( CmapSubtable ) :
length : int
language : int
firstCode : int
entryCount : int
glyphIdArray : List [ int ]
@dataclass
class SequentialMapGroup :
startCharCode : int
endCharCode : int
startGlyphID : int
def parse_sequential_map_group ( f : BinaryIO ) - > SequentialMapGroup :
startCharCode = read_u32 ( f )
endCharCode = read_u32 ( f )
startGlyphID = read_u32 ( f )
return SequentialMapGroup ( startCharCode , endCharCode , startGlyphID )
@dataclass
class CmapSubtable_Format_12 ( CmapSubtable ) :
length : int
language : int
numGroups : int
groups : List [ SequentialMapGroup ]
@dataclass
2024-05-04 11:34:37 +10:00
class UnicodeRangeRecord ( Record ) :
2024-05-03 21:02:58 +10:00
startUnicodeValue : int
additionalCount : int
def __repr__ ( self ) - > str :
2024-05-04 11:34:37 +10:00
return f " <U+ { hex ( self . startUnicodeValue ) [ 2 : ] : 0>4 } " + ( f " -U+ { hex ( self . startUnicodeValue + self . additionalCount ) [ 2 : ] : 0>4 } " if self . additionalCount > 0 else ' ' ) + " > "
2024-05-03 21:02:58 +10:00
def parse_unicode_range_record ( f : BinaryIO ) - > UnicodeRangeRecord :
startUnicodeValue = read_u24 ( f )
additionalCount = read_u8 ( f )
assert startUnicodeValue + additionalCount < = 0xFFFFFF , ( startUnicodeValue , additionalCount , startUnicodeValue + additionalCount )
return UnicodeRangeRecord ( startUnicodeValue , additionalCount )
@dataclass
class DefaultUVSTable ( Table ) :
numUnicodeValueRanges : int
ranges : List [ UnicodeRangeRecord ]
def parse_default_UVS_table ( f : BinaryIO ) - > DefaultUVSTable :
numUnicodeValueRanges = read_u32 ( f )
ranges = [ parse_unicode_range_record ( f ) for _ in range ( numUnicodeValueRanges ) ]
return DefaultUVSTable ( numUnicodeValueRanges , ranges )
@dataclass
2024-05-04 11:34:37 +10:00
class UVSMappingRecord ( Record ) :
2024-05-03 21:02:58 +10:00
unicodeValue : int
glyphID : int
def parse_UVS_mapping_record ( f : BinaryIO ) - > UVSMappingRecord :
unicodeValue = read_u24 ( f )
glyphID = read_u16 ( f )
return UVSMappingRecord ( unicodeValue , glyphID )
@dataclass
class NonDefaultUVSTable ( Table ) :
numUVSMappings : int
uvsMappings : List [ UVSMappingRecord ]
def parse_non_default_UVS_table ( f : BinaryIO ) - > NonDefaultUVSTable :
numUVSMappings = read_u32 ( f )
uvsMappings = [ parse_UVS_mapping_record ( f ) for _ in range ( numUVSMappings ) ]
return NonDefaultUVSTable ( numUVSMappings , uvsMappings )
@dataclass
2024-05-04 11:34:37 +10:00
class VariationSelectorRecord ( Record ) :
2024-05-03 21:02:58 +10:00
varSelector : int
defaultUVS : Optional [ DefaultUVSTable ]
nonDefaultUVS : Optional [ NonDefaultUVSTable ]
def parse_variation_selector_record ( f : BinaryIO , start_tell : int ) - > VariationSelectorRecord :
varSelector = read_u24 ( f )
defaultUVSOffset = read_u32 ( f )
nonDefaultUVSOffset = read_u32 ( f )
with SaveTell ( f ) :
defaultUVS = parse_at_optional_offset ( f , start_tell , defaultUVSOffset , parse_default_UVS_table )
nonDefaultUVS = parse_at_optional_offset ( f , start_tell , nonDefaultUVSOffset , parse_non_default_UVS_table )
return VariationSelectorRecord ( varSelector , defaultUVS , nonDefaultUVS )
@dataclass
class CmapSubtable_Format_14 ( CmapSubtable ) :
length : int
numVarSelectorRecords : int
varSelector : List [ VariationSelectorRecord ]
class PlatformID ( Enum ) :
Unicode = 0
Macintosh = 1
ISO = 2
Windows = 3
Custom = 4
def __str__ ( self ) - > str :
return self . _name_
def parse_cmap_subtable ( f : BinaryIO , platformID : PlatformID ) - > CmapSubtable :
start_tell = f . tell ( )
format = read_u16 ( f )
assert format in [ 0 , 2 , 4 , 6 , 8 , 10 , 12 , 13 , 14 ] , f " Invalid format: { format } "
match format :
2024-09-15 16:17:20 +10:00
case 0 :
length = read_u16 ( f )
language = read_u16 ( f )
if platformID != PlatformID . Macintosh : assert language == 0
glyphIdArray = [ read_u8 ( f ) for _ in range ( 256 ) ]
return CmapSubtable_Format_0 ( format , length , language , glyphIdArray )
2024-05-03 21:02:58 +10:00
case 4 :
length = read_u16 ( f )
language = read_u16 ( f )
if platformID != PlatformID . Macintosh : assert language == 0
segCountX2 = read_u16 ( f )
assert segCountX2 % 2 == 0
segCount = segCountX2 / / 2
searchRange = read_u16 ( f )
assert searchRange == 2 * 2 * * floor ( log2 ( segCount ) )
entrySelector = read_u16 ( f )
assert entrySelector == log2 ( searchRange / / 2 )
rangeShift = read_u16 ( f )
assert rangeShift == segCountX2 - searchRange
endCode = [ read_u16 ( f ) for _ in range ( segCount ) ]
assert read_u16 ( f ) == 0 , " Reserved "
startCode = [ read_u16 ( f ) for _ in range ( segCount ) ]
idDelta = [ read_i16 ( f ) for _ in range ( segCount ) ]
idRangeOffset = [ read_u16 ( f ) for _ in range ( segCount ) ]
remaining_length = length - ( f . tell ( ) - start_tell )
assert remaining_length % 2 == 0 # remaining length is made up of u16's
glyphIdArray = [ read_u16 ( f ) for _ in range ( remaining_length / / 2 ) ]
return CmapSubtable_Format_4 ( format , length , language , segCountX2 , searchRange , entrySelector , rangeShift , endCode , startCode , idDelta , idRangeOffset , glyphIdArray )
case 6 :
length = read_u16 ( f )
language = read_u16 ( f )
if platformID != PlatformID . Macintosh : assert language == 0
firstCode = read_u16 ( f )
entryCount = read_u16 ( f )
glyphIdArray = [ read_u16 ( f ) for _ in range ( entryCount ) ]
2024-09-15 16:10:41 +10:00
assert length - 4 < f . tell ( ) - start_tell < = length , ( f . tell ( ) - start_tell , length )
f . seek ( length - ( f . tell ( ) - start_tell ) )
2024-05-03 21:02:58 +10:00
return CmapSubtable_Format_6 ( format , length , language , firstCode , entryCount , glyphIdArray )
case 12 :
assert read_u16 ( f ) == 0 , " Reserved "
length = read_u32 ( f )
language = read_u32 ( f ) # idk why language needs to also be a u32??
if platformID != PlatformID . Macintosh : assert language == 0
numGroups = read_u32 ( f )
groups = [ parse_sequential_map_group ( f ) for _ in range ( numGroups ) ]
assert f . tell ( ) - start_tell == length , ( f . tell ( ) - start_tell , length )
return CmapSubtable_Format_12 ( format , length , language , numGroups , groups )
case 14 :
length = read_u32 ( f )
numVarSelectorRecords = read_u32 ( f )
varSelector = [ parse_variation_selector_record ( f , start_tell ) for _ in range ( numVarSelectorRecords ) ]
# assert f.tell()-start_tell == length, (numVarSelectorRecords, f.tell()-start_tell, length) # this will return false because tables/records stored with offsets to them aren't counted by python, but are in the `length` variable
return CmapSubtable_Format_14 ( format , length , numVarSelectorRecords , varSelector )
case _ :
assert False , f " Unimplemented: format: { format } "
assert False , format
2024-09-15 16:22:01 +10:00
class EncodingID ( ABE ) :
@abstractmethod
def get_code_point ( self , char : str ) - > Optional [ int ] :
assert False , f " Unimplemented: get_code_point for { repr ( self ) } "
2024-05-03 21:02:58 +10:00
class UnicodeEncodingID ( EncodingID , Enum ) :
Unicode_1_0 = 0
Unicode_1_1 = 1
ISO_IEC_10646 = 2
Unicode_2_0_BMP_Only = 3
Unicode_2_0_Full = 4
UnicodeVariationSequences = 5
UnicodeFull = 6
2024-09-15 16:22:01 +10:00
def __str__ ( self ) - > str :
return self . _name_
def get_code_point ( self , char : str ) - > int | None :
return ord ( char ) # TODO: Actually validate that the codepoint is within range of these different versions of Unicode Encoding
match self :
case _ : assert False , self
2024-05-03 21:02:58 +10:00
class MacintoshEncodingID ( EncodingID , Enum ) :
Roman = 0
def __str__ ( self ) - > str :
return self . _name_
class ISOEncodingID ( EncodingID , Enum ) :
pass
class WindowsEncodingID ( EncodingID , Enum ) :
Symbol = 0
UnicodeBMP = 1
ShiftJIS = 2
PRC = 3
Big5 = 4
Wansung = 5
Johab = 6
2024-09-15 16:10:41 +10:00
UnicodeFull = 10
2024-05-03 21:02:58 +10:00
def __str__ ( self ) - > str :
return self . _name_
2024-09-15 16:22:01 +10:00
def get_code_point ( self , char : str ) - > int | None :
match self :
case self . UnicodeBMP :
code_point = ord ( char )
if 0 < = code_point < = 0xFFFF : return code_point
return None
case self . UnicodeFull : return ord ( char )
case _ : assert False , self
2024-05-03 21:02:58 +10:00
@dataclass
class CustomEncodingID ( EncodingID ) :
encodingID : int
def encoding_ID_cls_from_platform_ID ( platformID : PlatformID ) - > Callable [ [ int ] , EncodingID ] :
match platformID :
case PlatformID . Unicode : return UnicodeEncodingID
case PlatformID . Macintosh : return MacintoshEncodingID
case PlatformID . Windows : return WindowsEncodingID
case _ :
assert False , f " Unimplemented: platformID: { platformID } "
assert False , platformID
2024-05-04 11:34:37 +10:00
def parse_encoding_ID ( f : BinaryIO , platformID : PlatformID ) - > EncodingID :
return read_id ( f , encoding_ID_cls_from_platform_ID ( platformID ) )
2024-05-03 21:02:58 +10:00
2024-09-15 16:10:41 +10:00
# TODO: Finish this
2024-05-03 21:02:58 +10:00
def parse_string_with_encoding_ID ( f : BinaryIO , length : int , encodingID : EncodingID ) - > str :
bytes = f . read ( length )
match encodingID :
case MacintoshEncodingID . Roman : return bytes . decode ( encoding = ' mac_roman ' )
case WindowsEncodingID . UnicodeBMP : return bytes . decode ( encoding = ' utf-16be ' )
case _ :
assert False , f " Unimplemented: encodingID: { encodingID } "
assert False , encodingID
@dataclass
2024-05-04 11:34:37 +10:00
class EncodingRecord ( Record ) :
2024-05-03 21:02:58 +10:00
platformID : PlatformID
encodingID : EncodingID
subtable : CmapSubtable
def parse_encoding_record ( f : BinaryIO , start_tell : int ) - > EncodingRecord :
2024-05-04 11:34:37 +10:00
platformID = read_id ( f , PlatformID )
2024-05-03 21:02:58 +10:00
encodingID = parse_encoding_ID ( f , platformID )
subtableOffset = read_u32 ( f )
with SaveTell ( f ) :
subtable = parse_at_offset ( f , start_tell , subtableOffset , lambda f : parse_cmap_subtable ( f , platformID ) )
return EncodingRecord ( platformID , encodingID , subtable )
@dataclass
class CmapTable ( Table ) :
version : int
numTables : int
encodingRecords : List [ EncodingRecord ]
def parse_cmap_table ( f : BinaryIO ) - > CmapTable :
start_tell = f . tell ( )
version = read_u16 ( f )
assert version in [ 0 ]
numTables = read_u16 ( f )
encodingRecords = [ parse_encoding_record ( f , start_tell ) for _ in range ( numTables ) ]
return CmapTable ( version , numTables , encodingRecords )
2024-09-15 16:10:41 +10:00
HEAD_TABLE_MAGIC = 0x5F0F3CF5
2024-05-03 21:02:58 +10:00
@dataclass
class HeadTable ( Table ) :
majorVersion : int
minorVersion : int
fontRevision : float
checkSumAdjustment : int
flags : int
unitsPerEm : int
created : datetime
modified : datetime
xMin : int
yMin : int
xMax : int
yMax : int
macStyle : int
lowestRecPPEM : int
fontDirectionHint : int
indexToLocFormat : int
glyphDataFormat : int
def parse_head_table ( f : BinaryIO ) - > HeadTable :
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 ]
fontRevision = read_fixed ( f )
checkSumAdjustment = read_u32 ( f )
2024-09-15 16:10:41 +10:00
assert read_u32 ( f ) == HEAD_TABLE_MAGIC , " magicNumber "
2024-05-03 21:02:58 +10:00
flags = read_u16 ( f )
unitsPerEm = read_u16 ( f )
created = read_long_datetime ( f )
modified = read_long_datetime ( f )
xMin = read_i16 ( f )
yMin = read_i16 ( f )
xMax = read_i16 ( f )
yMax = read_i16 ( f )
macStyle = read_u16 ( f )
lowestRecPPEM = read_u16 ( f )
fontDirectionHint = read_i16 ( f )
indexToLocFormat = read_i16 ( f )
assert indexToLocFormat in [ 0 , 1 ]
glyphDataFormat = read_i16 ( f )
assert glyphDataFormat == 0
# assert macStyle & 0x01 == os2.fsSelection & 0x20 # bit 0 and bit 5 must match
# assert macStyle & 0x02 == os2.fsSelection & 0x01 # bit 1 and bit 0 must match
return HeadTable ( majorVersion , minorVersion , fontRevision , checkSumAdjustment , flags , unitsPerEm , created , modified , xMin , yMin , xMax , yMax , macStyle , lowestRecPPEM , fontDirectionHint , indexToLocFormat , glyphDataFormat )
@dataclass
class HheaTable ( Table ) :
majorVersion : int
minorVersion : int
ascender : int
descender : int
lineGap : int
advanceWidthMax : int
minLeftSideBearing : int
minRightSideBearing : int
xMaxExtent : int
caretSlopeRise : int
caretSlopeRun : int
caretOffset : int
metricDataFormat : int
numberOfHMetrics : int
def parse_hhea_table ( f : BinaryIO ) - > HheaTable :
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 ]
ascender = read_i16 ( f )
descender = read_i16 ( f )
lineGap = read_i16 ( f )
advanceWidthMax = read_u16 ( f )
minLeftSideBearing = read_i16 ( f )
minRightSideBearing = read_i16 ( f )
xMaxExtent = read_i16 ( f )
caretSlopeRise = read_i16 ( f )
caretSlopeRun = read_i16 ( f )
caretOffset = read_i16 ( f )
for _ in range ( 4 ) : assert read_i16 ( f ) == 0 , " Reserved "
metricDataFormat = read_i16 ( f )
assert metricDataFormat == 0
numberOfHMetrics = read_u16 ( f )
return HheaTable ( majorVersion , minorVersion , ascender , descender , lineGap , advanceWidthMax , minLeftSideBearing , minRightSideBearing , xMaxExtent , caretSlopeRise , caretSlopeRun , caretOffset , metricDataFormat , numberOfHMetrics )
@dataclass
class LongHorMetric :
advanceWidth : int
lsb : int
def parse_long_hor_metric ( f : BinaryIO ) - > LongHorMetric :
advanceWidth = read_u16 ( f )
lsb = read_i16 ( f )
return LongHorMetric ( advanceWidth , lsb )
@dataclass
class HmtxTable ( Table ) :
hMetrics : List [ LongHorMetric ]
leftSideBearing : List [ int ]
def parse_hmtx_table ( f : BinaryIO , numberOfHMetrics : int , numGlyphs : int ) - > HmtxTable :
assert numberOfHMetrics < = numGlyphs
hMetrics = [ parse_long_hor_metric ( f ) for _ in range ( numberOfHMetrics ) ]
leftSideBearing = [ read_i16 ( f ) for _ in range ( numGlyphs - numberOfHMetrics ) ]
return HmtxTable ( hMetrics , leftSideBearing )
@dataclass
class MaxpTable ( Table ) :
version : float
numGlyphs : int
@dataclass
class MaxpTable_Ver_0_5 ( MaxpTable ) : pass
@dataclass
class MaxpTable_Ver_1_0 ( MaxpTable_Ver_0_5 ) :
maxPoints : int
maxContours : int
maxCompositePoints : int
maxCompositeContours : int
maxZones : int
maxTwilightPoints : int
maxStorage : int
maxFunctionDefs : int
maxInstructionDefs : int
maxStackElements : int
maxSizeOfInstructions : int
maxComponentElements : int
maxComponentDepth : int
def parse_maxp_table ( f : BinaryIO ) - > MaxpTable :
version = read_fixed_version ( f )
assert version in [ 0.5 , 1.0 ] , f " Invalid version: { version } "
numGlyphs = read_u16 ( f )
if version == 0.5 :
return MaxpTable_Ver_0_5 ( version , numGlyphs )
maxPoints = read_u16 ( f )
maxContours = read_u16 ( f )
maxCompositePoints = read_u16 ( f )
maxCompositeContours = read_u16 ( f )
maxZones = read_u16 ( f )
maxTwilightPoints = read_u16 ( f )
maxStorage = read_u16 ( f )
maxFunctionDefs = read_u16 ( f )
maxInstructionDefs = read_u16 ( f )
maxStackElements = read_u16 ( f )
maxSizeOfInstructions = read_u16 ( f )
maxComponentElements = read_u16 ( f )
maxComponentDepth = read_u16 ( f )
if version == 1.0 :
return MaxpTable_Ver_1_0 ( version , numGlyphs , maxPoints , maxContours , maxCompositePoints , maxCompositeContours , maxZones , maxTwilightPoints , maxStorage , maxFunctionDefs , maxInstructionDefs , maxStackElements , maxSizeOfInstructions , maxComponentElements , maxComponentDepth )
assert False , f " Unimplemented: version: { version } "
@dataclass
class NameTable ( Table , ABD ) :
format : int
class LanguageID ( ABE ) : pass
class MacintoshLanguageID ( LanguageID , Enum ) :
English = 0
2024-09-15 16:10:41 +10:00
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
2024-05-03 21:02:58 +10:00
def __str__ ( self ) - > str : return self . _name_
class WindowsLanguageID ( LanguageID , Enum ) :
Afrikaans_SouthAfrica = 0x0436
Albanian_Albania = 0x041C
Alsatian_France = 0x0484
Amharic_Ethiopia = 0x045E
Arabic_Algeria = 0x1401
Arabic_Bahrain = 0x3C01
Arabic_Egypt = 0x0C01
Arabic_Iraq = 0x0801
Arabic_Jordan = 0x2C01
Arabic_Kuwait = 0x3401
Arabic_Lebanon = 0x3001
Arabic_Libya = 0x1001
Arabic_Morocco = 0x1801
Arabic_Oman = 0x2001
Arabic_Qatar = 0x4001
Arabic_SaudiArabia = 0x0401
Arabic_Syria = 0x2801
Arabic_Tunisia = 0x1C01
Arabic_UAE = 0x3801
Arabic_Yemen = 0x2401
Armenian_Armenia = 0x042B
Assamese_India = 0x044D
Azeri_Cyrillic_Azerbaijan = 0x082C
Azeri_Latin_Azerbaijan = 0x042C
Bashkir_Russia = 0x046D
Basque_Basque = 0x042D
Belarusian_Belarus = 0x0423
Bengali_Bangladesh = 0x0845
Bengali_India = 0x0445
Bosnian_Cyrillic_BosniaAndHerzegovina = 0x201A
Bosnian_Latin_BosniaAndHerzegovina = 0x141A
Breton_France = 0x047E
Bulgarian_Bulgaria = 0x0402
Catalan_Catalan = 0x0403
Chinese_HongKongSAR = 0x0C04
Chinese_MacaoSAR = 0x1404
Chinese_PeoplesRepublicOfChina = 0x0804
Chinese_Singapore = 0x1004
Chinese_Taiwan = 0x0404
Corsican_France = 0x0483
Croatian_Croatia = 0x041A
Croatian_Latin_BosniaAndHerzegovina = 0x101A
Czech_CzechRepublic = 0x0405
Danish_Denmark = 0x0406
Dari_Afghanistan = 0x048C
Divehi_Maldives = 0x0465
Dutch_Belgium = 0x0813
Dutch_Netherlands = 0x0413
English_Australia = 0x0C09
English_Belize = 0x2809
English_Canada = 0x1009
English_Caribbean = 0x2409
English_India = 0x4009
English_Ireland = 0x1809
English_Jamaica = 0x2009
English_Malaysia = 0x4409
English_NewZealand = 0x1409
English_RepublicOfThePhilippines = 0x3409
English_Singapore = 0x4809
English_SouthAfrica = 0x1C09
English_TrinidadAndTobago = 0x2C09
English_UnitedKingdom = 0x0809
English_UnitedStates = 0x0409
English_Zimbabwe = 0x3009
Estonian_Estonia = 0x0425
Faroese_FaroeIslands = 0x0438
Filipino_Philippines = 0x0464
Finnish_Finland = 0x040B
French_Belgium = 0x080C
French_Canada = 0x0C0C
French_France = 0x040C
French_Luxembourg = 0x140c
French_PrincipalityOfMonaco = 0x180C
French_Switzerland = 0x100C
Frisian_Netherlands = 0x0462
Galician_Galician = 0x0456
Georgian_Georgia = 0x0437
German_Austria = 0x0C07
German_Germany = 0x0407
German_Liechtenstein = 0x1407
German_Luxembourg = 0x1007
German_Switzerland = 0x0807
Greek_Greece = 0x0408
Greenlandic_Greenland = 0x046F
Gujarati_India = 0x0447
Hausa_Latin_Nigeria = 0x0468
Hebrew_Israel = 0x040D
Hindi_India = 0x0439
Hungarian_Hungary = 0x040E
Icelandic_Iceland = 0x040F
Igbo_Nigeria = 0x0470
Indonesian_Indonesia = 0x0421
Inuktitut_Canada = 0x045D
Inuktitut_Latin_Canada = 0x085D
Irish_Ireland = 0x083C
isiXhosa_SouthAfrica = 0x0434
isiZulu_SouthAfrica = 0x0435
Italian_Italy = 0x0410
Italian_Switzerland = 0x0810
Japanese_Japan = 0x0411
Kannada_India = 0x044B
Kazakh_Kazakhstan = 0x043F
Khmer_Cambodia = 0x0453
Kiche_Guatemala = 0x0486
Kinyarwanda_Rwanda = 0x0487
Kiswahili_Kenya = 0x0441
Konkani_India = 0x0457
Korean_Korea = 0x0412
Kyrgyz_Kyrgyzstan = 0x0440
Lao_LaoPDR = 0x0454
Latvian_Latvia = 0x0426
Lithuanian_Lithuania = 0x0427
LowerSorbian_Germany = 0x082E
Luxembourgish_Luxembourg = 0x046E
Macedonian_FYROM_FormerYugoslavRepublicOfMacedonia = 0x042F
Malay_BruneiDarussalam = 0x083E
Malay_Malaysia = 0x043E
Malayalam_India = 0x044C
Maltese_Malta = 0x043A
Maori_NewZealand = 0x0481
Mapudungun_Chile = 0x047A
Marathi_India = 0x044E
Mohawk_Mohawk = 0x047C
Mongolian_Cyrillic_Mongolia = 0x0450
Mongolian_Traditional_PeoplesRepublicOfChina = 0x0850
Nepali_Nepal = 0x0461
Norwegian_Bokmal_Norway = 0x0414
Norwegian_Nynorsk_Norway = 0x0814
Occitan_France = 0x0482
Odia_formerlyOriya_India = 0x0448
Pashto_Afghanistan = 0x0463
Polish_Poland = 0x0415
Portuguese_Brazil = 0x0416
Portuguese_Portugal = 0x0816
Punjabi_India = 0x0446
Quechua_Bolivia = 0x046B
Quechua_Ecuador = 0x086B
Quechua_Peru = 0x0C6B
Romanian_Romania = 0x0418
Romansh_Switzerland = 0x0417
Russian_Russia = 0x0419
Sami_Inari_Finland = 0x243B
Sami_Lule_Norway = 0x103B
Sami_Lule_Sweden = 0x143B
Sami_Northern_Finland = 0x0C3B
Sami_Northern_Norway = 0x043B
Sami_Northern_Sweden = 0x083B
Sami_Skolt_Finland = 0x203B
Sami_Southern_Norway = 0x183B
Sami_Southern_Sweden = 0x1C3B
Sanskrit_India = 0x044F
Serbian_Cyrillic_BosniaAndHerzegovina = 0x1C1A
Serbian_Cyrillic_Serbia = 0x0C1A
Serbian_Latin_BosniaAndHerzegovina = 0x181A
Serbian_Latin_Serbia = 0x081A
SesothoSaLeboa_SouthAfrica = 0x046C
Setswana_SouthAfrica = 0x0432
Sinhala_SriLanka = 0x045B
Slovak_Slovakia = 0x041B
Slovenian_Slovenia = 0x0424
Spanish_Argentina = 0x2C0A
Spanish_Bolivia = 0x400A
Spanish_Chile = 0x340A
Spanish_Colombia = 0x240A
Spanish_CostaRica = 0x140A
Spanish_DominicanRepublic = 0x1C0A
Spanish_Ecuador = 0x300A
Spanish_ElSalvador = 0x440A
Spanish_Guatemala = 0x100A
Spanish_Honduras = 0x480A
Spanish_Mexico = 0x080A
Spanish_Nicaragua = 0x4C0A
Spanish_Panama = 0x180A
Spanish_Paraguay = 0x3C0A
Spanish_Peru = 0x280A
Spanish_PuertoRico = 0x500A
Spanish_ModernSort_Spain = 0x0C0A
Spanish_TraditionalSort_Spain = 0x040A
Spanish_UnitedStates = 0x540A
Spanish_Uruguay = 0x380A
Spanish_Venezuela = 0x200A
Sweden_Finland = 0x081D
Swedish_Sweden = 0x041D
Syriac_Syria = 0x045A
Tajik_Cyrillic_Tajikistan = 0x0428
Tamazight_Latin_Algeria = 0x085F
Tamil_India = 0x0449
Tatar_Russia = 0x0444
Telugu_India = 0x044A
Thai_Thailand = 0x041E
Tibetan_PRC = 0x0451
Turkish_Turkey = 0x041F
Turkmen_Turkmenistan = 0x0442
Uighur_PRC = 0x0480
Ukrainian_Ukraine = 0x0422
UpperSorbian_Germany = 0x042E
Urdu_IslamicRepublicOfPakistan = 0x0420
Uzbek_Cyrillic_Uzbekistan = 0x0843
Uzbek_Latin_Uzbekistan = 0x0443
Vietnamese_Vietnam = 0x042A
Welsh_UnitedKingdom = 0x0452
Wolof_Senegal = 0x0488
Yakut_Russia = 0x0485
Yi_PRC = 0x0478
Yoruba_Nigeria = 0x046A
def __str__ ( self ) - > str : return self . _name_
def languageID_cls_from_platform_ID ( platformID : PlatformID ) - > Callable [ [ int ] , LanguageID ] :
match platformID :
case PlatformID . Macintosh : return MacintoshLanguageID
case PlatformID . Windows : return WindowsLanguageID
case _ :
assert False , f " Unimplemented: platformID: { platformID } "
assert False , platformID
def parse_language_ID ( f : BinaryIO , platformID : PlatformID ) - > LanguageID :
2024-05-04 11:34:37 +10:00
return read_id ( f , languageID_cls_from_platform_ID ( platformID ) )
2024-05-03 21:02:58 +10:00
class NameID ( ABE ) : pass
class PredefinedNameID ( NameID , Enum ) :
COPYRIGHT_NOTICE = 0
FAMILY = 1
SUBFAMILY = 2
UNIQUE_ID = 3
FULL_NAME = 4
VERSION = 5
POST_SCRIPT_NAME = 6
TRADEMARK = 7
MANUFACTURER = 8
DESIGNER = 9
DESCRIPTION = 10
VENDOR_URL = 11
DESIGNER_URL = 12
LICENSE = 13
LICENSE_URL = 14
# RESERVED
TYPOGRAPHIC_FAMILY = 16
TYPOGRAPHIC_SUBFAMILY = 17
COMPATIBLE_FULL = 18
SAMPLE_TEXT = 19
POST_SCRIPT_CID = 20
WWS_FAMILY = 21
WWS_SUBFAMILY = 22
LIGHT_BACKGROUND_PALETTE = 23
DARK_BACKGROUND_PALETTE = 24
VARIATIONS_POST_SCRIPT_NAME_PREFIX = 25
@dataclass
class FontSpecificNameID ( NameID ) :
nameID : int
def parse_name_ID ( f : BinaryIO ) - > NameID :
2024-05-04 11:34:37 +10:00
return read_id_from_ranges ( f , ( 255 , PredefinedNameID ) , ( 32767 , FontSpecificNameID ) , umbrellaIdCls = NameID )
2024-05-03 21:02:58 +10:00
@dataclass
class NameRecord :
platformID : PlatformID
encodingID : EncodingID
languageID : LanguageID
nameID : NameID
length : int
string : str
def parse_name_record ( f : BinaryIO , stringBytes : BinaryIO ) - > NameRecord :
2024-05-04 11:34:37 +10:00
platformID = read_id ( f , PlatformID )
2024-05-03 21:02:58 +10:00
encodingID = parse_encoding_ID ( f , platformID )
languageID = parse_language_ID ( f , platformID )
nameID = parse_name_ID ( f )
length = read_u16 ( f )
offset = read_u16 ( f )
stringBytes . seek ( offset )
string = parse_string_with_encoding_ID ( stringBytes , length , encodingID )
return NameRecord ( platformID , encodingID , languageID , nameID , length , string )
@dataclass
class NameTable_Format_0 ( NameTable ) :
count : int
nameRecord : List [ NameRecord ]
2024-09-15 16:10:41 +10:00
def parse_name_table ( f : BinaryIO , length : int ) - > NameTable :
2024-05-03 21:02:58 +10:00
start_tell = f . tell ( )
format = read_u16 ( f )
assert format in [ 0 , 1 ]
match format :
case 0 :
count = read_u16 ( f )
stringOffset = read_u16 ( f )
stringLength = length - stringOffset
with SaveTell ( f ) :
stringBytes = parse_at_offset ( f , start_tell , stringOffset , lambda f : BytesIO ( f . read ( stringLength ) ) )
nameRecord = [ parse_name_record ( f , stringBytes ) for _ in range ( count ) ]
return NameTable_Format_0 ( format , count , nameRecord )
case _ :
assert False , f " Unimplemented: format: { format } "
2024-09-15 16:10:41 +10:00
assert False , format
2024-05-03 21:02:58 +10:00
@dataclass
class VendorTag :
achVendID : str
2024-05-04 11:34:37 +10:00
def parse_vendor_tag ( f : BinaryIO ) - > VendorTag :
return read_tag ( f , VendorTag , strict = False )
2024-05-03 21:02:58 +10:00
@dataclass
class OS2Table ( Table , ABD ) :
version : int
xAvgCharWidth : int
usWeightClass : int
usWidthClass : int
fsType : int
ySubscriptXSize : int
ySubscriptYSize : int
ySubscriptXOffset : int
ySubscriptYOffset : int
ySuperscriptXSize : int
ySuperscriptYSize : int
ySuperscriptXOffset : int
ySuperscriptYOffset : int
yStrikeoutSize : int
yStrikeoutPosition : int
sFamilyClass : int
panose : Tuple [ int , int , int , int , int , int , int , int , int , int ]
ulUnicodeRange1 : int
ulUnicodeRange2 : int
ulUnicodeRange3 : int
ulUnicodeRange4 : int
achVendID : VendorTag
fsSelection : int
usFirstCharIndex : int
usLastCharIndex : int
sTypoAscender : int
sTypoDescender : int
sTypoLineGap : int
usWinAscent : int
usWinDescent : int
2024-09-15 16:10:41 +10:00
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 "
2024-05-03 21:02:58 +10:00
@dataclass
class OS2Table_Ver_0 ( OS2Table ) : pass
@dataclass
class OS2Table_Ver_1 ( OS2Table_Ver_0 ) :
ulCodePageRange1 : int
ulCodePageRange2 : int
@dataclass
class OS2Table_Ver_2 ( OS2Table_Ver_1 ) :
sxHeight : int
sCapHeight : int
usDefaultChar : int
usBreakChar : int
usMaxContext : int
@dataclass
class OS2Table_Ver_3 ( OS2Table_Ver_2 ) : pass
@dataclass
class OS2Table_Ver_4 ( OS2Table_Ver_3 ) : pass
@dataclass
class OS2Table_Ver_5 ( OS2Table_Ver_4 ) :
usLowerOpticalPointSize : int
usUpperOpticalPointSize : int
def parse_OS2_table ( f : BinaryIO ) - > OS2Table :
version = read_u16 ( f )
assert version in [ 0 , 1 , 2 , 3 , 4 , 5 ]
xAvgCharWidth = read_i16 ( f )
usWeightClass , usWidthClass = read_u16 ( f ) , read_u16 ( f )
fsType = read_u16 ( f )
ySubscriptXSize , ySubscriptYSize , ySubscriptXOffset , ySubscriptYOffset , ySuperscriptXSize , ySuperscriptYSize , ySuperscriptXOffset , ySuperscriptYOffset , yStrikeoutSize , yStrikeoutPosition = [ read_i16 ( f ) for _ in range ( 10 ) ]
sFamilyClass = read_i16 ( f )
panose0 , panose1 , panose2 , panose3 , panose4 , panose5 , panose6 , panose7 , panose8 , panose9 = [ read_u8 ( f ) for _ in range ( 10 ) ]
panose = ( panose0 , panose1 , panose2 , panose3 , panose4 , panose5 , panose6 , panose7 , panose8 , panose9 )
ulUnicodeRange1 , ulUnicodeRange2 , ulUnicodeRange3 , ulUnicodeRange4 = [ read_u32 ( f ) for _ in range ( 4 ) ]
2024-05-04 11:34:37 +10:00
achVendID = parse_vendor_tag ( f )
2024-05-03 21:02:58 +10:00
fsSelection = read_u16 ( f )
usFirstCharIndex , usLastCharIndex = read_u16 ( f ) , read_u16 ( f )
sTypoAscender , sTypoDescender , sTypoLineGap = read_i16 ( f ) , read_i16 ( f ) , read_i16 ( f )
usWinAscent , usWinDescent = read_u16 ( f ) , read_u16 ( f )
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 )
ulCodePageRange1 , ulCodePageRange2 = read_u32 ( f ) , read_u32 ( f )
if version == 1 :
return OS2Table_Ver_1 ( 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 , ulCodePageRange1 , ulCodePageRange2 )
sxHeight , sCapHeight = read_i16 ( f ) , read_i16 ( f )
usDefaultChar , usBreakChar , usMaxContext = read_u16 ( f ) , read_u16 ( f ) , read_u16 ( f )
if version == 2 :
return OS2Table_Ver_2 ( 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 , ulCodePageRange1 , ulCodePageRange2 , sxHeight , sCapHeight , usDefaultChar , usBreakChar , usMaxContext )
if version == 3 :
return OS2Table_Ver_3 ( 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 , ulCodePageRange1 , ulCodePageRange2 , sxHeight , sCapHeight , usDefaultChar , usBreakChar , usMaxContext )
if version == 4 :
return OS2Table_Ver_4 ( 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 , ulCodePageRange1 , ulCodePageRange2 , sxHeight , sCapHeight , usDefaultChar , usBreakChar , usMaxContext )
usLowerOpticalPointSize , usUpperOpticalPointSize = read_u16 ( f ) , read_u16 ( f )
if version == 5 :
return OS2Table_Ver_5 ( 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 , ulCodePageRange1 , ulCodePageRange2 , sxHeight , sCapHeight , usDefaultChar , usBreakChar , usMaxContext , usLowerOpticalPointSize , usUpperOpticalPointSize )
assert False , f " Unimplemented: version: { version } "
# TODO: Maybe make this use formats instead?
@dataclass
class PostTable ( Table , ABD ) :
"""
NOTE : Different versions ( even among minor versions ) are not necessarily backwards compatible
"""
version : float
italicAngle : float
underlinePosition : int
underlineThickness : int
isFixedPitch : int
minMemType42 : int
maxMemType42 : int
minMemType1 : int
maxMemType1 : int
@dataclass
class PostTable_Ver_1_0 ( PostTable ) : pass
@dataclass
class PostTable_Ver_2_0 ( PostTable ) :
numGlyphs : int
glyphNameIndex : List [ int ]
names : List [ str ]
@dataclass
class PostTable_Ver_3_0 ( PostTable ) : pass
def parse_post_table ( f : BinaryIO , length : int ) - > PostTable :
start_tell = f . tell ( )
version = read_fixed_version ( f )
assert version in [ 1.0 , 2.0 , 2.5 , 3.0 , 4.0 ] # Apple documentation says that there is a version 4.0 (format4)
italicAngle = read_fixed ( f )
underlinePosition = read_i16 ( f )
underlineThickness = read_i16 ( f )
isFixedPitch = read_u32 ( f )
minMemType42 = read_u32 ( f )
maxMemType42 = read_u32 ( f )
minMemType1 = read_u32 ( f )
maxMemType1 = read_u32 ( f )
match version :
case 1.0 :
assert False # TODO: I think that that's it?
case 2.0 :
numGlyphs = read_u16 ( f )
glyphNameIndex = [ read_u16 ( f ) for _ in range ( numGlyphs ) ]
names : List [ str ] = [ ]
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 ) )
2024-09-15 16:10:41 +10:00
assert f . tell ( ) - start_tell == length
2024-05-03 21:02:58 +10:00
return PostTable_Ver_2_0 ( version , italicAngle , underlinePosition , underlineThickness , isFixedPitch , minMemType42 , maxMemType42 , minMemType1 , maxMemType1 , numGlyphs , glyphNameIndex , names )
case 3.0 :
return PostTable_Ver_3_0 ( version , italicAngle , underlinePosition , underlineThickness , isFixedPitch , minMemType42 , maxMemType42 , minMemType1 , maxMemType1 )
case _ :
assert False , f " Unimplemented: version: { version } "
assert False , version
# 5.5
@dataclass
class SvgTable ( Table ) :
pass
def parse_svg_table ( f : BinaryIO ) - > SvgTable :
assert False
# 5.6
@dataclass
class SignatureBlock ( ABD ) : pass
@dataclass
class SignatureBlock_Format_1 ( SignatureBlock ) :
signatureLength : int
signature : bytes # PKCS#7 packet
def parse_signature_block ( f : BinaryIO , format : int , length : int ) - > SignatureBlock :
assert format in [ 1 ]
start_tell = f . tell ( )
match format :
case 1 :
assert read_u16 ( f ) == 0 , " Reserved1 "
assert read_u16 ( f ) == 0 , " Reserved2 "
signatureLength = read_u32 ( f )
signature = f . read ( signatureLength )
assert f . tell ( ) - start_tell == length , ( f . tell ( ) - start_tell , length )
return SignatureBlock_Format_1 ( signatureLength , signature )
case _ :
assert False , f " Unimplemented: format: { format } "
assert False , format
@dataclass
class SignatureRecord :
format : int
length : int
signatureBlock : SignatureBlock
def parse_signature_record ( f : BinaryIO , start_tell : int ) - > SignatureRecord :
format = read_u32 ( f )
length = read_u32 ( f )
signatureBlockOffset = read_u32 ( f )
with SaveTell ( f ) :
signatureBlock = parse_at_offset ( f , start_tell , signatureBlockOffset , lambda f : parse_signature_block ( f , format , length ) )
return SignatureRecord ( format , length , signatureBlock )
@dataclass
class DSIGTable ( Table , ABD ) :
version : int # for some reasons it's u32?
numSignatures : int
flags : int # It's u16 but only bits 0-7 are documented?
signatureRecords : List [ SignatureRecord ] # there's a typo in the ISO documentation.
2024-09-15 16:10:41 +10:00
def __post_init__ ( self ) :
assert self . flags & 0xfe == 0 , " Reserved "
2024-05-03 21:02:58 +10:00
@dataclass
class DSIGTable_Ver_1 ( DSIGTable ) : pass
def parse_DSIG_table ( f : BinaryIO ) - > DSIGTable :
start_tell = f . tell ( )
version = read_u32 ( f )
assert version in [ 1 ]
numSignatures = read_u16 ( f )
flags = read_u16 ( f )
signatureRecords = [ parse_signature_record ( f , start_tell ) for _ in range ( numSignatures ) ]
if version == 1 :
return DSIGTable_Ver_1 ( version , numSignatures , flags , signatureRecords )
assert False , f " Unimplemented: version: { version } "
@dataclass
class DeviceRecord :
# format: int # format seems to be a constant 0?
pixelSize : int
maxWidth : int
widths : List [ int ]
def parse_device_record ( f : BinaryIO , size : int , numGlyphs : int ) - > DeviceRecord :
start_tell = f . tell ( )
pixelSize = read_u8 ( f )
maxWidth = read_u8 ( f )
widths = [ read_u8 ( f ) for _ in range ( numGlyphs ) ]
# long aligning
assert size - 4 < f . tell ( ) - start_tell < = size
f . seek ( start_tell + size )
return DeviceRecord ( pixelSize , maxWidth , widths )
@dataclass
class HdmxTable ( Table ) :
version : int
numRecords : int
sizeDeviceRecord : int
records : List [ DeviceRecord ]
def parse_hdmx_table ( f : BinaryIO , numGlyphs : int ) - > HdmxTable :
version = read_u16 ( f )
assert version in [ 0 ]
numRecords = read_i16 ( f ) # I don't know why numRecords can be negative?
sizeDeviceRecord = read_i32 ( f ) # Similarly, why can a size be negative?
records = [ parse_device_record ( f , sizeDeviceRecord , numGlyphs ) for _ in range ( numRecords ) ]
return HdmxTable ( version , numRecords , sizeDeviceRecord , records )
2024-09-15 16:17:20 +10:00
@dataclass
class KernSubtableCoverage :
bytes : int
def horizontal ( self ) - > int : return self . bytes & 1
def minimum ( self ) - > int : return self . bytes >> 1 & 1
def crossstream ( self ) - > int : return self . bytes >> 2 & 1
def override ( self ) - > int : return self . bytes >> 3 & 1
def format ( self ) - > int : return self . bytes >> 8 & 0x1000
def __post_init__ ( self ) :
assert self . bytes >> 4 & 0x10 == 0 , self . bytes
assert self . format ( ) in [ 0 , 2 ]
@dataclass
class KernSubtable ( Table , ABD ) :
version : int
length : int
coverage : KernSubtableCoverage
@dataclass
class KerningPairValue :
left : int
right : int
value : float
def parse_kerning_pair_value ( f : BinaryIO ) - > KerningPairValue :
left = read_u16 ( f )
right = read_u16 ( f )
value = read_i16 ( f )
return KerningPairValue ( left , right , value )
@dataclass
class KernSubtable_Format_0 ( KernSubtable ) :
nPairs : int
searchRange : int
entrySelector : int
rangeShift : int
pairs : List [ KerningPairValue ]
def parse_kern_subtable ( f : BinaryIO ) - > KernSubtable :
start_tell = f . tell ( )
version = read_u16 ( f )
assert version in [ 0 ]
length = read_u16 ( f )
coverage = KernSubtableCoverage ( read_u16 ( f ) )
match coverage . format ( ) :
case 0 :
nPairs = read_u16 ( f )
searchRange = read_u16 ( f )
entrySelector = read_u16 ( f )
rangeShift = read_u16 ( f )
pairs = [ parse_kerning_pair_value ( f ) for _ in range ( nPairs ) ]
assert f . tell ( ) - start_tell == length , ( f . tell ( ) , start_tell , length )
return KernSubtable_Format_0 ( version , length , coverage , nPairs , searchRange , entrySelector , rangeShift , pairs )
case 2 :
assert False
case _ :
assert False , f " Unimplemented: format: { coverage . format ( ) } "
assert False , ( version , length , coverage )
2024-05-03 21:02:58 +10:00
@dataclass
class KernTable ( Table ) :
2024-09-15 16:17:20 +10:00
version : int
nTables : int
subtables : List [ KernSubtable ]
2024-05-03 21:02:58 +10:00
def parse_Kern_table ( f : BinaryIO ) - > KernTable :
2024-09-15 16:17:20 +10:00
version = read_u16 ( f )
assert version in [ 0 ]
nTables = read_u16 ( f )
subtables = [ parse_kern_subtable ( f ) for _ in range ( nTables ) ]
return KernTable ( version , nTables , subtables )
2024-05-03 21:02:58 +10:00
@dataclass
class LTSHTable ( Table ) :
2024-09-15 16:17:20 +10:00
version : int
numGlyphs : int
yPels : List [ int ]
@dataclass
class LTSHTable_Ver_0 ( LTSHTable ) : pass
2024-05-03 21:02:58 +10:00
def parse_LTSH_table ( f : BinaryIO ) - > LTSHTable :
2024-09-15 16:17:20 +10:00
version = read_u16 ( f )
assert version in [ 0 ]
numGlyphs = read_u16 ( f )
yPels = [ read_u8 ( f ) for _ in range ( numGlyphs ) ]
if version == 0 :
return LTSHTable_Ver_0 ( version , numGlyphs , yPels )
assert False , f " Unimplemented: version: { version } "
2024-05-03 21:02:58 +10:00
@dataclass
class PCLTTable ( Table ) :
pass
def parse_PCLT_table ( f : BinaryIO ) - > PCLTTable :
assert False
2024-09-15 16:17:20 +10:00
@dataclass
class RatioRangeRecord ( Record ) :
bCharSet : int
xRatio : int
yStartRatio : int
yEndRatio : int
def parse_ratio_range_record ( f : BinaryIO ) - > RatioRangeRecord :
bCharSet = read_u8 ( f )
xRatio = read_u8 ( f )
yStartRatio = read_u8 ( f )
yEndRatio = read_u8 ( f )
return RatioRangeRecord ( bCharSet , xRatio , yStartRatio , yEndRatio )
@dataclass
class VTableRecord ( Record ) :
yPelHeight : int
yMax : int
yMin : int
def parse_v_table_record ( f : BinaryIO ) - > VTableRecord :
yPelHeight = read_u16 ( f )
yMax = read_i16 ( f )
yMin = read_i16 ( f )
return VTableRecord ( yPelHeight , yMax , yMin )
@dataclass
class VDMXGroup :
recs : int
startsz : int
endsz : int
entry : List [ VTableRecord ]
def parse_vdmx_group ( f : BinaryIO ) - > VDMXGroup :
recs = read_u16 ( f )
startsz = read_u8 ( f )
endsz = read_u8 ( f )
entry = [ parse_v_table_record ( f ) for _ in range ( recs ) ]
return VDMXGroup ( recs , startsz , endsz , entry )
2024-05-03 21:02:58 +10:00
@dataclass
class VDMXTable ( Table ) :
2024-09-15 16:17:20 +10:00
version : int
numRecs : int
numRatios : int
ratRange : List [ RatioRangeRecord ]
groups_by_ratio : List [ VDMXGroup ] # same objects as in groups, but ordered by which ratio references them
groups : List [ VDMXGroup ]
2024-05-03 21:02:58 +10:00
def parse_VDMX_table ( f : BinaryIO ) - > VDMXTable :
2024-09-15 16:17:20 +10:00
start_tell = f . tell ( )
version = read_u16 ( f )
assert version in [ 0 , 1 ]
numRecs = read_u16 ( f )
numRatios = read_u16 ( f )
ratRange = [ parse_ratio_range_record ( f ) for _ in range ( numRecs ) ]
offsets = [ read_u16 ( f ) for _ in range ( numRatios ) ]
groups_by_ratio , groups = parse_list_and_use_offsets_into ( f , start_tell , offsets , numRecs , parse_vdmx_group )
return VDMXTable ( version , numRecs , numRatios , ratRange , groups_by_ratio , groups )
2024-05-03 21:02:58 +10:00
# NOTE: Different versions of VheaTable are not backwards compatible due to differences in three fields
@dataclass
class VheaTable ( Table ) :
version : float
# ascent / vertTypoAscender
# descent / vertTypoDescender
# lineGap / vertTypoLineGap
advanceHeightMax : int
minTopSideBearing : int
minBottomSideBearing : int
yMaxExtent : int
caretSlopeRise : int
caretSlopeRun : int
caretOffset : int
metricDataFormat : int
numOfLongVerMetris : int
@dataclass
class VheaTable_Ver_1_0 ( VheaTable ) :
ascent : int
descent : int
lineGap : int
@dataclass
class VheaTable_Ver_1_1 ( VheaTable ) :
vertTypeAscender : int
vertTypoDescender : int
vertTypoLineGap : int
def parse_vhea_table ( f : BinaryIO ) - > VheaTable :
version = read_fixed_version ( f )
assert version in [ 1.0 , 1.1 ] , f " Invalid version: { version } "
match version :
case 1.0 :
ascent = read_i16 ( f )
descent = read_i16 ( f )
lineGap = read_i16 ( f )
assert lineGap == 0 , " Reserved "
case 1.1 :
vertTypeAscender = read_i16 ( f )
vertTypoDescender = read_i16 ( f )
vertTypoLineGap = read_i16 ( f )
case _ :
assert False , f " Unimplemented: version: { version } "
advanceHeightMax = read_i16 ( f )
minTopSideBearing = read_i16 ( f )
minBottomSideBearing = read_i16 ( f )
yMaxExtent = read_i16 ( f )
caretSlopeRise = read_i16 ( f )
caretSlopeRun = read_i16 ( f )
caretOffset = read_i16 ( f )
for _ in range ( 4 ) : assert read_i16 ( f ) == 0 , " Reserved "
metricDataFormat = read_i16 ( f )
assert metricDataFormat == 0
numOfLongVerMetris = read_u16 ( f )
if version == 1.0 :
return VheaTable_Ver_1_0 ( version , advanceHeightMax , minTopSideBearing , minBottomSideBearing , yMaxExtent , caretSlopeRise , caretSlopeRun , caretOffset , metricDataFormat , numOfLongVerMetris , ascent , descent , lineGap )
if version == 1.1 :
return VheaTable_Ver_1_1 ( version , advanceHeightMax , minTopSideBearing , minBottomSideBearing , yMaxExtent , caretSlopeRise , caretSlopeRun , caretOffset , metricDataFormat , numOfLongVerMetris , vertTypeAscender , vertTypoDescender , vertTypoLineGap )
assert False , f " Unimplemented: version: { version } "
@dataclass
class VerticalMetricsEntry :
advanceHeight : int
topSideBearing : int
def parse_vertical_metrics_entry ( f : BinaryIO ) - > VerticalMetricsEntry :
advanceHeight = read_u16 ( f )
topSideBearing = read_i16 ( f )
return VerticalMetricsEntry ( advanceHeight , topSideBearing )
@dataclass
class VmtxTable ( Table ) :
vMetrics : List [ VerticalMetricsEntry ]
topSideBearing : List [ int ]
def parse_vmtx_table ( f : BinaryIO , numOfLongVerMetrics : int , numGlyphs : int ) - > VmtxTable :
vMetrics = [ parse_vertical_metrics_entry ( f ) for _ in range ( numOfLongVerMetrics ) ]
topSideBearing = [ read_i16 ( f ) for _ in range ( numGlyphs - numOfLongVerMetrics ) ]
return VmtxTable ( vMetrics , topSideBearing )
@dataclass
class BaseGlyphRecord :
pass
def parse_base_glyph_record ( f : BinaryIO ) - > BaseGlyphRecord :
assert False
@dataclass
class LayerRecord :
pass
def parse_layer_record ( f : BinaryIO ) - > LayerRecord :
assert False
@dataclass
class COLRTable ( Table , ABD ) :
version : int
numBaseGlyphRecords : int
baseGlyphRecord : List [ BaseGlyphRecord ]
layerRecords : List [ LayerRecord ]
numLayerRecords : int
@dataclass
class COLRTable_Ver_0 ( COLRTable ) : pass
@dataclass
class BaseGlyphPaintRecord :
pass
def parse_base_glyph_paint_record ( f : BinaryIO ) - > BaseGlyphPaintRecord :
assert False
@dataclass
class BaseGlyphListTable ( Table ) :
numBaseGlyphPaintRecords : int
baseGlyphPaintRecords : List [ BaseGlyphPaintRecord ]
def parse_base_glyph_list_table ( f : BinaryIO ) - > BaseGlyphListTable :
assert False
@dataclass
class LayerListTable ( Table ) :
pass
def parse_layer_list_table ( f : BinaryIO ) - > LayerListTable :
assert False
@dataclass
class ClipListTable ( Table ) :
pass
def parse_clip_list_table ( f : BinaryIO ) - > ClipListTable :
assert False
@dataclass
class COLRTable_Ver_1 ( COLRTable_Ver_0 ) :
baseGlyphList : BaseGlyphListTable
layerList : Optional [ LayerListTable ]
clipList : Optional [ ClipListTable ]
varIndexMap : Optional [ ' DeltaSetIndexMapTable ' ]
itemVariationStore : Optional [ ' ItemVariationStoreTable ' ]
def parse_COLR_table ( f : BinaryIO ) - > COLRTable :
version = read_u16 ( f )
assert version in [ 0 , 1 ] , f " Invalid version: { version } "
return COLRTable_Ver_0 ( version , 0 , [ ] , [ ] , 0 )
assert False , f " Unimplemented: version: { version } "
@dataclass
class CPALTable ( Table ) :
pass
def parse_CPAL_table ( f : BinaryIO ) - > CPALTable :
assert False
# 6.2
def is_CCXX ( CC : str , tag : str ) - > bool :
assert len ( CC ) == 2
return len ( tag ) == 4 and tag [ : 2 ] == CC and tag [ 2 : ] . isdigit ( )
class CCXXTag ( ABE ) :
__CC__ = ' '
__range__ = ( 1 , 99 ) # default range is CC01 - CC99
def __init__ ( self , tag : str ) :
assert len ( self . __CC__ ) == 2 , f " Subclass of CCXXTag must defined a __CC__ of length two "
if not is_CCXX ( self . __CC__ , tag ) : raise ValueError
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 ] } . " )
2024-09-15 16:10:41 +10:00
# @classmethod
# def from_num(cls, num: int) -> Self:
# 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
2024-05-03 21:02:58 +10:00
def __str__ ( self ) - > str :
return f " ' { self . __CC__ } { self . num : 0>2 } ' "
2024-09-15 16:10:41 +10:00
@property
def name ( self ) - > str :
return str ( self )
2024-05-03 21:02:58 +10:00
class ScriptTag ( Enum ) :
Adlam = ' adlm '
Ahom = ' ahom '
AnatolianHieroglyphs = ' hluw '
Arabic = ' arab '
Armenian = ' armn '
Avestan = ' avst '
Balinese = ' bali '
Bamum = ' bamu '
BassaVah = ' bass '
Batak = ' batk '
Bengali = ' beng '
Bengali_v2 = ' bng2 '
Bhaiksuki = ' bhks '
Bopomofo = ' bopo '
Brahmi = ' brah '
Braille = ' brai '
Buginese = ' bugi '
Buhid = ' buhd '
ByzantineMusic = ' byzm '
CanadianSyllabics = ' cans '
Carian = ' cari '
CaucasianAlbanian = ' aghb '
Chakma = ' cakm '
Cham = ' cham '
Cherokee = ' cher '
CJKIdeographic = ' hani '
Coptic = ' copt '
CypriotSyllabary = ' cprt '
Cyrillic = ' cyrl '
Default = ' DFLT '
Deseret = ' dsrt '
Devanagari = ' deva '
Devanagari_v2 = ' dev2 '
Dogra = ' dogr '
Duployan = ' dupl '
EgyptianHieroglyphs = ' egyp '
Elbasan = ' elba '
Ethiopic = ' ethi '
Georgian = ' geor '
Glagolitic = ' glag '
Gothic = ' goth '
Grantha = ' gran '
Greek = ' grek '
Gujarati = ' gujr '
Gujarati_v2 = ' gjr2 '
GunjalaGondi = ' gong '
Gurmukhi = ' guru '
Gurmukhi_v2 = ' gur2 '
Hangul = ' hang '
HangulJamo = ' jamo '
HanifiRohingya = ' rohg '
Hanunoo = ' hano '
Hatran = ' hatr '
Hebrew = ' hebr '
Hiragana = ' kana '
ImperialAramaic = ' armi '
InscriptionalPahlavi = ' phli '
InscriptionalParthian = ' prti '
Javanese = ' java '
Kaithi = ' kthi '
Kannada = ' knda '
Kannada_v2 = ' knd2 '
Katakana = ' kana '
KayahLi = ' kali '
Kharosthi = ' khar '
Khmer = ' khmr '
Khojki = ' khoj '
Khudawadi = ' sind '
Lao = ' lao '
Latin = ' latn '
Lepcha = ' lepc '
Limbu = ' limb '
LinearA = ' lina '
LinearB = ' linb '
Lisu_Fraser = ' lisu '
Lycian = ' lyci '
Lydian = ' lydi '
Mahajani = ' mahj '
Makasar = ' maka '
Malayalam = ' mlym '
Malayalam_v2 = ' mlm2 '
Mandaic_Mandaean = ' mand '
Manichaean = ' mani '
MasaramGondi = ' gonm '
Marchen = ' marc '
MathematicalAlphanumericSymbols = ' math '
Medefaidrin_OberiOkaime_OberiƆkaimɛ = ' medf ' # TODO: Maybe remove non-ascii characters?
MeiteiMayek_Meithei_Meetei = ' mtei '
MendeKikakui = ' mend '
MeroiticCursive = ' merc '
MeroiticHieroglyphs = ' mero '
Miao = ' plrd '
Modi = ' modi '
Mongolian = ' mong '
Mro = ' mroo '
Multani = ' mult '
MusicalSymbols = ' musc '
Myanmar = ' mymr '
Myanmar_v2 = ' mym2 '
Nabataean = ' nbat '
Newa = ' newa '
NewTaiLue = ' talu '
NKo = ' nko '
Nüshu = ' nshu '
Odia_formerlyOriya = ' orya '
Odia_formerlyOriya_v2 = ' ory2 '
Ogham = ' ogam '
OlChiki = ' olck '
OldItalic = ' ital '
OldHungarian = ' hung '
OldNorthArabian = ' narb '
OldPermic = ' perm '
OldPersianCuneiform = ' xpeo '
OldSogdian = ' sogo '
OldSouthArabian = ' sarb '
OldTurkic_OrkhonRunic = ' orkh '
Osage = ' osge '
Osmanya = ' osma '
PahawhHmong = ' hmng '
Palmyrene = ' palm '
PauCinHau = ' pauc '
PhagsPa = ' phag '
Phoenician = ' phnx '
PsalterPahlavi = ' phlp '
Rejang = ' rjng '
Runic = ' runr '
Samaritan = ' samr '
Saurashtra = ' saur '
Sharada = ' shrd '
Shavian = ' shaw '
Siddham = ' sidd '
SignWriting = ' sgnw '
Sinhala = ' sinh '
Sogdian = ' sogd '
SoraSompeng = ' sora '
Soyombo = ' soyo '
SumeroAkkadianCuneiform = ' xsux '
Sundanese = ' sund '
SylotiNagri = ' sylo '
Syriac = ' syrc '
Tagalog = ' tglg '
Tagbanwa = ' tagb '
TaiLe = ' tale '
TaiTham_Lanna = ' lana '
TaiViet = ' tavt '
Takri = ' takr '
Tamil = ' taml '
Tamil_v2 = ' tml2 '
Tangut = ' tang '
Telugu = ' telu '
Telugu_v2 = ' tel2 '
Thaana = ' thaa '
Thai = ' thai '
Tibetan = ' tibt '
Tifinagh = ' tfng '
Tirhuta = ' tirh '
UgariticCuneiform = ' ugar '
Vai = ' vai '
WarangCiti = ' wara '
Yi = ' yi \x20 \x20 '
ZanabazarSquare_ZanabazarinDörböljinUseg_XewteeDörböljinBicig_HorizontalSquareScript = ' zanb ' # TODO: Maybe remove non-ascii characters?
def __str__ ( self ) - > str : return self . _name_
@dataclass
class LangSysTable ( Table ) :
lookupOrder : Optional [ Table ] # documentation doesn't define the table for this
requiredFeatureIndex : int
featureIndexCount : int
featuresIndices : List [ int ]
def parse_lang_sys_table ( f : BinaryIO ) - > LangSysTable :
lookupOrderOffset = read_u16 ( f )
assert lookupOrderOffset == 0 , " Reserved "
lookupOrder = None
requiredFeatureIndex = read_u16 ( f )
featureIndexCount = read_u16 ( f )
featureIndices = [ read_u16 ( f ) for _ in range ( featureIndexCount ) ]
return LangSysTable ( lookupOrder , requiredFeatureIndex , featureIndexCount , featureIndices )
class LangSysTag ( ABE ) : pass
class ValidLangSysTag ( LangSysTag , Enum ) :
# Comments are the ISO 639 Codes
Abaza = ' ABA ' # abq
Abkhazian = ' ABK ' # abk
Acholi = ' ACH ' # ach
Achi = ' ACR ' # acr
Adyghe = ' ADY ' # ady
Afrikaans = ' AFK ' # afr
Afar = ' AFR ' # aar
Agaw = ' AGW ' # ahg
Aiton = ' AIO ' # aio
Akan = ' AKA ' # aka
Alsatian = ' ALS ' # gsw
Altai = ' ALT ' # atv, alt
Amharic = ' AMH ' # amh
AngloSaxon = ' ANG ' # ang
PhoneticTranscription_AmericanistConventions = ' APPH '
Arabic = ' ARA ' # ara
Aragonese = ' ARG ' # arg
Aari = ' ARI ' # aiw
Rakhine = ' ARK ' # mhv, rmz, rki
Assamese = ' ASM ' # asm
Asturian = ' AST ' # ast
Athapaskan = ' ATH ' # apk, apj, apl, apm, apw, nav, bea, sek, bcr, caf, crx, clc, gwi, haa, chp, dgr, scs, xsl, srs, ing, hoi, koy, hup, ktw, mvb, wlk, coq, ctc, gce, tol, tuu, kkz, tgx, tht, aht, tfn, taa, tau, tcb, kuu, tce, ttm, txc
Avar = ' AVR ' # ava
Awadhi = ' AWA ' # awa
Aymara = ' AYM ' # aym
Torki = ' AZB ' # azb
Azerbaijani = ' AZE ' # aze
Badaga = ' BAD ' # bfq
Banda = ' BAD0 ' # bad
Baghelkhandi = ' BAG ' # bfy
Balkar = ' BAL ' # krc
Balinese = ' BAN ' # ban
Bavarian = ' BAR ' # bar
Baoulé = ' BAU ' # bci
BatakToba = ' BBC ' # bbc
Berber = ' BBR '
Bench = ' BCH ' # bcq
BibleCree = ' BCR '
Bandjalang = ' BDY ' # bdy
Belarusian = ' BEL ' # bel
Bemba = ' BEM ' # bem
Bengali = ' BEN ' # ben
Haryanvi = ' BGC ' # bgc
Bagri = ' BGQ ' # bgq
Bulgarian = ' BGR ' # bul
Bhili = ' BHI ' # bhi, bhb
Bhojpuri = ' BHO ' # bho
Bikol = ' BIK ' # bik, bhk, bcl, bto, cts, bln
Bilen = ' BIL ' # byn
Bislama = ' BIS ' # bis
Kanauji = ' BJJ ' # bjj
Blackfoot = ' BKF ' # bla
Baluchi = ' BLI ' # bal
PaoKaren = ' BLK ' # blk
Balante = ' BLN ' # bjt, ble
Balti = ' BLT ' # bft
Bambara_Bamanankan = ' BMB ' # bam
Bamileke = ' BML '
Bosnian = ' BOS ' # bos
BishnupriyaManipuri = ' BPY ' # bpy
Breton = ' BRE ' # bre
Brahui = ' BRH ' # brh
BrajBhasha = ' BRI ' # bra
Burmese = ' BRM ' # mya
Bodo = ' BRX ' # brx
Bashkir = ' BSH ' # bak
Burushaski = ' BSK ' # bsk
Beti = ' BTI ' # btb
BatakSimalungun = ' BTS ' # bts
Bugis = ' BUG ' # bug
Medumba = ' BYV ' # byv
Kaqchikel = ' CAK ' # cak
Catalan = ' CAT ' # cat
ZamboangaChavacano = ' CBK ' # cbk
Chinantec = ' CCHN ' # cco, chj, chq, chz, cle, cnl, cnt, cpa, csa, cso, cte, ctl, cuc, cvn
Cebuano = ' CEB ' # ceb
Chechen = ' CHE ' # che
ChahaGurage = ' CHG ' # sgw
Chattisgarhi = ' CHH ' # hne
Chichewa_Chewa_Nyanja = ' CHI ' # nya
Chukchi = ' CHK ' # ckt
Chuukese = ' CHK0 ' # chk
Choctaw = ' CHO ' # cho
Chipewyan = ' CHP ' # chp
Cherokee = ' CHR ' # chr
Chamorro = ' CHA ' # cha
Chuvash = ' CHU ' # chv
Cheyenne = ' CHY ' # chy
Chiga = ' CGG ' # cgg
WesternCham = ' CJA ' # cja
EasternCham = ' CJM ' # cjm
Comorian = ' CMR ' # swb, wlc, wni, zdj
Coptic = ' COP ' # cop
Cornish = ' COR ' # cor
Corsican = ' COS ' # cos
Creoles = ' CPP ' # cpp
Cree = ' CRE ' # cre
Carrier = ' CRR ' # crx, caf
CrimeanTatar = ' CRT ' # crh
Kashubian = ' CSB ' # csb
ChurchSlavonic = ' CSL ' # chu
Czech = ' CSY ' # ces
Chittagonian = ' CTG ' # ctg
SanBlasKuna = ' CUK ' # cuk
Danish = ' DAN ' # dan
Dargwa = ' DAR ' # dar
Dayi = ' DAX ' # dax
WoodsCree = ' DCR ' # cwd
German = ' DEU ' # deu
Dogri = ' DGO ' # dgo
Dogri_ = ' DGR ' # doi
Dhangu = ' DHG ' # dhg
Divehi_Dhivehi_Maldivian_ = ' DHV ' # (deprecated) div
Dimli = ' DIQ ' # diq
Divehi_Dhivehi_Maldivian = ' DIV ' # div
Zarma = ' DJR ' # dje
Djambarrpuyngu = ' DJR0 ' # djr
Dangme = ' DNG ' # ada
Dan = ' DNJ ' # dnj
Dinka = ' DNK ' # din
Dari = ' DRI ' # prs
Dhuwal = ' DUJ ' # duj
Dungan = ' DUN ' # dng
Dzongkha = ' DZN ' # dzo
Ebira = ' EBI ' # igb
EasternCree = ' ECR ' # crj, crl
Edo = ' EDO ' # bin
Efik = ' EFI ' # efi
Greek = ' ELL ' # ell
EasternManinkakan = ' EMK ' # emk
English = ' ENG ' # eng
Erzya = ' ERZ ' # myv
Spanish = ' ESP ' # spa
CentralYupik = ' ESU ' # esu
Estonian = ' ETI ' # est
Basque = ' EUQ ' # eus
Evenki = ' EVK ' # evn
Even = ' EVN ' # eve
Ewe = ' EWE ' # ewe
FrenchAntillean = ' FAN ' # acf
Fang = ' FAN0 ' # fan
Persian = ' FAR ' # fas
Fanti = ' FAT ' # fat
Finnish = ' FIN ' # fin
Fijian = ' FJI ' # fij
Dutch_Flemish = ' FLE ' # vls
Fefe = ' FMP ' # fmp
ForestNenets = ' FNE ' # enf
Fon = ' FON ' # fon
Faroese = ' FOS ' # fao
French = ' FRA ' # fra
CajunFrench = ' FRC ' # frc
Frisian = ' FRI ' # fry
Friulian = ' FRL ' # fur
Arpitan = ' FRP ' # frp
Futa = ' FTA ' # fuf
Fulah = ' FUL ' # ful
NigerianFulfulde = ' FUV ' # fuv
Ga = ' GAD ' # gaa
ScottishGaelic_Gaelic = ' GAE ' # gla
Gagauz = ' GAG ' # gag
Galician = ' GAL ' # glg
Garshuni = ' GAR '
Garhwali = ' GAW ' # gbm
Geez = ' GEZ ' # gez
Githabul = ' GIH ' # gih
Gilyak = ' GIL ' # niv
Kiribati_Gilbertese = ' GIL0 ' # gil
Kpelle_Guinea = ' GKP ' # gkp
Gilaki = ' GLK ' # glk
Gumuz = ' GMZ ' # guk
Gumatj = ' GNN ' # gnn
Gogo = ' GOG ' # gog
Gondi = ' GON ' # gon, gno, ggo
Greenlandic = ' GRN ' # kal
Garo = ' GRO ' # grt
Guarani = ' GUA ' # grn
Wayuu = ' GUC ' # guc
Gupapuyngu = ' GUF ' # guf
Gujarati = ' GUJ ' # guj
Gusii = ' GUZ ' # guz
Haitian_HaitianCreole = ' HAI ' # hat
Halam = ' HAL ' # flm
Harauti = ' HAR ' # hoj
Hausa = ' HAU ' # hau
Hawaiian = ' HAW ' # haw
Haya = ' HAY ' # hay
Hazaragi = ' HAZ ' # haz
HammerBanna = ' HBN ' # amf
Herero = ' HER ' # her
Hiligaynon = ' HIL ' # hil
Hindi = ' HIN ' # hin
HighMari = ' HMA ' # mrj
Hmong = ' HMN ' # hmn
HiriMotu = ' HMO ' # hmo
Hindko = ' HND ' # hno, hnd
Ho = ' HO ' # hoc
Harari = ' HRI ' # har
Croatian = ' HRV ' # hrv
Hungarian = ' HUN ' # hun
Armenian = ' HYE ' # hye
ArmenianEast = ' HYE0 ' # hye
Iban = ' IBA ' # iba
Ibibio = ' IBB ' # ibb
Igbo = ' IBO ' # ibo
IjoLanguages = ' IJO ' # ijc
Ido = ' IDO ' # ido
Interlingue = ' ILE ' # ile
Ilokano = ' ILO ' # ilo
Interlingua = ' INA ' # ina
Indonesian = ' IND ' # ind
Ingush = ' ING ' # inh
Inuktitut = ' INU ' # iku
Inupiat = ' IPK ' # ipk
PhoneticTranscription_IPAConventions = ' IPPH '
Irish = ' IRI ' # gle
IrishTraditional = ' IRT ' # gle
Icelandic = ' ISL ' # isl
InariSami = ' ISM ' # smn
Italian = ' ITA ' # ita
Hebrew = ' IWR ' # heb
Javanese = ' JAV ' # jav
Yiddish = ' JII ' # yid
JamaicanCreole = ' JAM ' # jam
Japanese = ' JAN ' # jpn
Lojban = ' JBO ' # jbo
Krymchak = ' JCT ' # jct
Ladino = ' JUD ' # lad
Jula = ' JUL ' # dyu
Kabardian = ' KAB ' # kbd
Kabyle = ' KAB0 ' # kab
Kachchi = ' KAC ' # kfr
Kalenjin = ' KAL ' # kln
Kannada = ' KAN ' # kan
Karachay = ' KAR ' # krc
Georgian = ' KAT ' # kat
Kazakh = ' KAZ ' # kaz
Makonde = ' KDE ' # kde
KabuverdianuCrioulo = ' KEA ' # kea
Kebena = ' KEB ' # ktb
Kekchi = ' KEK ' # kek
KhutsuriGeorgian = ' KGE ' # kat
Khakass = ' KHA ' # kjh
KhantyKazim = ' KHK ' # kca
Khmer = ' KHM ' # khm
KhantyShurishkar = ' KHS ' # kca
KhamtiShan = ' KHT ' # kht
KhantyVakhi = ' KHV ' # kca
Khowar = ' KHW ' # khw
Kikuyu_Gikuyu = ' KIK ' # kik
Kirghiz_Kyrgyz = ' KIR ' # kir
Kisii = ' KIS ' # kqs, kss
Kirmanjki = ' KIU ' # kiu
SouthernKiwai = ' KJD ' # kjd
EasternPwoKaren = ' KJP ' # kjp
Kokni = ' KKN ' # kex
Kalmyk = ' KLM ' # xal
Kamba = ' KMB ' # kam
Kumaoni = ' KMN ' # kfy
Komo = ' KMO ' # kmw
Komso = ' KMS ' # kxc
KhorasaniTurkic = ' KMZ ' # kmz
Kanuri = ' KNR ' # kau
Kodagu = ' KOD ' # kfa
KoreanOldHangul = ' KOH ' # okm
Konkani = ' KOK ' # kok
Kikongo = ' KON ' # ktu
Kongo = ' KON0 ' # kon
Komi = ' KOM ' # kom
KomiPermyak = ' KOP ' # koi
Korean = ' KOR ' # kor
Kosraean = ' KOS ' # kos
KomiZyrian = ' KOZ ' # kpv
Kpelle = ' KPL ' # kpe
Krio = ' KRI ' # kri
Karakalpak = ' KRK ' # kaa
Karelian = ' KRL ' # krl
Karaim = ' KRM ' # kdr
Karen = ' KRN ' # kar
Koorete = ' KRT ' # kqy
Kashmiri = ' KSH ' # kas
Ripuarian = ' KSH0 ' # ksh
Khasi = ' KSI ' # kha
KildinSami = ' KSM ' # sjd
SgawKaren = ' KSW ' # ksw
Kuanyama = ' KUA ' # kua
Kui = ' KUI ' # kxu
Kulvi = ' KUL ' # kfx
Kumyk = ' KUM ' # kum
Kurdish = ' KUR ' # kur
Kurukh = ' KUU ' # kru
Kuy = ' KUY ' # kdt
Koryak = ' KYK ' # kpy
WesternKayah = ' KYU ' # kyu
Ladin = ' LAD ' # lld
Lahuli = ' LAH ' # bfu
Lak = ' LAK ' # lbe
Lambani = ' LAM ' # lmn
Lao = ' LAO ' # lao
Latin = ' LAT ' # lat
Laz = ' LAZ ' # lzz
LCree = ' LCR ' # crm
Ladakhi = ' LDK ' # lbj
Lezgi = ' LEZ ' # lez
Ligurian = ' LIJ ' # lij
Limburgish = ' LIM ' # lim
Lingala = ' LIN ' # lin
Lisu = ' LIS ' # lis
Lampung = ' LJP ' # ljp
Laki = ' LKI ' # lki
LowMari = ' LMA ' # mhr
Limbu = ' LMB ' # lif
Lombard = ' LMO ' # lmo
Lomwe = ' LMW ' # ngl
Loma = ' LOM ' # lom
Luri = ' LRC ' # lrc, luz, bqi, zum
LowerSorbian = ' LSB ' # dsb
LuleSami = ' LSM ' # smj
Lithuanian = ' LTH ' # lit
Luxembourgish = ' LTZ ' # ltz
LubaLulua = ' LUA ' # lua
LubaKatanga = ' LUB ' # lub
Ganda = ' LUG ' # lug
Luyia = ' LUH ' # luy
Luo = ' LUO ' # luo
Latvian = ' LVI ' # lav
Madura = ' MAD ' # mad
Magahi = ' MAG ' # mag
Marshallese = ' MAH ' # mah
Majang = ' MAJ ' # mpe
Makhuwa = ' MAK ' # vmw
MalayalamTraditional = ' MAL ' # mal
Mam = ' MAM ' # mam
Mansi = ' MAN ' # mns
Mapudungun = ' MAP ' # arn
Marathi = ' MAR ' # mar
Marwari = ' MAW ' # mwr, dhd, rwr, mve, wry, mtr, swv
Mbundu = ' MBN ' # kmb
Mbo = ' MBO ' # mbo
Manchu = ' MCH ' # mnc
MooseCree = ' MCR ' # crm
Mende = ' MDE ' # men
Mandar = ' MDR ' # mdr
Meen = ' MEN ' # mym
Meru = ' MER ' # mer
Morisyen = ' MFE ' # mfe
Minangkabau = ' MIN ' # min
Mizo = ' MIZ ' # lus
Macedonian = ' MKD ' # mkd
Makasar = ' MKR ' # mak
Kituba = ' MKW ' # mkw
Male = ' MLE ' # mdy
Malagasy = ' MLG ' # mlg
Malinke = ' MLN ' # mlq
MalayalamReformed = ' MLR ' # mal
Malay = ' MLY ' # msa
Mandinka = ' MND ' # mnk
Mongolian = ' MNG ' # mon
Manipuri = ' MNI ' # mni
Maninka = ' MNK ' # man, mnk, myq, mku, msc, emk, mwk, mlq
Manx = ' MNX ' # glv
Mohawk = ' MOH ' # mho
Moksha = ' MOK ' # mdf
Moldavian = ' MOL ' # mol
Mon = ' MON ' # mnw
Moroccan = ' MOR '
Mossi = ' MOS ' # mos
Maori = ' MRI ' # mri
Maithili = ' MTH ' # mai
Maltese = ' MTS ' # mlt
Mundari = ' MUN ' # unr
Muscogee = ' MUS ' # mus
Mirandese = ' MWL ' # mwl
HmongDaw = ' MWW ' # mww
Mayan = ' MYN ' # myn
Mazanderani = ' MZN ' # mzn
NagaAssamese = ' NAG ' # nag
Nahuatl = ' NAH ' # nah
Nanai = ' NAN ' # gld
Neapolitan = ' NAP ' # nap
Naskapi = ' NAS ' # nsk
Nauruan = ' NAU ' # nau
Navajo = ' NAV ' # nav
NCree = ' NCR ' # csw
Ndebele = ' NDB ' # nbl, nde
Ndau = ' NDC ' # ndc
Ndonga = ' NDG ' # ndo
LowSaxon = ' NDS ' # nds
Nepali = ' NEP ' # nep
Newari = ' NEW ' # new
Ngbaka = ' NGA ' # nga
Nagari = ' NGR '
NorwayHouseCree = ' NHC ' # csw
Nisi = ' NIS ' # dap
Niuean = ' NIU ' # niu
Nyankole = ' NKL ' # nyn
NKo = ' NKO ' # ngo
Dutch = ' NLD ' # nld
Nimadi = ' NOE ' # noe
Nogai = ' NOG ' # nog
Norwegian = ' NOR ' # nob
Novial = ' NOV ' # nov
NorthernSami = ' NSM ' # sme
Sotho_Northern = ' NSO ' # nso
NorthernThai = ' NTA ' # nod
Esperanto = ' NTO ' # epo
Nyamwezi = ' NYM ' # nym
NorwegianNynorsk_Nynorsk_Norwegian = ' NYN ' # nno
MbembeTigon = ' NZA ' # nza
Occitan = ' OCI ' # oci
OjiCree = ' OCR ' # ojs
Ojibway = ' OJB ' # oji
Odia_formerlyOriya = ' ORI ' # ori
Oromo = ' ORO ' # orm
Ossetian = ' OSS ' # oss
PalestinianAramaic = ' PAA ' # sam
Pangasinan = ' PAG ' # pag
Pali = ' PAL ' # pli
Pampangan = ' PAM ' # pam
Punjabi = ' PAN ' # pan
Palpa = ' PAP ' # plp
Papiamentu = ' PAP0 ' # pap
Pashto = ' PAS ' # pus
Palauan = ' PAU ' # pau
Bouyei = ' PCC ' # pcc
Picard = ' PCD ' # pcd
PennsylvaniaGerman = ' PDC ' # pdc
PolytonicGreek = ' PGR ' # ell
Phake = ' PHK ' # phk
Norfolk = ' PIH ' # pih
Filipino = ' PIL ' # fil
Palaung = ' PLG ' # pce, rbb, pll
Polish = ' PLK ' # pol
Piemontese = ' PMS ' # pms
WesternPanjabi = ' PNB ' # pnb
Pocomchi = ' POH ' # poh
Pohnpeian = ' PON ' # pon
Provencal = ' PRO ' # pro
Portuguese = ' PTG ' # por
WesternPwoKaren = ' PWO ' # pwo
Chin = ' QIN ' # bgr, cnh, cnw, czt, sez, tcp, csy, ctd, flm, pck, tcz, zom, cmr, dao, hlt, cka, cnk, mrh, cbl, cnb, csh
Kiche = ' QUC ' # quc
Quechua_Bolivia = ' QUH ' # quh
Quechua = ' QUZ ' # quz
Quechua_Ecuador = ' QVI ' # qvi
Quechua_Peru = ' QWH ' # qwh
Rajasthani = ' RAJ ' # raj
Rarotongan = ' RAR ' # rar
RCree = ' RCR ' # atj
RussianBuriat = ' RBU ' # bxr
Rejang = ' REJ ' # rej
Riang = ' RIA ' # ria
Tarifit = ' RIF ' # rif
Ritarungo = ' RIT ' # rit
Arakwal = ' RKW ' # rkw
Romansh = ' RMS ' # roh
VlaxRomani = ' RMY ' # rmy
Romanian = ' ROM ' # ron
Romany = ' ROY ' # rom
Rusyn = ' RSY ' # rue
Rotuman = ' RTM ' # rtm
Kinyarwanda = ' RUA ' # kin
Rundi = ' RUN ' # run
Aromanian = ' RUP ' # rup
Russian = ' RUS ' # rus
Sadri = ' SAD ' # sck
Sanskrit = ' SAN ' # san
Sasak = ' SAS ' # sas
Santali = ' SAT ' # sat
Sayisi = ' SAY ' # chp
Sicilian = ' SCN ' # scn
Scots = ' SCO ' # sco
NorthSlavey = ' SCS ' # scs
Sekota = ' SEK ' # xan
Selkup = ' SEL ' # sel
OldIrish = ' SGA ' # sga
Sango = ' SGO ' # sag
Samogitian = ' SGS ' # sgs
Tachelhit = ' SHI ' # shi
Shan = ' SHN ' # shn
Sibe = ' SIB ' # sjo
Sidamo = ' SID ' # sid
SilteGurage = ' SIG ' # xst
SkoltSami = ' SKS ' # sms
Slovak = ' SKY ' # slk
Slavey = ' SLA ' # scs, xsl
Slovenian = ' SLV ' # slv
Somali = ' SML ' # som
Samoan = ' SMO ' # smo
Sena = ' SNA ' # she
Shona = ' SNA0 ' # sna
Sindhi = ' SND ' # snd
Sinhala_Sinhalese = ' SNH ' # sin
Soninke = ' SNK ' # snk
SodoGurage = ' SOG ' # gru
Songe = ' SOP ' # sop
Sotho_Southern = ' SOT ' # sot
Albanian = ' SQI ' # gsw
Serbian = ' SRB ' # srp
Sardinian = ' SRD ' # srd
Seraiki = ' SRK ' # skr
Serer = ' SRR ' # srr
SouthSlavey = ' SSL ' # xsl
SouthernSami = ' SSM ' # sma
SaterlandFrisian = ' STQ ' # stq
Sukuma = ' SUK ' # suk
Sundanese = ' SUN ' # sun
Suri = ' SUR ' # suq
Svan = ' SVA ' # sva
Swedish = ' SVE ' # swe
SwadayaAramaic = ' SWA ' # aii
Swahili = ' SWK ' # swa
Swati = ' SWZ ' # ssw
Sutu = ' SXT ' # ngo
UpperSaxon = ' SXU ' # sxu
Sylheti = ' SYL ' # syl
Syriac = ' SYR ' # aii, amw, cld, syc, syr, tru
Syriac_EstrangelaScriptVariant = ' SYRE ' # syc, syr # equivalent to ISO 15924 'Syre'
Syriac_WesternScriptVariant = ' SYRJ ' # syc, syr # equivalent to ISO 15924 'Syrj'
Syriac_EasternScriptVariant = ' SYRN ' # syc, syr # equivalent to ISO 15924 'Syrn'
Silesian = ' SZL ' # szl
Tabasaran = ' TAB ' # tab
Tajik = ' TAJ ' # tgk
Tamil = ' TAM ' # tam
Tatar = ' TAT ' # tat
THCree = ' TCR ' # cwd
DehongDai = ' TDD ' # tdd
Telugu = ' TEL ' # tel
Tetum = ' TET ' # tet
Tagalog = ' TGL ' # tgl
Tongan = ' TGN ' # ton
Tigre = ' TGR ' # tig
Tigrinya = ' TGY ' # tir
Thai = ' THA ' # tha
Tahitian = ' THT ' # tah
Tibetan = ' TIB ' # bod
Tiv = ' TIV ' # tiv
Turkmen = ' TKM ' # tuk
Tamashek = ' TMH ' # tmh
Temne = ' TMN ' # tem
Tswana = ' TNA ' # tsn
TundraNenets = ' TNE ' # enh
Tonga = ' TNG ' # toi
Todo = ' TOD ' # xal
Toma = ' TOD0 ' # tod
TokPisin = ' TPI ' # tpi
Turkish = ' TRK ' # tur
Tsonga = ' TSG ' # tso
TuroyoAramaic = ' TUA ' # tru
Tulu = ' TUL ' # tcy
Tumbuka = ' TUM ' # tum
Tuvin = ' TUV ' # tyv
Tuvalu = ' TVL ' # tvl
Twi = ' TWI ' # twi
Tày = ' TYZ ' # tyz
Tamazight = ' TZM ' # tzm
Tzotzil = ' TZO ' # tzo
Udmurt = ' UDM ' # udm
Ukrainian = ' UKR ' # ukr
Umbundu = ' UMB ' # umb
Urdu = ' URD ' # urd
UpperSorbian = ' USB ' # hsb
Uyghur = ' UYG ' # uig
Uzbek = ' UZB ' # uzb, uzn, uzs
Venetian = ' VEC ' # vec
Venda = ' VEN ' # ven
Vietnamese = ' VIT ' # vie
Volapük = ' VOL ' # vol # TODO: Maybe remove non-ascii characters? (cute fraud)
Võro = ' VRO ' # vro # TODO: Maybe remove non-ascii characters?
Wa = ' WA ' # wbm
Wagdi = ' WAG ' # wbr
WarayWaray = ' WAR ' # war
WestCree = ' WCR ' # crk
Welsh = ' WEL ' # cym
Walloon = ' WLN ' # wln
Wolof = ' WLF ' # wol
Mewati = ' WTM ' # wtm
Lü = ' XBD ' # khb
Xhosa = ' XHS ' # xho
Minjangbal = ' XJB ' # xjb
Soga = ' XOG ' # xog
Kpelle_Liberia = ' XPE ' # xpe
Sakha = ' YAK ' # sah
Yao = ' YAO ' # yao
Yapese = ' YAP ' # yap
Yoruba = ' YBA ' # yor
YCree = ' YCR ' # cre
YiClassic = ' YIC '
YiModern = ' YIM ' # iii
Zealandic = ' ZEA ' # zea
StandardMorrocanTamazigh = ' ZGH ' # zgh
Zhuang = ' ZHA ' # zha
Chinese_HongKongSAR = ' ZHH ' # zho
ChinesePhonetic = ' ZHP ' # zho
ChineseSimplified = ' ZHS ' # zho
ChineseTraditional = ' ZHT ' # zho
Zande = ' ZND ' # zne
Zulu = ' ZUL ' # zul
Zazaki = ' ZZA ' # zza
def __str__ ( self ) - > str : return self . _name_
2024-09-15 16:10:41 +10:00
@dataclass
class InvalidLangSysTag ( LangSysTag ) :
tag : str
2024-05-03 21:02:58 +10:00
def parse_lang_sys_tag ( f : BinaryIO ) - > LangSysTag :
2024-09-15 16:10:41 +10:00
return read_tag_from_tags ( f , ValidLangSysTag , InvalidLangSysTag , MS_VOLT_Tag , umbrellaTagCls = LangSysTag )
2024-05-03 21:02:58 +10:00
@dataclass
class LangSysRecord :
langSysTag : LangSysTag
langSys : LangSysTable
def parse_lang_sys_record ( f : BinaryIO , start_tell : int ) - > LangSysRecord :
langSysTag = parse_lang_sys_tag ( f )
langSysOffset = read_u16 ( f )
with SaveTell ( f ) :
langSys = parse_at_offset ( f , start_tell , langSysOffset , parse_lang_sys_table )
return LangSysRecord ( langSysTag , langSys )
@dataclass
class ScriptTable ( Table ) :
defaultLangSys : Optional [ LangSysTable ]
langSysCount : int
langSysRecords : List [ LangSysRecord ]
def parse_script_table ( f : BinaryIO ) - > ScriptTable :
start_tell = f . tell ( )
defaultLangSysOffset = read_u16 ( f )
with SaveTell ( f ) :
defaultLangSys = parse_at_optional_offset ( f , start_tell , defaultLangSysOffset , parse_lang_sys_table )
langSysCount = read_u16 ( f )
langSysRecords = [ parse_lang_sys_record ( f , start_tell ) for _ in range ( langSysCount ) ]
return ScriptTable ( defaultLangSys , langSysCount , langSysRecords )
@dataclass
class ScriptRecord :
scriptTag : ScriptTag
script : ScriptTable
def parse_script_record ( f : BinaryIO , start_tell : int ) - > ScriptRecord :
2024-05-04 11:34:37 +10:00
tag = read_tag ( f , ScriptTag )
2024-05-03 21:02:58 +10:00
scriptOffset = read_u16 ( f )
with SaveTell ( f ) :
script = parse_at_offset ( f , start_tell , scriptOffset , parse_script_table )
return ScriptRecord ( tag , script )
@dataclass
class ScriptListTable ( Table ) :
scriptCount : int
scriptRecords : List [ ScriptRecord ]
def parse_script_list_table ( f : BinaryIO ) - > ScriptListTable :
start_tell = f . tell ( )
scriptCount = read_u16 ( f )
scriptRecords = [ parse_script_record ( f , start_tell ) for _ in range ( scriptCount ) ]
return ScriptListTable ( scriptCount , scriptRecords )
class FeatureTag ( ABE ) : pass
class SimpleFeatureTag ( FeatureTag , Enum ) :
Aalt = ' aalt ' # Access All Alternates
Abvf = ' abvf ' # Above-base Forms
Abvm = ' abvm ' # Above-base Mark Positioning
Abvs = ' abvs ' # Above-base Substitutions
Afrc = ' afrc ' # Alternative Fractions
Akhn = ' akhn ' # Akhands
Blwf = ' blwf ' # Below-base Forms
Blwm = ' blwm ' # Below-base Mark Positioning
Blws = ' blws ' # Below-base Substitutions
Calt = ' calt ' # Contextual Alternates
Case = ' case ' # Case-Sensitive Forms
Ccmp = ' ccmp ' # Glyph Composition / Decomposition
Cfar = ' cfar ' # Conjunct Form After Ro
Cjct = ' cjct ' # Conjunct Forms
Clig = ' clig ' # Contextual Ligatures
Cpct = ' cpct ' # Centered CJK Punctuation
Cpsp = ' cpsp ' # Capital Spacing
Cswh = ' cswh ' # Contextual Swash
Curs = ' curs ' # Cursive Positioning
# Charcater Variant 1-99 are handled by CvXXFeatureTag
C2pc = ' c2pc ' # Petite Capitals From Capitals
C2sc = ' c2sc ' # Small Capitals From Capitals
Dist = ' dist ' # Distances
Dlig = ' dlig ' # Discretionary Ligatures
Dnom = ' dnom ' # Denominators
Dtls = ' dtls ' # Dotless Forms
Expt = ' expt ' # Expert Forms
Falt = ' falt ' # Final Glyph on Line Alternates
Fin2 = ' fin2 ' # Terminal Forms #2
Fin3 = ' fin3 ' # Terminal Forms #3
Fina = ' fina ' # Terminal Forms
Flac = ' flac ' # Flattened ascent forms
Frac = ' frac ' # Fractions
Fwid = ' fwid ' # Full Widths
Half = ' half ' # Half Forms
Haln = ' haln ' # Halant Forms
Halt = ' halt ' # Alternate Half Widths
Hist = ' hist ' # Historical Forms
Hkna = ' hkna ' # Horizontal Kana Alternates
Hlig = ' hlig ' # Historical Ligatures
Hngl = ' hngl ' # Hangul
Hojo = ' hojo ' # Hojo Kanji Forms (JIS X 0212-1990 Kanji Forms)
Hwid = ' hwid ' # Half Widths
Init = ' init ' # Initial Forms
Isol = ' isol ' # Isolated Forms
Ital = ' ital ' # Italics
Jalt = ' jalt ' # Justification Alternates
Jp78 = ' jp78 ' # JIS78 Forms
Jp83 = ' jp83 ' # JIS83 Forms
Jp90 = ' jp90 ' # JIS90 Forms
Jp04 = ' jp04 ' # JIS2004 Forms
Kern = ' kern ' # Kerning
Lfbd = ' lfbd ' # Left Bounds
Liga = ' liga ' # Standard Ligatures
Ljmo = ' ljmo ' # Leading Jamo Forms
Lnum = ' lnum ' # Lining Figures
Locl = ' locl ' # Localized Forms
Ltra = ' ltra ' # Left-to-right glyph alternates
Ltrm = ' ltrm ' # Left-to-right mirrored forms
Mark = ' mark ' # Mark Positioning
Med2 = ' med2 ' # Medial Forms #2
Medi = ' medi ' # Medial Forms
Mgrk = ' mgrk ' # Mathematical Greek
Mkmk = ' mkmk ' # Mark to Mark Positioning
Mset = ' mset ' # Mark Positioning via Substitution
Nalt = ' nalt ' # Alternate Annotation Forms
Nlck = ' nlck ' # NLC Kanji Forms
Nukt = ' nukt ' # Nukta Forms
Numr = ' numr ' # Numerators
Onum = ' onum ' # Oldstyle Figures
Opbd = ' opbd ' # Optical Bounds
Ordn = ' ordn ' # Ordinals
Ornm = ' ornm ' # Ornaments
Palt = ' palt ' # Proportional Alternate Widths
Pcap = ' pcap ' # Petite Capitals
Pkna = ' pkna ' # Proportional Kana
Pnum = ' pnum ' # Proportional Figures
Pref = ' pref ' # Pre-Base Forms
Pres = ' pres ' # Pre-base Substitutions
Pstf = ' pstf ' # Post-base Forms
Psts = ' psts ' # Post-base Substitutions
Pwid = ' pwid ' # Proportional Widths
Qwid = ' qwid ' # Quarter Widths
Rand = ' rand ' # Randomize
Rclt = ' rclt ' # Required Contextual Alternates
Rkrf = ' rkrf ' # Rakar Forms
Rlig = ' rlig ' # Required Ligatures
Rphf = ' rphf ' # Reph Forms
Rtbd = ' rtbd ' # Right Bounds
Rtla = ' rtla ' # Right-to-left alternates
Rtlm = ' rtlm ' # Right-to-left mirrored forms
Ruby = ' ruby ' # Ruby Notation Forms
Rvrn = ' rvrn ' # Required Variation Alternates
Salt = ' salt ' # Stylistic Alternates
Sinf = ' sinf ' # Scientific Inferiors
Size = ' size ' # Optical size
Smcp = ' smcp ' # Small Capitals
Smpl = ' smpl ' # Simplified Forms
# Stylistic Set 1-20 are handled by SsXXFeatureTag
Ssty = ' ssty ' # Math script style alternates
Stch = ' stch ' # Stretching Glyph Decomposition
Subs = ' subs ' # Subscript
Sups = ' sups ' # Superscript
Swsh = ' swsh ' # Swash
Titl = ' titl ' # Titling
Tjmo = ' tjmo ' # Trailing Jamo Forms
Tnam = ' tnam ' # Traditional Name Forms
Tnum = ' tnum ' # Tabular Figures
Trad = ' trad ' # Traditional Forms
Twid = ' twid ' # Third Widths
Unic = ' unic ' # Unicase
Valt = ' valt ' # Alternate Vertical Metrics
Vatu = ' vatu ' # Vattu Variants
Vert = ' vert ' # Vertical Writing
Vhal = ' vhal ' # Alternate Vertical Half Metrics
Vjmo = ' vjmo ' # Vowel Jamo Forms
Vkna = ' vkna ' # Vertical Kana Alternates
Vkrn = ' vkrn ' # Vertical Kerning
Vpal = ' vpal ' # Proportional Alternate Vertical Metrics
Vrt2 = ' vrt2 ' # Vertical Alternates and Rotation
Vrtr = ' vrtr ' # Vertical Alternates for Rotation
Zero = ' zero ' # Slashed Zero
def __str__ ( self ) - > str : return self . _value_
class CvXXFeatureTag ( FeatureTag , CCXXTag ) :
"""
Character Variant XX
"""
__CC__ = ' cv '
class SsXXFeatureTag ( FeatureTag , CCXXTag ) :
"""
Stylistic Set XX
"""
__CC__ = ' ss '
__range__ = ( 1 , 20 )
class MS_VOLT_Tag ( CCXXTag , LangSysTag , FeatureTag ) :
"""
Tags in the form ` zzXX ` , produced by an error in MS VOLT .
"""
__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
2024-09-15 16:10:41 +10:00
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
2024-05-03 21:02:58 +10:00
def parse_feature_tag ( f : BinaryIO ) - > FeatureTag :
2024-09-15 16:10:41 +10:00
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 )
2024-05-03 21:02:58 +10:00
@dataclass
class FeatureParamsTable ( Table , ABD ) : pass
@dataclass
class CvXXFeatureParamsTable ( FeatureParamsTable , ABD ) :
format : int
@dataclass
class CvXXFeatureParamsTable_Format_0 ( CvXXFeatureParamsTable ) :
featUiLabelNameId : Optional [ int ]
featUiTooltipTextNameId : Optional [ int ]
sampleTextNameId : Optional [ int ]
numNamedParameters : int
firstParamUiLabelNameId : Optional [ int ] # None means that it is 0, which means that there are no parameters
charCount : int
character : List [ int ]
@dataclass
class SsXXFeatureParamsTable ( FeatureParamsTable , ABD ) :
version : int
UINameID : int
@dataclass
class SsXXFeatureParamsTable_Ver_0 ( SsXXFeatureParamsTable ) : pass
@dataclass
class SizeFeatureParamsTable ( FeatureParamsTable ) :
design_size : int
subfamily_identifier : int
subfamily_nameID : Optional [ int ]
recommended_usage_range : Optional [ Tuple [ int , int ] ]
2024-05-04 11:34:37 +10:00
def parse_feature_params_table ( f : BinaryIO , featureTag : FeatureTag ) - > FeatureParamsTable :
2024-05-03 21:02:58 +10:00
match featureTag :
case CvXXFeatureTag ( num = _ ) :
format = read_u16 ( f )
assert format in [ 0 ]
match format :
case 0 :
featUiLabelNameId = null_if_zero ( read_u16 ( f ) )
featUiTooltipTextNameId = null_if_zero ( read_u16 ( f ) )
sampleTextNameId = null_if_zero ( read_u16 ( f ) )
numNamedParameters = read_u16 ( f )
firstParamUiLabelNameId = null_if_zero ( read_u16 ( f ) )
if numNamedParameters == 0 : assert firstParamUiLabelNameId is None , f " Cannot specify paramUiLabelNameIDs if numNamedParameters is 0 "
charCount = read_u16 ( f )
character = [ read_u24 ( f ) for _ in range ( charCount ) ]
return CvXXFeatureParamsTable_Format_0 ( format , featUiLabelNameId , featUiTooltipTextNameId , sampleTextNameId , numNamedParameters , firstParamUiLabelNameId , charCount , character )
case _ :
assert False , f " Unimplemented: format: { format } "
assert False , format
case SsXXFeatureTag ( num = _ ) :
version = read_u16 ( f )
assert version in [ 0 ]
UINameID = read_u16 ( f )
if version == 0 :
return SsXXFeatureParamsTable_Ver_0 ( version , UINameID )
assert False , f " Unimplemented: version: { version } "
case SimpleFeatureTag . Size :
design_size = read_u16 ( f )
subfamily_identifier = read_u16 ( f )
subfamily_nameID = read_u16 ( f )
if subfamily_identifier != 0 : assert 256 < = subfamily_nameID < = 32767
else : assert subfamily_nameID == 0
recommended_usage_range_start = read_u16 ( f )
recommended_usage_range_end = read_u16 ( f )
if subfamily_identifier != 0 : assert recommended_usage_range_start < = recommended_usage_range_end
else : assert ( recommended_usage_range_start , recommended_usage_range_end ) == ( 0 , 0 )
return SizeFeatureParamsTable ( design_size , subfamily_identifier , subfamily_nameID if subfamily_identifier else None , ( recommended_usage_range_start , recommended_usage_range_end ) if subfamily_identifier else None )
case _ :
assert False , f " Unimplemented: featureTag: { featureTag } "
@dataclass
class FeatureTable ( Table ) :
featureParams : Optional [ FeatureParamsTable ]
lookupIndexCount : int
lookupListIndices : List [ int ]
def parse_feature_table ( f : BinaryIO , featureTag : FeatureTag ) - > FeatureTable :
start_tell = f . tell ( )
featureParamsOffset = read_u16 ( f )
with SaveTell ( f ) :
featureParams = parse_at_optional_offset ( f , start_tell , featureParamsOffset , lambda f : parse_feature_params_table ( f , featureTag ) )
lookupIndexCount = read_u16 ( f )
lookupListIndices = [ read_u16 ( f ) for _ in range ( lookupIndexCount ) ]
return FeatureTable ( featureParams , lookupIndexCount , lookupListIndices )
@dataclass
class FeatureRecord :
featureTag : FeatureTag
feature : FeatureTable
def parse_feature_record ( f : BinaryIO , start_tell : int ) - > FeatureRecord :
featureTag = parse_feature_tag ( f )
featureOffset = read_u16 ( f )
with SaveTell ( f ) :
feature = parse_at_offset ( f , start_tell , featureOffset , lambda f : parse_feature_table ( f , featureTag ) )
return FeatureRecord ( featureTag , feature )
@dataclass
class FeatureListTable ( Table ) :
featureCount : int
featureRecords : List [ FeatureRecord ]
def parse_feature_list_table ( f : BinaryIO ) - > FeatureListTable :
start_tell = f . tell ( )
featureCount = read_u16 ( f )
featureRecords = [ parse_feature_record ( f , start_tell ) for _ in range ( featureCount ) ]
return FeatureListTable ( featureCount , featureRecords )
LookupType = TypeVar ( ' LookupType ' , bound = Enum )
@dataclass
class LookupSubtable ( Table , ABD ) : pass
LookupSubtable_ = TypeVar ( ' LookupSubtable_ ' , bound = LookupSubtable )
@dataclass
class LookupFlag : # TODO: Do this like the other flags
rightToLeft : bool
ignoreBaseGlyphs : bool
ignoreLigatures : bool
ignoreMarks : bool
useMarkFilteringSet : bool
markAttachmentType : int
def parse_lookup_flag ( f : BinaryIO ) - > LookupFlag :
lookupFlag = read_u16 ( f )
rightToLeft , ignoreBaseGlyphs , ignoreLigatures , ignoreMarks , useMarkFilteringSet = [ bool ( lookupFlag & ( 1 << i ) ) for i in range ( 5 ) ]
2024-09-15 16:10:41 +10:00
assert lookupFlag & 0x00e0 == 0 , " Reserved " # TODO: Once you do this like the other flags, put this in the __post_init__
2024-05-03 21:02:58 +10:00
markAttachmentType = ( lookupFlag & 0xff00 ) >> 8
return LookupFlag ( rightToLeft , ignoreBaseGlyphs , ignoreLigatures , ignoreMarks , useMarkFilteringSet , markAttachmentType )
@dataclass
class LookupTable ( Table , Generic [ LookupType , LookupSubtable_ ] ) :
lookupType : LookupType
lookupFlag : LookupFlag
subTableCount : int
subTables : List [ LookupSubtable_ ]
markFilteringSet : Optional [ int ]
def parse_lookup_table ( f : BinaryIO , _lookupType : Callable [ [ int ] , LookupType ] , lookup_subtable_parser : Callable [ [ BinaryIO , LookupType ] , LookupSubtable_ ] ) - > LookupTable [ LookupType , LookupSubtable_ ] :
assert isinstance ( _lookupType , EnumMeta )
start_tell = f . tell ( )
2024-05-04 11:34:37 +10:00
lookupType = read_id ( f , _lookupType )
2024-05-03 21:02:58 +10:00
lookupFlag = parse_lookup_flag ( f )
subTableCount = read_u16 ( f )
subTableOffsets = [ read_u16 ( f ) for _ in range ( subTableCount ) ]
with SaveTell ( f ) :
subTables = parse_at_offsets ( f , start_tell , subTableOffsets , lambda f : lookup_subtable_parser ( f , lookupType ) )
markAttachmentType = read_u16 ( f ) if lookupFlag . markAttachmentType else None
return LookupTable ( lookupType , lookupFlag , subTableCount , subTables , markAttachmentType )
@dataclass
class LookupListTable ( Table , Generic [ LookupType , LookupSubtable_ ] ) :
lookupCount : int
lookups : List [ LookupTable [ LookupType , LookupSubtable_ ] ]
def parse_lookup_list_table ( f : BinaryIO , lookupType : Callable [ [ int ] , LookupType ] , lookup_subtable_parser : Callable [ [ BinaryIO , LookupType ] , LookupSubtable_ ] ) - > LookupListTable [ LookupType , LookupSubtable_ ] :
assert isinstance ( lookupType , EnumMeta )
start_tell = f . tell ( )
lookupCount = read_u16 ( f )
lookupOffsets = [ read_u16 ( f ) for _ in range ( lookupCount ) ]
with SaveTell ( f ) :
lookups = parse_at_offsets ( f , start_tell , lookupOffsets , lambda f : parse_lookup_table ( f , lookupType , lookup_subtable_parser ) )
return LookupListTable ( lookupCount , lookups )
@dataclass
class ConditionTable ( Table , ABD ) :
format : int
@dataclass
class ConditionTable_Format_1 ( ConditionTable ) :
axisIndex : int
filterRangeMinValue : float
filterRangeMaxValue : float
def parse_condition_table ( f : BinaryIO ) - > ConditionTable :
format = read_u16 ( f )
assert format in [ 1 ]
match format :
case 1 :
axisIndex = read_u16 ( f )
filterRangeMinValue = read_F2DOT14 ( f )
filterRangeMaxValue = read_F2DOT14 ( f )
return ConditionTable_Format_1 ( format , axisIndex , filterRangeMinValue , filterRangeMaxValue )
case _ :
assert False , f " Unimplemented: format: { format } "
assert False , format
2024-05-04 11:34:37 +10:00
ConditionSetTable = SetTable [ ConditionTable ]
2024-05-03 21:02:58 +10:00
def parse_condition_set_table ( f : BinaryIO ) - > ConditionSetTable :
2024-05-04 11:34:37 +10:00
return parse_set_table ( f , parse_condition_table , offset_reader = read_u32 )
# start_tell = f.tell()
2024-05-03 21:02:58 +10:00
2024-05-04 11:34:37 +10:00
# conditionCount = read_u16(f)
# conditionOffsets = [read_u32(f) for _ in range(conditionCount)]
# with SaveTell(f):
# conditions = parse_at_offsets(f, start_tell, conditionOffsets, parse_condition_table)
2024-05-03 21:02:58 +10:00
2024-05-04 11:34:37 +10:00
# return ConditionSetTable(conditionCount, conditions)
2024-05-03 21:02:58 +10:00
@dataclass
class FeatureTableSubstitutionRecord :
featureIndex : int
alternateFeatureTable : FeatureTable
def parse_feature_table_substitution_record ( f : BinaryIO , start_tell : int , featureList : FeatureListTable ) - > FeatureTableSubstitutionRecord :
featureIndex = read_u16 ( f )
alternateFeatureTableOffset = read_u32 ( f )
with SaveTell ( f ) :
alternateFeatureTable = parse_at_offset ( f , start_tell , alternateFeatureTableOffset , lambda f : parse_feature_table ( f , featureList . featureRecords [ featureIndex ] . featureTag ) ) # TODO: Am I sure that it's the same featureTag? ttf-parser doesn't even know what to do and just passes in b'dflt'
return FeatureTableSubstitutionRecord ( featureIndex , alternateFeatureTable )
@dataclass
class FeatureTableSubstitutionTable ( Table , ABD ) :
majorVersion : int
minorVersion : int
substitutionCount : int
substitutions : List [ FeatureTableSubstitutionRecord ]
@dataclass
class FeatureTableSubstitutionTable_Ver_1_0 ( FeatureTableSubstitutionTable ) : pass
def parse_feature_table_substitution_table ( f : BinaryIO , featureList : FeatureListTable ) - > FeatureTableSubstitutionTable :
start_tell = f . tell ( )
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 ]
substitutionCount = read_u16 ( f )
substitutions = [ parse_feature_table_substitution_record ( f , start_tell , featureList ) for _ in range ( substitutionCount ) ]
if minorVersion == 0 :
return FeatureTableSubstitutionTable_Ver_1_0 ( majorVersion , minorVersion , substitutionCount , substitutions )
assert False , f " Unimplemented: minorVersion: { minorVersion } "
@dataclass
class FeatureVariationRecord :
2024-05-04 11:34:37 +10:00
conditionSet : ConditionSetTable
2024-05-03 21:02:58 +10:00
featureTableSubstitution : FeatureTableSubstitutionTable
def parse_feature_variation_record ( f : BinaryIO , start_tell : int , featureList : FeatureListTable ) - > FeatureVariationRecord :
conditionSetOffset = read_u32 ( f )
featureTableSubstitutionOffset = read_u32 ( f )
with SaveTell ( f ) :
2024-05-04 11:34:37 +10:00
conditionSet = parse_at_offset ( f , start_tell , conditionSetOffset , parse_condition_set_table )
2024-05-03 21:02:58 +10:00
featureTableSubstitution = parse_at_offset ( f , start_tell , featureTableSubstitutionOffset , lambda f : parse_feature_table_substitution_table ( f , featureList ) )
return FeatureVariationRecord ( conditionSet , featureTableSubstitution )
@dataclass
class FeatureVariationsTable ( Table , ABD ) :
majorVersion : int
minorVersion : int
featureVariationRecordCount : int
featureVariationRecords : List [ FeatureVariationRecord ]
@dataclass
class FeatureVariationsTable_Ver_1_0 ( FeatureVariationsTable ) : pass
def parse_feature_variations_table ( f : BinaryIO , featureList : FeatureListTable ) - > FeatureVariationsTable :
start_tell = f . tell ( )
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 ]
featureVariationRecordCount = read_u32 ( f )
featureVariationRecords = [ parse_feature_variation_record ( f , start_tell , featureList ) for _ in range ( featureVariationRecordCount ) ]
if minorVersion == 0 :
return FeatureVariationsTable_Ver_1_0 ( majorVersion , minorVersion , featureVariationRecordCount , featureVariationRecords )
assert False , f " Unimplemented: minorVersion: { minorVersion } "
@dataclass
class CoverageTable ( Table , ABD ) :
coverageFormat : int
@dataclass
class CoverageTable_Format_1 ( CoverageTable ) :
glyphCount : int
glyphArray : List [ int ]
@dataclass
2024-05-04 11:34:37 +10:00
class RangeRecord ( Record ) :
2024-05-03 21:02:58 +10:00
startGlyphID : int
endGlyphID : int
startCoverageIndex : int
def parse_range_record ( f : BinaryIO ) - > RangeRecord :
startGlyphID = read_u16 ( f )
endGlyphID = read_u16 ( f )
startCoverageIndex = read_u16 ( f )
return RangeRecord ( startGlyphID , endGlyphID , startCoverageIndex )
@dataclass
class CoverageTable_Format_2 ( CoverageTable ) :
rangeCount : int
rangeRecords : List [ RangeRecord ]
def parse_coverage_table ( f : BinaryIO ) - > CoverageTable :
coverageFormat = read_u16 ( f )
assert coverageFormat in [ 1 , 2 ]
match coverageFormat :
case 1 :
glyphCount = read_u16 ( f )
glyphArray = [ read_u16 ( f ) for _ in range ( glyphCount ) ]
return CoverageTable_Format_1 ( coverageFormat , glyphCount , glyphArray )
case 2 :
rangeCount = read_u16 ( f )
rangeRecords = [ parse_range_record ( f ) for _ in range ( rangeCount ) ]
return CoverageTable_Format_2 ( coverageFormat , rangeCount , rangeRecords )
case _ :
assert False , f " Unimplemented: coverageFormat: { coverageFormat } "
@dataclass
class ClassDefTable ( Table , ABD ) :
classFormat : int
@dataclass
class ClassDefTable_Format_1 ( ClassDefTable ) :
startGlyphID : int
glyphCount : int
classValueArray : List [ int ]
@dataclass
class ClassRangeRecord :
startGlyphID : int
endGlyphID : int
classValue : int
2024-05-04 11:34:37 +10:00
def parse_class_range_record ( f : BinaryIO ) - > ClassRangeRecord :
2024-05-03 21:02:58 +10:00
startGlyphID = read_u16 ( f )
endGlyphID = read_u16 ( f )
classValue = read_u16 ( f )
return ClassRangeRecord ( startGlyphID , endGlyphID , classValue )
@dataclass
class ClassDefTable_Format_2 ( ClassDefTable ) :
classRangeCount : int
classRangeRecords : List [ ClassRangeRecord ]
def parse_class_def_table ( f : BinaryIO ) - > ClassDefTable :
classFormat = read_u16 ( f )
assert classFormat in [ 1 , 2 ] , f " Invalid classFormat: { classFormat } . Expected 1 or 2. "
match classFormat :
case 1 :
startGlyphID = read_u16 ( f )
glyphCount = read_u16 ( f )
classValueArray = [ read_u16 ( f ) for _ in range ( glyphCount ) ]
return ClassDefTable_Format_1 ( classFormat , startGlyphID , glyphCount , classValueArray )
case 2 :
classRangeCount = read_u16 ( f )
classRangeRecords = [ parse_class_range_record ( f ) for _ in range ( classRangeCount ) ]
return ClassDefTable_Format_2 ( classFormat , classRangeCount , classRangeRecords )
case _ :
assert False , f " Unimplemented: classFormat: { classFormat } "
@dataclass
class ValueFormatFlags :
bytes : int
def x_placement ( self ) - > bool : return ( self . bytes & 0x0001 ) != 0
def y_placement ( self ) - > bool : return ( self . bytes & 0x0002 ) != 0
def x_advance ( self ) - > bool : return ( self . bytes & 0x0004 ) != 0
def y_advance ( self ) - > bool : return ( self . bytes & 0x0008 ) != 0
def x_placement_device ( self ) - > bool : return ( self . bytes & 0x0010 ) != 0
def y_placement_device ( self ) - > bool : return ( self . bytes & 0x0020 ) != 0
def x_advance_device ( self ) - > bool : return ( self . bytes & 0x0040 ) != 0
def y_advance_device ( self ) - > bool : return ( self . bytes & 0x0080 ) != 0
2024-09-15 16:10:41 +10:00
def __post_init__ ( self ) :
assert self . bytes & 0xFF00 == 0 , " Reserved "
2024-05-04 11:34:37 +10:00
def parse_value_format ( f : BinaryIO ) - > ValueFormatFlags :
2024-05-03 21:02:58 +10:00
valueFormat = read_u16 ( f )
return ValueFormatFlags ( valueFormat )
@dataclass
class DeviceTable ( Table ) :
startSize : int
endSize : int
deltaFormat : int
deltaValue : List [ int ]
2024-09-15 16:10:41 +10:00
def __post_init__ ( self ) :
assert self . deltaFormat & 0x7ffc == 0 , " Reserved "
2024-05-03 21:02:58 +10:00
@dataclass
class VariationIndexTable ( Table ) :
deltaSetOuterIndex : int
deltaSetInnerIndex : int
deltaFormat : int
2024-09-15 16:10:41 +10:00
def __post_init__ ( self ) :
assert self . deltaFormat == 0x8000
2024-05-03 21:02:58 +10:00
DeviceTable_ = DeviceTable | VariationIndexTable
def parse_device_table ( f : BinaryIO ) - > DeviceTable_ :
first = read_u16 ( f )
second = read_u16 ( f )
deltaFormat = read_u16 ( f )
assert deltaFormat in [ 1 , 2 , 3 , 0x8000 ] , f " Invalid deltaFormat: { deltaFormat } "
match deltaFormat :
case 1 | 2 | 3 :
assert False
case 0x8000 :
return VariationIndexTable ( first , second , deltaFormat )
case _ :
assert False , f " Unimplemented: deltaFormat: { deltaFormat } "
class BaselineTag ( Enum ) :
Hang = ' hang '
Icfb = ' icfb '
Icft = ' icft '
Ideo = ' ideo '
Idtp = ' idtp '
Math = ' math '
Romn = ' romn '
def __str__ ( self ) - > str : return self . _value_
@dataclass
class BaseTagListTable ( Table ) :
baseTagCount : int
baselineTags : List [ BaselineTag ]
def parse_base_tag_list_table ( f : BinaryIO ) - > BaseTagListTable :
baseTagCount = read_u16 ( f )
2024-05-04 11:34:37 +10:00
baselineTags = [ read_tag ( f , BaselineTag ) for _ in range ( baseTagCount ) ]
2024-05-03 21:02:58 +10:00
return BaseTagListTable ( baseTagCount , baselineTags )
@dataclass
class BaseCoordTable ( Table , ABD ) :
baseCoordFormat : int
@dataclass
class BaseCoordTable_Format_1 ( BaseCoordTable ) :
coordinate : int
def parse_base_coord_table ( f : BinaryIO ) - > BaseCoordTable :
baseCoordFormat = read_u16 ( f )
assert baseCoordFormat in [ 1 , 2 , 3 ]
match baseCoordFormat :
case 1 :
coordinate = read_i16 ( f )
return BaseCoordTable_Format_1 ( baseCoordFormat , coordinate )
case _ :
assert False , f " Unimplemented: baseCoordFormat: { baseCoordFormat } "
assert False , baseCoordFormat
@dataclass
class BaseValuesTable ( Table ) :
defaultBaselineIndex : int
baseCoordCount : int
baseCoords : List [ BaseCoordTable ]
def parse_base_values_table ( f : BinaryIO ) - > BaseValuesTable :
start_tell = f . tell ( )
defaultBaselineIndex = read_u16 ( f )
baseCoordCount = read_u16 ( f )
baseCoordOffsets = [ read_u16 ( f ) for _ in range ( baseCoordCount ) ]
with SaveTell ( f ) :
baseCoords = parse_at_offsets ( f , start_tell , baseCoordOffsets , parse_base_coord_table )
return BaseValuesTable ( defaultBaselineIndex , baseCoordCount , baseCoords )
@dataclass
class MinMaxTable ( Table ) :
pass
def parse_min_max_table ( f : BinaryIO ) - > MinMaxTable :
assert False
@dataclass
class BaseLangSysRecord :
pass
def parse_base_lang_sys_record ( f : BinaryIO ) - > BaseLangSysRecord :
assert False
@dataclass
class BaseScriptTable ( Table ) :
baseValues : Optional [ BaseValuesTable ]
defaultMinMax : Optional [ MinMaxTable ]
baseLangSysCount : int
baseLangSysRecords : List [ BaseLangSysRecord ]
def parse_base_script_table ( f : BinaryIO ) - > BaseScriptTable :
start_tell = f . tell ( )
baseValuesOffset = read_u16 ( f )
defaultMinMaxOffset = read_u16 ( f )
baseLangSysCount = read_u16 ( f )
baseLangSysRecords = [ parse_base_lang_sys_record ( f ) for _ in range ( baseLangSysCount ) ]
with SaveTell ( f ) :
baseValues = parse_at_optional_offset ( f , start_tell , baseValuesOffset , parse_base_values_table )
defaultMinMax = parse_at_optional_offset ( f , start_tell , defaultMinMaxOffset , parse_min_max_table )
return BaseScriptTable ( baseValues , defaultMinMax , baseLangSysCount , baseLangSysRecords )
@dataclass
class BaseScriptRecord :
baseScriptTag : ScriptTag
baseScript : BaseScriptTable
def parse_base_script_record ( f : BinaryIO , start_tell : int ) - > BaseScriptRecord :
2024-05-04 11:34:37 +10:00
baseScriptTag = read_tag ( f , ScriptTag )
2024-05-03 21:02:58 +10:00
baseScriptOffset = read_u16 ( f )
with SaveTell ( f ) :
baseScript = parse_at_offset ( f , start_tell , baseScriptOffset , parse_base_script_table )
return BaseScriptRecord ( baseScriptTag , baseScript )
@dataclass
class BaseScriptListTable ( Table ) :
baseScriptCount : int
baseScriptRecords : List [ BaseScriptRecord ]
def parse_base_script_list_table ( f : BinaryIO ) - > BaseScriptListTable :
start_tell = f . tell ( )
baseScriptCount = read_u16 ( f )
baseScriptRecords = [ parse_base_script_record ( f , start_tell ) for _ in range ( baseScriptCount ) ]
return BaseScriptListTable ( baseScriptCount , baseScriptRecords )
@dataclass
class AxisTable ( Table ) :
baseTagList : Optional [ BaseTagListTable ]
baseScriptList : BaseScriptListTable
def parse_axis_table ( f : BinaryIO ) - > AxisTable :
start_tell = f . tell ( )
baseTagListOffset = read_u16 ( f )
baseScriptListOffset = read_u16 ( f )
with SaveTell ( f ) :
baseTagList = parse_at_optional_offset ( f , start_tell , baseTagListOffset , parse_base_tag_list_table )
baseScriptList = parse_at_offset ( f , start_tell , baseScriptListOffset , parse_base_script_list_table )
return AxisTable ( baseTagList , baseScriptList )
@dataclass
class BASETable ( Table , ABD ) :
majorVersion : int
minorVersion : int
horizAxis : Optional [ AxisTable ]
vertAxis : Optional [ AxisTable ]
@dataclass
class BASETable_Ver_1_0 ( BASETable ) : pass
@dataclass
class BASETable_Ver_1_1 ( BASETable_Ver_1_0 ) :
itemVarStore : ' Optional[ItemVariationStoreTable] '
def parse_BASE_table ( f : BinaryIO ) - > BASETable :
start_tell = f . tell ( )
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 , 1 ]
horizAxisOffset = read_u16 ( f )
vertAxisOffset = read_u16 ( f )
with SaveTell ( f ) :
horizAxis = parse_at_optional_offset ( f , start_tell , horizAxisOffset , parse_axis_table )
vertAxis = parse_at_optional_offset ( f , start_tell , vertAxisOffset , parse_axis_table )
if minorVersion == 0 :
return BASETable_Ver_1_0 ( majorVersion , minorVersion , horizAxis , vertAxis )
assert False , f " Unimplemented: minorVersion: { minorVersion } "
@dataclass
class AttachPointTable ( Table ) :
pointCount : int
pointIndices : List [ int ]
def parse_attach_point_table ( f : BinaryIO ) - > AttachPointTable :
pointCount = read_u16 ( f )
pointIndices = [ read_u16 ( f ) for _ in range ( pointCount ) ]
return AttachPointTable ( pointCount , pointIndices )
@dataclass
class AttachPointListTable ( Table ) :
coverage : CoverageTable
glyphCount : int
attachPoints : List [ AttachPointTable ]
def parse_attach_point_list_table ( f : BinaryIO ) - > AttachPointListTable :
start_tell = f . tell ( )
coverageOffset = read_u16 ( f )
glyphCount = read_u16 ( f )
attachPointOffsets = [ read_u16 ( f ) for _ in range ( glyphCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
attachPoints = parse_at_offsets ( f , start_tell , attachPointOffsets , parse_attach_point_table )
return AttachPointListTable ( coverage , glyphCount , attachPoints )
@dataclass
class CaretValueTable ( Table , ABD ) :
caretValueFormat : int
@dataclass
class CaretValueTable_Format_1 ( CaretValueTable ) :
coordinate : int
def parse_caret_value_table ( f : BinaryIO ) - > CaretValueTable :
caretValueFormat = read_u16 ( f )
assert caretValueFormat in [ 1 , 2 , 3 ]
match caretValueFormat :
case 1 :
coordinate = read_i16 ( f )
return CaretValueTable_Format_1 ( caretValueFormat , coordinate )
case _ :
assert False , f " Unimplemented: caretValueFormat: { caretValueFormat } "
assert False , caretValueFormat
@dataclass
class LigGlyphTable ( Table ) :
caretCount : int
caretValues : List [ CaretValueTable ]
def parse_lig_glyph_table ( f : BinaryIO ) - > LigGlyphTable :
start_tell = f . tell ( )
caretCount = read_u16 ( f )
caretValueOffsets = [ read_u16 ( f ) for _ in range ( caretCount ) ]
with SaveTell ( f ) :
caretValues = parse_at_offsets ( f , start_tell , caretValueOffsets , parse_caret_value_table )
return LigGlyphTable ( caretCount , caretValues )
@dataclass
class LigCaretListTable ( Table ) :
coverage : CoverageTable
ligGlyphCount : int
ligGlyphs : List [ LigGlyphTable ]
def parse_lig_caret_list_table ( f : BinaryIO ) - > LigCaretListTable :
start_tell = f . tell ( )
coverageOffset = read_u16 ( f )
ligGlyphCount = read_u16 ( f )
ligGlyphOffsets = [ read_u16 ( f ) for _ in range ( ligGlyphCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
ligGlyphs = parse_at_offsets ( f , start_tell , ligGlyphOffsets , parse_lig_glyph_table )
return LigCaretListTable ( coverage , ligGlyphCount , ligGlyphs )
@dataclass
class GDEFTable ( Table , ABD ) :
majorVersion : int
minorVersion : int
glyphClassDef : Optional [ ClassDefTable ]
attachList : Optional [ AttachPointListTable ]
ligCaretList : Optional [ LigCaretListTable ]
markAttachClassDef : Optional [ ClassDefTable ]
@dataclass
class GDEFTable_Ver_1_0 ( GDEFTable ) : pass
@dataclass
class MarkGlyphSetsTable ( Table ) :
markGlyphSetTableFormat : int
markGlyphSetCount : int
coverages : List [ CoverageTable ]
def parse_mark_glyph_sets_table ( f : BinaryIO ) - > MarkGlyphSetsTable :
start_tell = f . tell ( )
markGlyphSetTableFormat = read_u16 ( f )
assert markGlyphSetTableFormat == 1
markGlyphSetCount = read_u16 ( f )
coverageOffsets = [ read_u32 ( f ) for _ in range ( markGlyphSetCount ) ]
with SaveTell ( f ) :
coverages = parse_at_offsets ( f , start_tell , coverageOffsets , parse_coverage_table )
return MarkGlyphSetsTable ( markGlyphSetTableFormat , markGlyphSetCount , coverages )
@dataclass
class GDEFTable_Ver_1_2 ( GDEFTable_Ver_1_0 ) :
markGlyphSetsDef : Optional [ MarkGlyphSetsTable ]
@dataclass
class GDEFTable_Ver_1_3 ( GDEFTable_Ver_1_2 ) :
itemVarStore : ' Optional[ItemVariationStoreTable] '
def parse_GDEF_table ( f : BinaryIO ) - > GDEFTable :
start_tell = f . tell ( )
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 , 2 , 3 ] , f " Invalid minorVersion: { minorVersion } . Expected 0, 2, or 3. "
glyphClassDefOffset = read_u16 ( f )
attachListOffset = read_u16 ( f )
ligCaretListOffset = read_u16 ( f )
markAttachClassDefOffset = read_u16 ( f )
with SaveTell ( f ) :
glyphClassDef = parse_at_optional_offset ( f , start_tell , glyphClassDefOffset , parse_class_def_table )
attachList = parse_at_optional_offset ( f , start_tell , attachListOffset , parse_attach_point_list_table )
ligCaretList = parse_at_optional_offset ( f , start_tell , ligCaretListOffset , parse_lig_caret_list_table )
markAttachClassDef = parse_at_optional_offset ( f , start_tell , markAttachClassDefOffset , parse_class_def_table )
if minorVersion == 0 :
return GDEFTable_Ver_1_0 ( majorVersion , minorVersion , glyphClassDef , attachList , ligCaretList , markAttachClassDef )
markGlyphSetsDefOffset = read_u16 ( f )
with SaveTell ( f ) :
markGlyphSetsDef = parse_at_optional_offset ( f , start_tell , markGlyphSetsDefOffset , parse_mark_glyph_sets_table )
if minorVersion == 2 :
return GDEFTable_Ver_1_2 ( majorVersion , minorVersion , glyphClassDef , attachList , ligCaretList , markAttachClassDef , markGlyphSetsDef )
itemVarStoreOffset = read_u32 ( f )
with SaveTell ( f ) :
itemVarStore = parse_at_optional_offset ( f , start_tell , itemVarStoreOffset , parse_item_variation_store_table )
if minorVersion == 3 :
return GDEFTable_Ver_1_3 ( majorVersion , minorVersion , glyphClassDef , attachList , ligCaretList , markAttachClassDef , markGlyphSetsDef , itemVarStore )
assert False , f " Unimplemented: minorVersion: { minorVersion } "
@dataclass
class ValueRecord :
xPlacement : Optional [ int ]
yPlacement : Optional [ int ]
xAdvance : Optional [ int ]
yAdvance : Optional [ int ]
xPlaDevice : Optional [ DeviceTable_ ]
yPlaDevice : Optional [ DeviceTable_ ]
xAdvDevice : Optional [ DeviceTable_ ]
yAdvDevice : Optional [ DeviceTable_ ]
def parse_value_record ( f : BinaryIO , start_tell : int , valueFormat : ValueFormatFlags ) - > ValueRecord :
xPlacement = read_i16 ( f ) if valueFormat . x_placement ( ) else None
yPlacement = read_i16 ( f ) if valueFormat . y_placement ( ) else None
xAdvance = read_i16 ( f ) if valueFormat . x_advance ( ) else None
yAdvance = read_i16 ( f ) if valueFormat . y_advance ( ) else None
2024-09-15 16:10:41 +10:00
xPlaDeviceOffset = read_u16 ( f ) if valueFormat . x_placement_device ( ) else 0
yPlaDeviceOffset = read_u16 ( f ) if valueFormat . y_placement_device ( ) else 0
xAdvDeviceOffset = read_u16 ( f ) if valueFormat . x_advance_device ( ) else 0
yAdvDeviceOffset = read_u16 ( f ) if valueFormat . y_advance_device ( ) else 0
2024-05-03 21:02:58 +10:00
with SaveTell ( f ) :
xPlaDevice = parse_at_optional_offset ( f , start_tell , xPlaDeviceOffset , parse_device_table )
yPlaDevice = parse_at_optional_offset ( f , start_tell , yPlaDeviceOffset , parse_device_table )
xAdvDevice = parse_at_optional_offset ( f , start_tell , xAdvDeviceOffset , parse_device_table )
yAdvDevice = parse_at_optional_offset ( f , start_tell , yAdvDeviceOffset , parse_device_table )
return ValueRecord ( xPlacement , yPlacement , xAdvance , yAdvance , xPlaDevice , yPlaDevice , xAdvDevice , yAdvDevice )
@dataclass
class AnchorTable ( Table , ABD ) :
anchorFormat : int
@dataclass
class AnchorTable_Format_1 ( AnchorTable ) :
xCoordinate : int
yCoordinate : int
@dataclass
class AnchorTable_Format_2 ( AnchorTable ) :
xCoordinate : int
yCoordinate : int
anchorPoint : int
@dataclass
class AnchorTable_Format_3 ( AnchorTable ) :
xCoordinate : int
yCoordinate : int
xDevice : Optional [ DeviceTable_ ]
yDevice : Optional [ DeviceTable_ ]
def parse_anchor_table ( f : BinaryIO ) - > AnchorTable :
start_tell = f . tell ( )
anchorFormat = read_u16 ( f )
assert anchorFormat in [ 1 , 2 , 3 ] , f " Invalid anchorFormat: { anchorFormat } "
match anchorFormat :
case 1 :
xCoordinate = read_i16 ( f )
yCoordinate = read_i16 ( f )
return AnchorTable_Format_1 ( anchorFormat , xCoordinate , yCoordinate )
case 2 :
xCoordinate = read_i16 ( f )
yCoordinate = read_i16 ( f )
anchorPoint = read_u16 ( f )
return AnchorTable_Format_2 ( anchorFormat , xCoordinate , yCoordinate , anchorPoint )
case 3 :
xCoordinate = read_i16 ( f )
yCoordinate = read_i16 ( f )
xDeviceOffset = read_u16 ( f )
yDeviceOffset = read_u16 ( f )
with SaveTell ( f ) :
xDevice = parse_at_optional_offset ( f , start_tell , xDeviceOffset , parse_device_table )
yDevice = parse_at_optional_offset ( f , start_tell , yDeviceOffset , parse_device_table )
return AnchorTable_Format_3 ( anchorFormat , xCoordinate , yCoordinate , xDevice , yDevice )
case _ :
assert False , f " Unimplemented: anchorFormat: { anchorFormat } "
assert False
class GPOSLookupType ( Enum ) :
SinglePos = 1
PairPos = 2
CursivePos = 3
MarkBasePos = 4
MarkLigPos = 5
MarkMarkPos = 6
ContextPos = 7
ChainContextPos = 8
ExtensionPos = 9
def __str__ ( self ) - > str : return self . _name_
@dataclass
class GPOSLookupSubtable ( LookupSubtable , ABD ) : pass
# 1
@dataclass
class SinglePosSubtable ( GPOSLookupSubtable , ABD ) :
posFormat : int
@dataclass
class SinglePosSubtable_Format_1 ( SinglePosSubtable ) :
coverage : CoverageTable
valueFormat : ValueFormatFlags
valueRecord : ValueRecord
@dataclass
class SinglePosSubtable_Format_2 ( SinglePosSubtable ) :
coverage : CoverageTable
valueFormat : ValueFormatFlags
valueCount : int
valueRecords : List [ ValueRecord ]
# 2
@dataclass
class PairPosSubtable ( GPOSLookupSubtable , ABD ) :
posFormat : int
@dataclass
class PairValueRecord :
secondGlyph : int
valueRecord1 : ValueRecord
valueRecord2 : ValueRecord
def parse_pair_value_record ( f : BinaryIO , start_tell : int , valueFormat1 : ValueFormatFlags , valueFormat2 : ValueFormatFlags ) - > PairValueRecord :
secondGlyph = read_u16 ( f )
valueRecord1 = parse_value_record ( f , start_tell , valueFormat1 )
valueRecord2 = parse_value_record ( f , start_tell , valueFormat2 )
return PairValueRecord ( secondGlyph , valueRecord1 , valueRecord2 )
@dataclass
class PairSetTable ( Table ) :
pairValueCount : int
pairValueRecords : List [ PairValueRecord ]
def parse_pair_set_table ( f : BinaryIO , start_tell : int , valueFormat1 : ValueFormatFlags , valueFormat2 : ValueFormatFlags ) - > PairSetTable :
"""
` start_tell ` should be the start of the PairSetTable , so pass in ` f . tell ( ) `
It ' s a parameter because I might be wrong, so there is always the option to pass in another `start_tell`.
"""
# start_tell = f.tell()
pairValueCount = read_u16 ( f )
pairValueRecords = [ parse_pair_value_record ( f , start_tell , valueFormat1 , valueFormat2 ) for _ in range ( pairValueCount ) ]
return PairSetTable ( pairValueCount , pairValueRecords )
@dataclass
class PairPosSubtable_Format_1 ( PairPosSubtable ) :
coverage : CoverageTable
valueFormat1 : ValueFormatFlags
valueFormat2 : ValueFormatFlags
pairSetCount : int
pairSets : List [ PairSetTable ]
@dataclass
class Class2Record :
valueRecord1 : ValueRecord
valueRecord2 : ValueRecord
def parse_class2_record ( f : BinaryIO , start_tell : int , valueFormat1 : ValueFormatFlags , valueFormat2 : ValueFormatFlags ) - > Class2Record :
valueRecord1 = parse_value_record ( f , start_tell , valueFormat1 )
valueRecord2 = parse_value_record ( f , start_tell , valueFormat2 )
return Class2Record ( valueRecord1 , valueRecord2 )
@dataclass
class Class1Record :
class2Records : List [ Class2Record ]
def parse_class1_record ( f : BinaryIO , start_tell : int , class2Count : int , valueFormat1 : ValueFormatFlags , valueFormat2 : ValueFormatFlags ) - > Class1Record :
class2Records = [ parse_class2_record ( f , start_tell , valueFormat1 , valueFormat2 ) for _ in range ( class2Count ) ]
return Class1Record ( class2Records )
@dataclass
class PairPosSubtable_Format_2 ( PairPosSubtable ) :
coverage : CoverageTable
valueFormat1 : ValueFormatFlags
valueFormat2 : ValueFormatFlags
classDef1 : ClassDefTable
classDef2 : ClassDefTable
class1Count : int
class2Count : int
class1Record : List [ Class1Record ]
# 3
@dataclass
class CursivePosSubtable ( GPOSLookupSubtable , ABD ) :
posFormat : int
@dataclass
class EntryExitRecord :
entryAnchor : Optional [ AnchorTable ]
exitAnchor : Optional [ AnchorTable ]
def parse_entry_exit_record ( f : BinaryIO , start_tell : int ) - > EntryExitRecord :
entryAnchorOffset = read_u16 ( f )
exitAnchorOffset = read_u16 ( f )
with SaveTell ( f ) :
entryAnchor = parse_at_optional_offset ( f , start_tell , entryAnchorOffset , parse_anchor_table )
exitAnchor = parse_at_optional_offset ( f , start_tell , exitAnchorOffset , parse_anchor_table )
return EntryExitRecord ( entryAnchor , exitAnchor )
@dataclass
class CursivePosSubtable_Format_1 ( CursivePosSubtable ) :
coverage : CoverageTable
entryExitCount : int
entryExitRecord : List [ EntryExitRecord ]
# 4?
@dataclass
class MarkRecord :
markClass : int
markAnchor : AnchorTable
def parse_mark_record ( f : BinaryIO , start_tell : int ) - > MarkRecord :
markClass = read_u16 ( f )
markAnchorOffset = read_u16 ( f )
with SaveTell ( f ) :
markAnchor = parse_at_offset ( f , start_tell , markAnchorOffset , parse_anchor_table )
return MarkRecord ( markClass , markAnchor )
@dataclass
class MarkArrayTable ( Table ) :
markCount : int
markRecords : List [ MarkRecord ]
def parse_mark_array_table ( f : BinaryIO ) - > MarkArrayTable :
start_tell = f . tell ( )
markCount = read_u16 ( f )
markRecords = [ parse_mark_record ( f , start_tell ) for _ in range ( markCount ) ]
return MarkArrayTable ( markCount , markRecords )
@dataclass
class MarkBasePosSubtable ( GPOSLookupSubtable , ABD ) :
posFormat : int
@dataclass
class BaseRecord :
baseAnchor : List [ Optional [ AnchorTable ] ] # ISO Documentation doesn't say that this can be null, but Microsoft documentation does, and the file I'm using to test agrees with Microsoft, so...
def parse_base_record ( f : BinaryIO , markClassCount : int , start_tell : int ) - > BaseRecord :
baseAnchorOffsets = [ read_u16 ( f ) for _ in range ( markClassCount ) ]
with SaveTell ( f ) :
baseAnchor = parse_at_optional_offsets ( f , start_tell , baseAnchorOffsets , parse_anchor_table )
return BaseRecord ( baseAnchor )
@dataclass
class BaseArrayTable ( Table ) :
baseCount : int
baseRecords : List [ BaseRecord ]
def parse_base_array_table ( f : BinaryIO , markClassCount : int ) - > BaseArrayTable :
start_tell = f . tell ( )
baseCount = read_u16 ( f )
baseRecords = [ parse_base_record ( f , markClassCount , start_tell ) for _ in range ( baseCount ) ]
return BaseArrayTable ( baseCount , baseRecords )
# TODO: Maybe don't reparse the same region if they're all pointing to the same place in the file?
@dataclass
class MarkBasePosSubtable_Format_1 ( MarkBasePosSubtable ) :
markCoverage : CoverageTable
baseCoverage : CoverageTable
markClassCount : int
markArray : MarkArrayTable
baseArray : BaseArrayTable
# 5
@dataclass
class MarkLigPosSubtable ( GPOSLookupSubtable , ABD ) :
posFormat : int
@dataclass
class ComponentRecord :
ligatureAnchors : List [ AnchorTable ]
def parse_component_record ( f : BinaryIO , start_tell : int , markClassCount : int ) - > ComponentRecord :
ligatureAnchorOffsets = [ read_u16 ( f ) for _ in range ( markClassCount ) ]
with SaveTell ( f ) :
ligatureAnchors = parse_at_offsets ( f , start_tell , ligatureAnchorOffsets , parse_anchor_table )
return ComponentRecord ( ligatureAnchors )
@dataclass
class LigatureAttachTable ( Table ) :
componentCount : int
componentRecords : List [ ComponentRecord ]
def parse_ligature_attach_table ( f : BinaryIO , markClassCount : int ) - > LigatureAttachTable :
start_tell = f . tell ( )
componentCount = read_u16 ( f )
componentRecords = [ parse_component_record ( f , start_tell , markClassCount ) for _ in range ( componentCount ) ]
return LigatureAttachTable ( componentCount , componentRecords )
@dataclass
class LigatureArrayTable ( Table ) :
ligatureCount : int
ligatureAttachs : List [ LigatureAttachTable ]
def parse_ligature_array_table ( f : BinaryIO , markClassCount : int ) - > LigatureArrayTable :
start_tell = f . tell ( )
ligatureCount = read_u16 ( f )
ligatureAttachOffsets = [ read_u16 ( f ) for _ in range ( ligatureCount ) ]
with SaveTell ( f ) :
ligatureAttachs = parse_at_offsets ( f , start_tell , ligatureAttachOffsets , lambda f : parse_ligature_attach_table ( f , markClassCount ) )
return LigatureArrayTable ( ligatureCount , ligatureAttachs )
@dataclass
class MarkLigPosSubtable_Format_1 ( MarkLigPosSubtable ) :
markCoverage : CoverageTable
ligatureCoverage : CoverageTable
markClassCount : int
markArray : MarkArrayTable
ligatureArray : LigatureArrayTable
# 6
@dataclass
class MarkMarkPosSubtable ( GPOSLookupSubtable , ABD ) :
posFormat : int
@dataclass
class Mark2Record :
mark2Anchor : List [ Optional [ AnchorTable ] ]
def parse_mark2_record ( f : BinaryIO , markClassCount : int , start_tell : int ) - > Mark2Record :
mark2AnchorOffsets = [ read_u16 ( f ) for _ in range ( markClassCount ) ]
with SaveTell ( f ) :
mark2Anchor = parse_at_optional_offsets ( f , start_tell , mark2AnchorOffsets , parse_anchor_table )
return Mark2Record ( mark2Anchor )
@dataclass
class Mark2ArrayTable ( Table ) :
mark2Count : int
mark2Records : List [ Mark2Record ]
def parse_mark2_array_table ( f : BinaryIO , markClassCount : int ) - > Mark2ArrayTable :
start_tell = f . tell ( )
mark2Count = read_u16 ( f )
mark2Records = [ parse_mark2_record ( f , markClassCount , start_tell ) for _ in range ( mark2Count ) ]
return Mark2ArrayTable ( mark2Count , mark2Records )
@dataclass
class MarkMarkPosSubtable_Format_1 ( MarkMarkPosSubtable ) :
mark1Coverage : CoverageTable
mark2Coverage : CoverageTable
markClassCount : int
mark1Array : MarkArrayTable
mark2Array : Mark2ArrayTable
# 7
@dataclass
class ContextPosSubtable ( GPOSLookupSubtable , ABD ) :
2024-09-15 16:17:20 +10:00
posFormat : int
2024-05-03 21:02:58 +10:00
@dataclass
class PosLookupRecord :
sequenceIndex : int
lookupListIndex : int
def parse_pos_lookup_record ( f : BinaryIO ) - > PosLookupRecord :
sequenceIndex = read_u16 ( f )
lookupListIndex = read_u16 ( f )
return PosLookupRecord ( sequenceIndex , lookupListIndex )
2024-09-15 16:17:20 +10:00
@dataclass
class PosRuleTable ( Table ) :
glyphCount : int
posCount : int
inputSequence : List [ int ]
posLookupRecords : List [ PosLookupRecord ]
def parse_pos_rule_table ( f : BinaryIO ) - > PosRuleTable :
glyphCount = read_u16 ( f )
posCount = read_u16 ( f )
inputSequence = [ read_u16 ( f ) for _ in range ( glyphCount - 1 ) ]
posLookupRecords = [ parse_pos_lookup_record ( f ) for _ in range ( posCount ) ]
return PosRuleTable ( glyphCount , posCount , inputSequence , posLookupRecords )
PosRuleSetTable = SetTable [ PosRuleTable ]
def parse_pos_rule_set_table ( f : BinaryIO ) - > PosRuleSetTable :
return parse_set_table ( f , parse_pos_rule_table )
@dataclass
class ContextPosSubtable_Format_1 ( ContextPosSubtable ) :
coverage : CoverageTable
posRuleSetCount : int
posRuleSets : List [ PosRuleSetTable ]
@dataclass
class PosClassRuleTable ( Table ) :
glyphCount : int
posCount : int
classes : List [ int ]
posLookupRecords : List [ PosLookupRecord ]
def parse_pos_class_rule_table ( f : BinaryIO ) - > PosClassRuleTable :
glyphCount = read_u16 ( f )
posCount = read_u16 ( f )
classes = [ read_u16 ( f ) for _ in range ( glyphCount - 1 ) ]
posLookupRecords = [ parse_pos_lookup_record ( f ) for _ in range ( posCount ) ]
return PosClassRuleTable ( glyphCount , posCount , classes , posLookupRecords )
PosClassSetTable = SetTable [ PosClassRuleTable ]
def parse_pos_class_set_table ( f : BinaryIO ) - > PosClassSetTable :
return parse_set_table ( f , parse_pos_class_rule_table )
@dataclass
class ContextPosSubtable_Format_2 ( ContextPosSubtable ) :
coverage : CoverageTable
classDef : ClassDefTable
posClassSetCount : int
posClassSets : List [ Optional [ PosClassSetTable ] ]
2024-05-03 21:02:58 +10:00
# 8
@dataclass
class ChainContextPosSubtable ( GPOSLookupSubtable , ABD ) :
posFormat : int
@dataclass
class ChainPosRuleTable ( Table ) :
backtrackGlyphCount : int
backtrackSequence : List [ int ]
inputGlyphCount : int
inputSequence : List [ int ]
lookaheadGlyphCount : int
lookAheadSequence : List [ int ]
posCount : int
posLookupRecords : List [ PosLookupRecord ]
def parse_chain_pos_rule_table ( f : BinaryIO ) - > ChainPosRuleTable :
backtrackGlyphCount = read_u16 ( f )
backtrackSequence = [ read_u16 ( f ) for _ in range ( backtrackGlyphCount ) ]
inputGlyphCount = read_u16 ( f )
inputSequence = [ read_u16 ( f ) for _ in range ( inputGlyphCount - 1 ) ]
lookaheadGlyphCount = read_u16 ( f )
lookAheadSequence = [ read_u16 ( f ) for _ in range ( lookaheadGlyphCount ) ]
posCount = read_u16 ( f )
posLookupRecords = [ parse_pos_lookup_record ( f ) for _ in range ( posCount ) ]
return ChainPosRuleTable ( backtrackGlyphCount , backtrackSequence , inputGlyphCount , inputSequence , lookaheadGlyphCount , lookAheadSequence , posCount , posLookupRecords )
2024-05-04 11:34:37 +10:00
ChainPosRuleSetTable = SetTable [ ChainPosRuleTable ]
def parse_chain_pos_rule_set_table ( f : BinaryIO ) - > ChainPosRuleSetTable :
return parse_set_table ( f , parse_chain_pos_rule_table )
2024-05-03 21:02:58 +10:00
@dataclass
class ChainContextPosSubtable_Format_1 ( ChainContextPosSubtable ) :
coverage : CoverageTable
chainPosRuleSetCount : int
2024-05-04 11:34:37 +10:00
chainPosRuleSets : List [ ChainPosRuleSetTable ]
2024-05-03 21:02:58 +10:00
@dataclass
class ChainPosClassRuleTable ( Table ) :
backtrackGlyphCount : int
backtrackSequence : List [ int ]
inputGlyphCount : int
inputSequence : List [ int ]
lookaheadGlyphCount : int
lookAheadSequence : List [ int ]
posCount : int
posLookupRecords : List [ PosLookupRecord ]
def parse_chain_pos_class_rule_table ( f : BinaryIO ) - > ChainPosClassRuleTable :
backtrackGlyphCount = read_u16 ( f )
backtrackSequence = [ read_u16 ( f ) for _ in range ( backtrackGlyphCount ) ]
inputGlyphCount = read_u16 ( f )
inputSequence = [ read_u16 ( f ) for _ in range ( inputGlyphCount - 1 ) ]
lookaheadGlyphCount = read_u16 ( f )
lookAheadSequence = [ read_u16 ( f ) for _ in range ( lookaheadGlyphCount ) ]
posCount = read_u16 ( f )
posLookupRecords = [ parse_pos_lookup_record ( f ) for _ in range ( posCount ) ]
return ChainPosClassRuleTable ( backtrackGlyphCount , backtrackSequence , inputGlyphCount , inputSequence , lookaheadGlyphCount , lookAheadSequence , posCount , posLookupRecords )
2024-05-04 11:34:37 +10:00
ChainPosClassRuleSetTable = SetTable [ ChainPosClassRuleTable ]
def parse_chain_pos_class_rule_set_table ( f : BinaryIO ) - > ChainPosClassRuleSetTable :
return parse_set_table ( f , parse_chain_pos_class_rule_table )
2024-05-03 21:02:58 +10:00
@dataclass
class ChainContextPosSubtable_Format_2 ( ChainContextPosSubtable ) :
coverage : CoverageTable
backtrackClassDef : ClassDefTable
inputClassDef : ClassDefTable
lookaheadClassDef : ClassDefTable
chainPosClassSetCount : int
2024-05-04 11:34:37 +10:00
chainPosClassSets : List [ Optional [ ChainPosClassRuleSetTable ] ]
2024-05-03 21:02:58 +10:00
@dataclass
class ChainContextPosSubtable_Format_3 ( ChainContextPosSubtable ) :
backtrackGlyphCount : int
backtrackCoverages : List [ CoverageTable ]
inputGlyphCount : int
inputCoverages : List [ CoverageTable ]
lookaheadGlyphCount : int
lookaheadCoverages : List [ CoverageTable ]
posCount : int
posLookupRecords : List [ PosLookupRecord ]
# 9
@dataclass
class ExtensionPosSubtable ( GPOSLookupSubtable , ABD ) :
posFormat : int
@dataclass
class ExtensionPosSubtable_Format_1 ( ExtensionPosSubtable ) :
extensionLookupType : GPOSLookupType
extension : GPOSLookupSubtable
def parse_GPOS_lookup_subtable ( f : BinaryIO , lookupType : GPOSLookupType ) - > GPOSLookupSubtable :
match lookupType :
case GPOSLookupType . SinglePos :
start_tell = f . tell ( )
posFormat = read_u16 ( f )
assert posFormat in [ 1 , 2 ]
match posFormat :
case 1 :
coverageOffset = read_u16 ( f )
valueFormat = parse_value_format ( f )
valueRecord = parse_value_record ( f , start_tell , valueFormat )
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
return SinglePosSubtable_Format_1 ( posFormat , coverage , valueFormat , valueRecord )
case 2 :
coverageOffset = read_u16 ( f )
valueFormat = parse_value_format ( f )
valueCount = read_u16 ( f )
valueRecords = [ parse_value_record ( f , start_tell , valueFormat ) for _ in range ( valueCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
return SinglePosSubtable_Format_2 ( posFormat , coverage , valueFormat , valueCount , valueRecords )
case _ :
assert False , f " Unimplemented: posFormat: { posFormat } "
assert False , posFormat
case GPOSLookupType . PairPos :
start_tell = f . tell ( )
posFormat = read_u16 ( f )
assert posFormat in [ 1 , 2 ]
match posFormat :
case 1 :
coverageOffset = read_u16 ( f )
valueFormat1 = parse_value_format ( f )
valueFormat2 = parse_value_format ( f )
pairSetCount = read_u16 ( f )
pairSetOffsets = [ read_u16 ( f ) for _ in range ( pairSetCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
# I think that the documentation for the Device tables located in these PairSetTables is wrong.
# ISO Documentation says that the offsets are from the start of the PairPos Subtable,
# but I think that it is actually from the start of the PairSet table.
# ttf-parser also seems to agree.
pairSets = parse_at_offsets ( f , start_tell , pairSetOffsets , lambda f : parse_pair_set_table ( f , f . tell ( ) , valueFormat1 , valueFormat2 ) )
return PairPosSubtable_Format_1 ( posFormat , coverage , valueFormat1 , valueFormat2 , pairSetCount , pairSets )
case 2 :
coverageOffset = read_u16 ( f )
valueFormat1 = parse_value_format ( f )
valueFormat2 = parse_value_format ( f )
classDef1Offset = read_u16 ( f )
classDef2Offset = read_u16 ( f )
class1Count = read_u16 ( f )
class2Count = read_u16 ( f )
class1Record = [ parse_class1_record ( f , start_tell , class2Count , valueFormat1 , valueFormat2 ) for _ in range ( class1Count ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
classDef1 = parse_at_offset ( f , start_tell , classDef1Offset , parse_class_def_table )
classDef2 = parse_at_offset ( f , start_tell , classDef2Offset , parse_class_def_table )
return PairPosSubtable_Format_2 ( posFormat , coverage , valueFormat1 , valueFormat2 , classDef1 , classDef2 , class1Count , class2Count , class1Record )
case _ :
assert False , f " Unimplemented: posFormat: { posFormat } "
assert False , posFormat
case GPOSLookupType . CursivePos :
start_tell = f . tell ( )
posFormat = read_u16 ( f )
assert posFormat in [ 1 ]
match posFormat :
case 1 :
coverageOffset = read_u16 ( f )
entryExitCount = read_u16 ( f )
entryExitRecord = [ parse_entry_exit_record ( f , start_tell ) for _ in range ( entryExitCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
return CursivePosSubtable_Format_1 ( posFormat , coverage , entryExitCount , entryExitRecord )
case _ :
assert False , f " Unimplemented: posFormat: { posFormat } "
assert False , posFormat
case GPOSLookupType . MarkBasePos :
start_tell = f . tell ( )
posFormat = read_u16 ( f )
assert posFormat in [ 1 ]
match posFormat :
case 1 :
markCoverageOffset = read_u16 ( f )
baseCoverageOffset = read_u16 ( f )
markClassCount = read_u16 ( f )
markArrayOffset = read_u16 ( f )
baseArrayOffset = read_u16 ( f )
with SaveTell ( f ) :
markCoverage = parse_at_offset ( f , start_tell , markCoverageOffset , parse_coverage_table )
baseCoverage = parse_at_offset ( f , start_tell , baseCoverageOffset , parse_coverage_table )
markArray = parse_at_offset ( f , start_tell , markArrayOffset , parse_mark_array_table )
baseArray = parse_at_offset ( f , start_tell , baseArrayOffset , lambda f : parse_base_array_table ( f , markClassCount ) )
return MarkBasePosSubtable_Format_1 ( posFormat , markCoverage , baseCoverage , markClassCount , markArray , baseArray )
case _ :
assert False , f " Unimplemented: posFormat: { posFormat } "
assert False , posFormat
case GPOSLookupType . MarkLigPos :
start_tell = f . tell ( )
posFormat = read_u16 ( f )
assert posFormat in [ 1 ]
match posFormat :
case 1 :
markCoverageOffset = read_u16 ( f )
ligatureCoverageOffset = read_u16 ( f )
markClassCount = read_u16 ( f )
markArrayOffset = read_u16 ( f )
ligatureArrayOffset = read_u16 ( f )
with SaveTell ( f ) :
markCoverage = parse_at_offset ( f , start_tell , markCoverageOffset , parse_coverage_table )
ligatureCoverage = parse_at_offset ( f , start_tell , ligatureCoverageOffset , parse_coverage_table )
markArray = parse_at_offset ( f , start_tell , markArrayOffset , parse_mark_array_table )
ligatureArray = parse_at_offset ( f , start_tell , ligatureArrayOffset , lambda f : parse_ligature_array_table ( f , markClassCount ) )
return MarkLigPosSubtable_Format_1 ( posFormat , markCoverage , ligatureCoverage , markClassCount , markArray , ligatureArray )
case _ :
assert False , f " Unimplemented: posFormat: { posFormat } "
assert False , posFormat
case GPOSLookupType . MarkMarkPos :
start_tell = f . tell ( )
posFormat = read_u16 ( f )
assert posFormat in [ 1 ]
match posFormat :
case 1 :
mark1CoverageOffset = read_u16 ( f )
mark2CoverageOffset = read_u16 ( f )
markClassCount = read_u16 ( f )
mark1ArrayOffset = read_u16 ( f )
mark2ArrayOffset = read_u16 ( f )
with SaveTell ( f ) :
mark1Coverage = parse_at_offset ( f , start_tell , mark1CoverageOffset , parse_coverage_table )
mark2Coverage = parse_at_offset ( f , start_tell , mark2CoverageOffset , parse_coverage_table )
mark1Array = parse_at_offset ( f , start_tell , mark1ArrayOffset , parse_mark_array_table )
mark2Array = parse_at_offset ( f , start_tell , mark2ArrayOffset , lambda f : parse_mark2_array_table ( f , markClassCount ) )
return MarkMarkPosSubtable_Format_1 ( posFormat , mark1Coverage , mark2Coverage , markClassCount , mark1Array , mark2Array )
case _ :
assert False , f " Unimplemented: posFormat: { posFormat } "
2024-09-15 16:17:20 +10:00
assert False , posFormat
case GPOSLookupType . ContextPos :
start_tell = f . tell ( )
posFormat = read_u16 ( f )
assert posFormat in [ 1 , 2 , 3 ]
match posFormat :
case 1 :
coverageOffset = read_u16 ( f )
posRuleSetCount = read_u16 ( f )
posRuleSetOffsets = [ read_u16 ( f ) for _ in range ( posRuleSetCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
posRuleSets = parse_at_offsets ( f , start_tell , posRuleSetOffsets , parse_pos_rule_set_table )
return ContextPosSubtable_Format_1 ( posFormat , coverage , posRuleSetCount , posRuleSets )
case 2 :
coverageOffset = read_u16 ( f )
classDefOffset = read_u16 ( f )
posClassSetCount = read_u16 ( f )
posClassSetOffsets = [ read_u16 ( f ) for _ in range ( posClassSetCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
classDef = parse_at_offset ( f , start_tell , classDefOffset , parse_class_def_table )
posClassSets = parse_at_optional_offsets ( f , start_tell , posClassSetOffsets , parse_pos_class_set_table )
return ContextPosSubtable_Format_2 ( posFormat , coverage , classDef , posClassSetCount , posClassSets )
case _ :
assert False , f " Unimplemented: posFormat: { posFormat } "
2024-05-03 21:02:58 +10:00
assert False , posFormat
case GPOSLookupType . ChainContextPos :
start_tell = f . tell ( )
posFormat = read_u16 ( f )
assert posFormat in [ 1 , 2 , 3 ]
match posFormat :
case 1 :
coverageOffset = read_u16 ( f )
chainPosRuleSetCount = read_u16 ( f )
chainPosRuleSetOffsets = [ read_u16 ( f ) for _ in range ( chainPosRuleSetCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
2024-05-04 11:34:37 +10:00
chainPosRuleSets = parse_at_offsets ( f , start_tell , chainPosRuleSetOffsets , parse_chain_pos_rule_set_table )
2024-05-03 21:02:58 +10:00
return ChainContextPosSubtable_Format_1 ( posFormat , coverage , chainPosRuleSetCount , chainPosRuleSets )
case 2 :
coverageOffset = read_u16 ( f )
backtrackClassDefOffset = read_u16 ( f )
inputClassDefOffset = read_u16 ( f )
lookaheadClassDefOffset = read_u16 ( f )
chainPosClassSetCount = read_u16 ( f )
chainPosClassSetOffsets = [ read_u16 ( f ) for _ in range ( chainPosClassSetCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
backtrackClassDef = parse_at_offset ( f , start_tell , backtrackClassDefOffset , parse_class_def_table )
inputClassDef = parse_at_offset ( f , start_tell , inputClassDefOffset , parse_class_def_table )
lookaheadClassDef = parse_at_offset ( f , start_tell , lookaheadClassDefOffset , parse_class_def_table )
2024-05-04 11:34:37 +10:00
chainPosClassSets = parse_at_optional_offsets ( f , start_tell , chainPosClassSetOffsets , parse_chain_pos_class_rule_set_table )
2024-05-03 21:02:58 +10:00
return ChainContextPosSubtable_Format_2 ( posFormat , coverage , backtrackClassDef , inputClassDef , lookaheadClassDef , chainPosClassSetCount , chainPosClassSets )
case 3 :
backtrackGlyphCount = read_u16 ( f )
backtrackCoverageOffsets = [ read_u16 ( f ) for _ in range ( backtrackGlyphCount ) ]
inputGlyphCount = read_u16 ( f )
inputCoverageOffsets = [ read_u16 ( f ) for _ in range ( inputGlyphCount ) ]
lookaheadGlyphCount = read_u16 ( f )
lookaheadCoverageOffsets = [ read_u16 ( f ) for _ in range ( lookaheadGlyphCount ) ]
posCount = read_u16 ( f )
posLookupRecords = [ parse_pos_lookup_record ( f ) for _ in range ( posCount ) ]
with SaveTell ( f ) :
backtrackCoverages = parse_at_offsets ( f , start_tell , backtrackCoverageOffsets , parse_coverage_table )
inputCoverages = parse_at_offsets ( f , start_tell , inputCoverageOffsets , parse_coverage_table )
lookaheadCoverages = parse_at_offsets ( f , start_tell , lookaheadCoverageOffsets , parse_coverage_table )
return ChainContextPosSubtable_Format_3 ( posFormat , backtrackGlyphCount , backtrackCoverages , inputGlyphCount , inputCoverages , lookaheadGlyphCount , lookaheadCoverages , posCount , posLookupRecords )
case _ :
assert False , f " Unimplemented: posFormat: { posFormat } "
assert False , posFormat
case GPOSLookupType . ExtensionPos :
start_tell = f . tell ( )
posFormat = read_u16 ( f )
assert posFormat in [ 1 ]
match posFormat :
case 1 :
2024-05-04 11:34:37 +10:00
extensionLookupType = read_id ( f , GPOSLookupType )
2024-05-03 21:02:58 +10:00
assert extensionLookupType != GPOSLookupType . ExtensionPos , f " ExtensionPos subtable cannot reference another ExtensionPos subtable "
extensionOffset = read_u32 ( f )
with SaveTell ( f ) :
extension = parse_at_offset ( f , start_tell , extensionOffset , lambda f : parse_GPOS_lookup_subtable ( f , extensionLookupType ) )
return ExtensionPosSubtable_Format_1 ( posFormat , extensionLookupType , extension )
case _ :
assert False , f " Unimplemented: posFormat: { posFormat } "
assert False , posFormat
case _ :
assert False , f " Unimplemented: GPOSLookupType: { lookupType } "
assert False , lookupType
@dataclass
class GPOSTable ( Table , ABD ) :
majorVersion : int
minorVersion : int
scriptList : ScriptListTable
featureList : FeatureListTable
lookupList : LookupListTable [ GPOSLookupType , GPOSLookupSubtable ]
@dataclass
class GPOSTable_Ver_1_0 ( GPOSTable ) : pass
@dataclass
class GPOSTable_Ver_1_1 ( GPOSTable_Ver_1_0 ) :
featureVariations : Optional [ FeatureVariationsTable ]
def parse_GPOS_table ( f : BinaryIO ) - > GPOSTable :
start_tell = f . tell ( )
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 , 1 ]
scriptListOffset = read_u16 ( f )
featureListOffset = read_u16 ( f )
lookupListOffset = read_u16 ( f )
with SaveTell ( f ) :
scriptList = parse_at_offset ( f , start_tell , scriptListOffset , parse_script_list_table )
featureList = parse_at_offset ( f , start_tell , featureListOffset , parse_feature_list_table )
lookupList = parse_at_offset ( f , start_tell , lookupListOffset , lambda f : parse_lookup_list_table ( f , GPOSLookupType , parse_GPOS_lookup_subtable ) )
if minorVersion == 0 :
return GPOSTable_Ver_1_0 ( majorVersion , minorVersion , scriptList , featureList , lookupList )
featureVariationsOffset = read_u32 ( f )
with SaveTell ( f ) :
2024-09-15 16:10:41 +10:00
featureVariations = parse_at_offset ( f , start_tell , featureVariationsOffset , lambda f : parse_feature_variations_table ( f , featureList ) )
2024-05-03 21:02:58 +10:00
if minorVersion == 1 :
return GPOSTable_Ver_1_1 ( majorVersion , minorVersion , scriptList , featureList , lookupList , featureVariations )
assert False , f " Unimplemented: minorVersion: { minorVersion } "
class GSUBLookupType ( Enum ) :
SingleSubst = 1
MultipleSubst = 2
AlternateSubst = 3
LigatureSubst = 4
ContextSubst = 5
ChainContextSubst = 6
ExtensionSubst = 7
ReverseChainSingleSubst = 8
def __str__ ( self ) - > str : return self . _name_
@dataclass
class GSUBLookupSubtable ( LookupSubtable , ABD ) : pass
# 1
@dataclass
class SingleSubstSubtable ( GSUBLookupSubtable , ABD ) :
substFormat : int
@dataclass
class SingleSubstSubtable_Format_1 ( SingleSubstSubtable ) :
coverage : CoverageTable
deltaGlyphID : int
@dataclass
class SingleSubstSubtable_Format_2 ( SingleSubstSubtable ) :
coverage : CoverageTable
glyphCount : int
substituteGlyphIDs : List [ int ]
# 2
@dataclass
class MultipleSubstSubtable ( GSUBLookupSubtable , ABD ) :
substFormat : int
@dataclass
class SequenceTable ( Table ) :
glyphCount : int
substituteGlyphIDs : List [ int ]
def parse_sequence_table ( f : BinaryIO ) - > SequenceTable :
glyphCount = read_u16 ( f )
assert glyphCount > 0 , " Cannot use multiple substitution to delete an input glyph "
substituteGlyphIDs = [ read_u16 ( f ) for _ in range ( glyphCount ) ]
return SequenceTable ( glyphCount , substituteGlyphIDs )
@dataclass
class MultipleSubstSubtable_Format_1 ( MultipleSubstSubtable ) :
coverage : CoverageTable
sequenceCount : int
sequences : List [ SequenceTable ]
# 3
@dataclass
class AlternateSubstSubtable ( GSUBLookupSubtable , ABD ) :
substFormat : int
@dataclass
class AlternateSetTable ( Table ) :
glyphCount : int
alternateGlyphIDs : List [ int ]
def parse_alternate_set_table ( f : BinaryIO ) - > AlternateSetTable :
glyphCount = read_u16 ( f )
alternateGlyphIDs = [ read_u16 ( f ) for _ in range ( glyphCount ) ]
return AlternateSetTable ( glyphCount , alternateGlyphIDs )
@dataclass
class AlternateSubstSubtable_Format_1 ( AlternateSubstSubtable ) :
coverage : CoverageTable
alternateSetCount : int
alternateSets : List [ AlternateSetTable ]
# 4
@dataclass
class SubstLookupRecord :
glyphSequenceIndex : int
lookupListIndex : int
def parse_subst_lookup_record ( f : BinaryIO ) - > SubstLookupRecord :
glyphSequenceIndex = read_u16 ( f )
lookupListIndex = read_u16 ( f )
return SubstLookupRecord ( glyphSequenceIndex , lookupListIndex )
@dataclass
class LigatureSubstSubtable ( GSUBLookupSubtable , ABD ) :
substFormat : int
@dataclass
class LigatureTable ( Table ) :
ligatureGlyph : int
componentCount : int
componentGlyphIDs : List [ int ]
def parse_ligature_table ( f : BinaryIO ) - > LigatureTable :
ligatureGlyph = read_u16 ( f )
componentCount = read_u16 ( f )
componentGlyphIDs = [ read_u16 ( f ) for _ in range ( componentCount - 1 ) ] # starts from second component
return LigatureTable ( ligatureGlyph , componentCount , componentGlyphIDs )
2024-05-04 11:34:37 +10:00
LigatureSetTable = SetTable [ LigatureTable ]
def parse_ligature_set_table ( f : BinaryIO ) - > LigatureSetTable :
return parse_set_table ( f , parse_ligature_table )
2024-05-03 21:02:58 +10:00
@dataclass
class LigatureSubstSubtable_Format_1 ( LigatureSubstSubtable ) :
coverage : CoverageTable
ligatureSetCount : int
2024-05-04 11:34:37 +10:00
ligatureSets : List [ LigatureSetTable ]
2024-05-03 21:02:58 +10:00
# 5
@dataclass
class ContextSubstSubtable ( GSUBLookupSubtable , ABD ) :
substFormat : int
@dataclass
class SubRuleTable ( Table ) :
glyphCount : int
substitutionCount : int
inputSequence : List [ int ]
substLookupRecords : List [ SubstLookupRecord ]
def parse_sub_rule_table ( f : BinaryIO ) - > SubRuleTable :
glyphCount = read_u16 ( f )
substitutionCount = read_u16 ( f )
inputSequence = [ read_u16 ( f ) for _ in range ( glyphCount - 1 ) ]
substLookupRecords = [ parse_subst_lookup_record ( f ) for _ in range ( substitutionCount ) ]
return SubRuleTable ( glyphCount , substitutionCount , inputSequence , substLookupRecords )
2024-05-04 11:34:37 +10:00
SubRuleSetTable = SetTable [ SubRuleTable ]
def parse_sub_rule_set_table ( f : BinaryIO ) - > SubRuleSetTable :
return parse_set_table ( f , parse_sub_rule_table )
2024-05-03 21:02:58 +10:00
@dataclass
class ContextSubstSubtable_Format_1 ( ContextSubstSubtable ) :
coverage : CoverageTable
subRuleSetCount : int
2024-05-04 11:34:37 +10:00
subRuleSets : List [ SubRuleSetTable ]
2024-05-03 21:02:58 +10:00
@dataclass
class SubClassRuleTable ( Table ) :
glyphCount : int
substitutionCount : int
inputSequence : List [ int ]
substLookupRecords : List [ SubstLookupRecord ]
def parse_sub_class_rule_table ( f : BinaryIO ) - > SubClassRuleTable :
glyphCount = read_u16 ( f )
substitutionCount = read_u16 ( f )
inputSequence = [ read_u16 ( f ) for _ in range ( glyphCount - 1 ) ]
substLookupRecords = [ parse_subst_lookup_record ( f ) for _ in range ( substitutionCount ) ]
return SubClassRuleTable ( glyphCount , substitutionCount , inputSequence , substLookupRecords )
2024-05-04 11:34:37 +10:00
SubClassRuleSetTable = SetTable [ SubClassRuleTable ]
def parse_sub_class_rule_set_table ( f : BinaryIO ) - > SubClassRuleSetTable :
return parse_set_table ( f , parse_sub_class_rule_table )
2024-05-03 21:02:58 +10:00
@dataclass
class ContextSubstSubtable_Format_2 ( ContextSubstSubtable ) :
coverage : CoverageTable
classDef : ClassDefTable
subClassSetCount : int
2024-05-04 11:34:37 +10:00
subClassSets : List [ Optional [ SubClassRuleSetTable ] ]
2024-05-03 21:02:58 +10:00
@dataclass
class ContextSubstSubtable_Format_3 ( ContextSubstSubtable ) :
glyphCount : int
substitutionCount : int
coverages : List [ CoverageTable ]
substLookupRecords : List [ SubstLookupRecord ]
# 6
@dataclass
class ChainContextSubstSubtable ( GSUBLookupSubtable , ABD ) :
substFormat : int
@dataclass
class ChainSubRuleTable ( Table ) :
backtrackGlyphCount : int
backtrackSequence : List [ int ]
inputGlyphCount : int
inputSequence : List [ int ]
lookaheadGlyphCount : int
lookaheadGlyphSequence : List [ int ]
substitutionCount : int
substLookupRecords : List [ SubstLookupRecord ]
def parse_chain_sub_rule_table ( f : BinaryIO ) - > ChainSubRuleTable :
backtrackGlyphCount = read_u16 ( f )
backtrackSequence = [ read_u16 ( f ) for _ in range ( backtrackGlyphCount ) ]
inputGlyphCount = read_u16 ( f )
inputSequence = [ read_u16 ( f ) for _ in range ( inputGlyphCount - 1 ) ] # starts from second position, so one less glyph
lookaheadGlyphCount = read_u16 ( f )
lookAheadSequence = [ read_u16 ( f ) for _ in range ( lookaheadGlyphCount ) ]
substitutionCount = read_u16 ( f )
substLookupRecords = [ parse_subst_lookup_record ( f ) for _ in range ( substitutionCount ) ]
return ChainSubRuleTable ( backtrackGlyphCount , backtrackSequence , inputGlyphCount , inputSequence , lookaheadGlyphCount , lookAheadSequence , substitutionCount , substLookupRecords )
2024-05-04 11:34:37 +10:00
ChainSubRuleSetTable = SetTable [ ChainSubRuleTable ]
def parse_chain_sub_rule_set_table ( f : BinaryIO ) - > ChainSubRuleSetTable :
return parse_set_table ( f , parse_chain_sub_rule_table )
2024-05-03 21:02:58 +10:00
@dataclass
class ChainContextSubstSubtable_Format_1 ( ChainContextSubstSubtable ) :
coverage : CoverageTable
chainSubRuleSetCount : int
2024-05-04 11:34:37 +10:00
chainSubRuleSets : List [ ChainSubRuleSetTable ]
2024-05-03 21:02:58 +10:00
@dataclass
class ChainSubClassRuleTable ( Table ) :
backtrackGlyphCount : int
backtrackSequence : List [ int ]
inputGlyphCount : int
inputSequence : List [ int ]
lookaheadGlyphCount : int
lookAheadSequence : List [ int ]
substitutionCount : int
substLookupRecords : List [ SubstLookupRecord ]
def parse_chain_sub_class_rule_table ( f : BinaryIO ) - > ChainSubClassRuleTable :
backtrackGlyphCount = read_u16 ( f )
backtrackSequence = [ read_u16 ( f ) for _ in range ( backtrackGlyphCount ) ]
inputGlyphCount = read_u16 ( f )
inputSequence = [ read_u16 ( f ) for _ in range ( inputGlyphCount - 1 ) ] # starts from second position, so one less glyph
lookaheadGlyphCount = read_u16 ( f )
lookAheadSequence = [ read_u16 ( f ) for _ in range ( lookaheadGlyphCount ) ]
substitutionCount = read_u16 ( f )
substLookupRecords = [ parse_subst_lookup_record ( f ) for _ in range ( substitutionCount ) ]
return ChainSubClassRuleTable ( backtrackGlyphCount , backtrackSequence , inputGlyphCount , inputSequence , lookaheadGlyphCount , lookAheadSequence , substitutionCount , substLookupRecords )
2024-05-04 11:34:37 +10:00
ChainSubClassRuleSetTable = SetTable [ ChainSubClassRuleTable ]
def parse_chain_sub_class_rule_set_table ( f : BinaryIO ) - > ChainSubClassRuleSetTable :
return parse_set_table ( f , parse_chain_sub_class_rule_table )
2024-05-03 21:02:58 +10:00
@dataclass
class ChainContextSubstSubtable_Format_2 ( ChainContextSubstSubtable ) :
coverage : CoverageTable
backtrackClassDef : ClassDefTable
inputClassDef : ClassDefTable
lookaheadClassDef : ClassDefTable
chainSubClassSetCount : int
2024-05-04 11:34:37 +10:00
chainSubClassSets : List [ Optional [ ChainSubClassRuleSetTable ] ]
2024-05-03 21:02:58 +10:00
@dataclass
class ChainContextSubstSubtable_Format_3 ( ChainContextSubstSubtable ) :
backtrackGlyphCount : int
backtrackCoverages : List [ CoverageTable ]
inputGlyphCount : int
inputCoverages : List [ CoverageTable ]
lookaheadGlyphCount : int
lookaheadCoverages : List [ CoverageTable ]
substitutionCount : int
substLookupRecords : List [ SubstLookupRecord ]
# 7
@dataclass
class ExtensionSubstSubtable ( GSUBLookupSubtable , ABD ) :
substFormat : int
@dataclass
class ExtensionSubstSubtable_Format_1 ( ExtensionSubstSubtable ) :
extensionLookupType : GSUBLookupType
extension : GSUBLookupSubtable
# 8
@dataclass
class ReverseChainSingleSubstSubtable ( GSUBLookupSubtable , ABD ) :
substFormat : int
@dataclass
class ReverseChainSingleSubstSubtable_Format_1 ( ReverseChainSingleSubstSubtable ) :
coverage : CoverageTable
backtrackGlyphCount : int
backtrackCoverages : List [ CoverageTable ]
lookaheadGlyphCount : int
lookaheadCoverages : List [ CoverageTable ]
glyphCount : int
substituteGlyphIDs : List [ int ]
def parse_GSUB_lookup_subtable ( f : BinaryIO , lookupType : GSUBLookupType ) - > GSUBLookupSubtable :
match lookupType :
case GSUBLookupType . SingleSubst :
start_tell = f . tell ( )
substFormat = read_u16 ( f )
assert substFormat in [ 1 , 2 ]
match substFormat :
case 1 :
coverageOffset = read_u16 ( f )
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
deltaGlyphID = read_i16 ( f )
return SingleSubstSubtable_Format_1 ( substFormat , coverage , deltaGlyphID )
case 2 :
coverageOffset = read_u16 ( f )
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
glyphCount = read_u16 ( f )
substituteGlyphIDs = [ read_u16 ( f ) for _ in range ( glyphCount ) ]
return SingleSubstSubtable_Format_2 ( substFormat , coverage , glyphCount , substituteGlyphIDs )
case _ :
assert False , f " Unimplemented: substFormat: { substFormat } "
assert False , substFormat
case GSUBLookupType . MultipleSubst :
start_tell = f . tell ( )
substFormat = read_u16 ( f )
assert substFormat in [ 1 ]
match substFormat :
case 1 :
coverageOffset = read_u16 ( f )
sequenceCount = read_u16 ( f )
sequenceOffsets = [ read_u16 ( f ) for _ in range ( sequenceCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
sequences = parse_at_offsets ( f , start_tell , sequenceOffsets , parse_sequence_table )
return MultipleSubstSubtable_Format_1 ( substFormat , coverage , sequenceCount , sequences )
case _ :
assert False , f " Unimplemented: substFormat: { substFormat } "
assert False , substFormat
case GSUBLookupType . AlternateSubst :
start_tell = f . tell ( )
substFormat = read_u16 ( f )
assert substFormat in [ 1 ]
match substFormat :
case 1 :
coverageOffset = read_u16 ( f )
alternateSetCount = read_u16 ( f )
alternateSetOffsets = [ read_u16 ( f ) for _ in range ( alternateSetCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
alternateSets = parse_at_offsets ( f , start_tell , alternateSetOffsets , parse_alternate_set_table )
return AlternateSubstSubtable_Format_1 ( substFormat , coverage , alternateSetCount , alternateSets )
case _ :
assert False , f " Unimplemented: substFormat: { substFormat } "
assert False , substFormat
case GSUBLookupType . LigatureSubst :
start_tell = f . tell ( )
substFormat = read_u16 ( f )
assert substFormat in [ 1 ]
match substFormat :
case 1 :
coverageOffset = read_u16 ( f )
ligatureSetCount = read_u16 ( f )
ligatureSetOffsets = [ read_u16 ( f ) for _ in range ( ligatureSetCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
2024-09-15 16:10:41 +10:00
ligatureSets = parse_at_offsets ( f , start_tell , ligatureSetOffsets , parse_ligature_set_table )
2024-05-03 21:02:58 +10:00
return LigatureSubstSubtable_Format_1 ( substFormat , coverage , ligatureSetCount , ligatureSets )
case _ :
assert False , f " Unimplemented: substFormat: { substFormat } "
assert False , substFormat
case GSUBLookupType . ContextSubst :
start_tell = f . tell ( )
substFormat = read_u16 ( f )
assert substFormat in [ 1 , 2 , 3 ]
match substFormat :
case 1 :
coverageOffset = read_u16 ( f )
subRuleSetCount = read_u16 ( f )
subRuleSetOffsets = [ read_u16 ( f ) for _ in range ( subRuleSetCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
2024-05-04 11:34:37 +10:00
subRuleSets = parse_at_offsets ( f , start_tell , subRuleSetOffsets , parse_sub_rule_set_table )
2024-05-03 21:02:58 +10:00
return ContextSubstSubtable_Format_1 ( substFormat , coverage , subRuleSetCount , subRuleSets )
case 2 :
coverageOffset = read_u16 ( f )
classDefOffset = read_u16 ( f )
subClassSetCount = read_u16 ( f )
subClassSetOffsets = [ read_u16 ( f ) for _ in range ( subClassSetCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
classDef = parse_at_offset ( f , start_tell , classDefOffset , parse_class_def_table )
2024-05-04 11:34:37 +10:00
subClassSets = parse_at_optional_offsets ( f , start_tell , subClassSetOffsets , parse_sub_class_rule_set_table )
2024-05-03 21:02:58 +10:00
return ContextSubstSubtable_Format_2 ( substFormat , coverage , classDef , subClassSetCount , subClassSets )
case 3 :
glyphCount = read_u16 ( f )
substitutionCount = read_u16 ( f )
coverageOffsets = [ read_u16 ( f ) for _ in range ( glyphCount ) ]
substLookupRecords = [ parse_subst_lookup_record ( f ) for _ in range ( substitutionCount ) ]
with SaveTell ( f ) :
coverages = parse_at_offsets ( f , start_tell , coverageOffsets , parse_coverage_table )
return ContextSubstSubtable_Format_3 ( substFormat , glyphCount , substitutionCount , coverages , substLookupRecords )
case _ :
assert False , f " Unimplemented: substFormat: { substFormat } "
assert False , substFormat
case GSUBLookupType . ChainContextSubst :
start_tell = f . tell ( )
substFormat = read_u16 ( f )
assert substFormat in [ 1 , 2 , 3 ]
match substFormat :
case 1 :
coverageOffset = read_u16 ( f )
chainSubRuleSetCount = read_u16 ( f )
chainSubRuleSetOffsets = [ read_u16 ( f ) for _ in range ( chainSubRuleSetCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
2024-05-04 11:34:37 +10:00
chainSubRuleSets = parse_at_offsets ( f , start_tell , chainSubRuleSetOffsets , parse_chain_sub_rule_set_table )
2024-05-03 21:02:58 +10:00
return ChainContextSubstSubtable_Format_1 ( substFormat , coverage , chainSubRuleSetCount , chainSubRuleSets )
case 2 :
coverageOffset = read_u16 ( f )
backtrackClassDefOffset = read_u16 ( f )
inputClassDefOffset = read_u16 ( f )
lookaheadClassDefOffset = read_u16 ( f )
chainSubClassSetCount = read_u16 ( f )
chainSubClassSetOffsets = [ read_u16 ( f ) for _ in range ( chainSubClassSetCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
backtrackClassDef = parse_at_offset ( f , start_tell , backtrackClassDefOffset , parse_class_def_table )
inputClassDef = parse_at_offset ( f , start_tell , inputClassDefOffset , parse_class_def_table )
lookaheadClassDef = parse_at_offset ( f , start_tell , lookaheadClassDefOffset , parse_class_def_table )
2024-05-04 11:34:37 +10:00
chainSubClassSets = parse_at_optional_offsets ( f , start_tell , chainSubClassSetOffsets , parse_chain_sub_class_rule_set_table )
2024-05-03 21:02:58 +10:00
return ChainContextSubstSubtable_Format_2 ( substFormat , coverage , backtrackClassDef , inputClassDef , lookaheadClassDef , chainSubClassSetCount , chainSubClassSets )
case 3 :
backtrackGlyphCount = read_u16 ( f )
backtrackCoverageOffsets = [ read_u16 ( f ) for _ in range ( backtrackGlyphCount ) ]
inputGlyphCount = read_u16 ( f )
inputCoverageOffsets = [ read_u16 ( f ) for _ in range ( inputGlyphCount ) ]
lookaheadGlyphCount = read_u16 ( f )
lookaheadCoverageOffsets = [ read_u16 ( f ) for _ in range ( lookaheadGlyphCount ) ]
substitutionCount = read_u16 ( f )
substLookupRecords = [ parse_subst_lookup_record ( f ) for _ in range ( substitutionCount ) ]
with SaveTell ( f ) :
backtrackCoverages = parse_at_offsets ( f , start_tell , backtrackCoverageOffsets , parse_coverage_table )
inputCoverages = parse_at_offsets ( f , start_tell , inputCoverageOffsets , parse_coverage_table )
lookaheadCoverages = parse_at_offsets ( f , start_tell , lookaheadCoverageOffsets , parse_coverage_table )
return ChainContextSubstSubtable_Format_3 ( substFormat , backtrackGlyphCount , backtrackCoverages , inputGlyphCount , inputCoverages , lookaheadGlyphCount , lookaheadCoverages , substitutionCount , substLookupRecords )
case _ :
assert False , f " Unimplemented: substFormat: { substFormat } "
assert False , substFormat
case GSUBLookupType . ExtensionSubst :
start_tell = f . tell ( )
substFormat = read_u16 ( f )
assert substFormat in [ 1 ]
match substFormat :
case 1 :
2024-05-04 11:34:37 +10:00
extensionLookupType = read_id ( f , GSUBLookupType )
2024-05-03 21:02:58 +10:00
extensionOffset = read_u32 ( f )
with SaveTell ( f ) :
extension = parse_at_offset ( f , start_tell , extensionOffset , lambda f : parse_GSUB_lookup_subtable ( f , extensionLookupType ) )
return ExtensionSubstSubtable_Format_1 ( substFormat , extensionLookupType , extension )
case _ :
assert False , f " Unimplemented: substFormat: { substFormat } "
assert False , substFormat
case GSUBLookupType . ReverseChainSingleSubst :
start_tell = f . tell ( )
substFormat = read_u16 ( f )
assert substFormat in [ 1 ]
match substFormat :
case 1 :
coverageOffset = read_u16 ( f )
backtrackGlyphCount = read_u16 ( f )
backtrackCoverageOffsets = [ read_u16 ( f ) for _ in range ( backtrackGlyphCount ) ]
lookaheadGlyphCount = read_u16 ( f )
lookaheadCoverageOffsets = [ read_u16 ( f ) for _ in range ( lookaheadGlyphCount ) ]
glyphCount = read_u16 ( f )
substituteGlyphIDs = [ read_u16 ( f ) for _ in range ( glyphCount ) ]
with SaveTell ( f ) :
coverage = parse_at_offset ( f , start_tell , coverageOffset , parse_coverage_table )
backtrackCoverages = parse_at_offsets ( f , start_tell , backtrackCoverageOffsets , parse_coverage_table )
lookaheadCoverages = parse_at_offsets ( f , start_tell , lookaheadCoverageOffsets , parse_coverage_table )
return ReverseChainSingleSubstSubtable_Format_1 ( substFormat , coverage , backtrackGlyphCount , backtrackCoverages , lookaheadGlyphCount , lookaheadCoverages , glyphCount , substituteGlyphIDs )
case _ :
assert False , f " Unimplemented: substFormat: { substFormat } "
assert False , substFormat
case _ :
assert False , f " Unimplemented: GSUBLookupType: { lookupType } "
assert False , lookupType
@dataclass
2024-09-15 16:10:41 +10:00
class GSUBTable ( Table , ABD ) : # TODO: Maybe make a generic class for this, because this is the same as GPOSTable
2024-05-03 21:02:58 +10:00
majorVersion : int
minorVersion : int
# See: https://github.com/MicrosoftDocs/typography-issues/issues/79
scriptList : ScriptListTable
featureList : Optional [ FeatureListTable ]
lookupList : Optional [ LookupListTable [ GSUBLookupType , GSUBLookupSubtable ] ]
@dataclass
class GSUBTable_Ver_1_0 ( GSUBTable ) : pass
@dataclass
class GSUBTable_Ver_1_1 ( GSUBTable_Ver_1_0 ) :
featureVariations : Optional [ FeatureVariationsTable ]
def parse_GSUB_table ( f : BinaryIO ) - > GSUBTable :
start_tell = f . tell ( )
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 , 1 ]
scriptListOffset = read_u16 ( f )
featureListOffset = read_u16 ( f )
lookupListOffset = read_u16 ( f )
with SaveTell ( f ) :
scriptList = parse_at_offset ( f , start_tell , scriptListOffset , parse_script_list_table )
featureList = parse_at_optional_offset ( f , start_tell , featureListOffset , parse_feature_list_table )
lookupList = parse_at_optional_offset ( f , start_tell , lookupListOffset , lambda f : parse_lookup_list_table ( f , GSUBLookupType , parse_GSUB_lookup_subtable ) )
if minorVersion == 0 :
return GSUBTable_Ver_1_0 ( majorVersion , minorVersion , scriptList , featureList , lookupList )
2024-09-15 16:10:41 +10:00
2024-05-03 21:02:58 +10:00
featureVariationsOffset = read_u16 ( f )
with SaveTell ( f ) :
assert featureList
featureVariations = parse_at_optional_offset ( f , start_tell , featureVariationsOffset , lambda f : parse_feature_variations_table ( f , featureList ) )
if minorVersion == 1 :
return GSUBTable_Ver_1_1 ( majorVersion , minorVersion , scriptList , featureList , lookupList , featureVariations )
assert False , f " Unimplemented: minorVersion: { minorVersion } "
@dataclass
class JSTFTable ( Table ) :
pass
def parse_JSTF_table ( f : BinaryIO ) - > JSTFTable :
assert False
@dataclass
class MATHTable ( Table ) :
pass
def parse_MATH_table ( f : BinaryIO ) - > MATHTable :
assert False
@dataclass
class AdvancedFeatures :
baseline_data : Optional [ BASETable ]
glyph_definition_data : Optional [ GDEFTable ]
glyph_positioning_data : Optional [ GPOSTable ]
glyph_substitution_data : Optional [ GSUBTable ]
justification_data : Optional [ JSTFTable ]
math_layout_data : Optional [ MATHTable ]
@dataclass
class CvtTable ( Table ) :
fwords : List [ int ]
def parse_cvt_table ( f : BinaryIO , length : int ) - > CvtTable :
assert length % 2 == 0 , " Must have an integer number of FWORDS in length "
fwords = [ read_i16 ( f ) for _ in range ( length / / 2 ) ]
return CvtTable ( fwords )
@dataclass
class FpgmTable ( Table ) :
instructions : List [ int ]
def parse_fpgm_table ( f : BinaryIO , length : int ) - > FpgmTable :
instructions = [ read_u8 ( f ) for _ in range ( length ) ]
return FpgmTable ( instructions )
@dataclass
class Glyph ( ABD ) :
numberOfContours : int
xMin : int
yMin : int
xMax : int
yMax : int
@dataclass
class SimpleGlyphFlag :
byte : int
def on_curve_point ( self ) - > bool : return ( self . byte & 0x01 ) != 0
def x_short ( self ) - > bool : return ( self . byte & 0x02 ) != 0
def y_short ( self ) - > bool : return ( self . byte & 0x04 ) != 0
def repeat_flag ( self ) - > bool : return ( self . byte & 0x08 ) != 0
def x_is_same_or_positive_short ( self ) - > bool : return ( self . byte & 0x10 ) != 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
2024-09-15 16:10:41 +10:00
def __repr__ ( self ) - > str :
return repr_hex ( self . byte , 2 )
def __post_init__ ( self ) :
assert self . byte & 0x80 == 0 , self
2024-05-03 21:02:58 +10:00
def parse_simple_glyph_flag ( f : BinaryIO ) - > SimpleGlyphFlag :
flags = read_u8 ( f )
2024-09-15 16:10:41 +10:00
2024-05-03 21:02:58 +10:00
return SimpleGlyphFlag ( flags )
@dataclass
class SimpleGlyph ( Glyph ) :
endPtsOfContours : List [ int ]
instructionLength : int
instructions : List [ int ]
flags : List [ SimpleGlyphFlag ]
coordinates : List [ Tuple [ int , int ] ]
@dataclass
class CompoundGlyphFlag :
2024-09-15 16:10:41 +10:00
bytes : int
2024-05-03 21:02:58 +10:00
2024-09-15 16:10:41 +10:00
def arg_1_and_2_are_words ( self ) - > bool : return ( self . bytes & 0x0001 ) != 0
def args_are_xy_values ( self ) - > bool : return ( self . bytes & 0x0002 ) != 0
def round_xy_to_grid ( self ) - > bool : return ( self . bytes & 0x0004 ) != 0
def we_have_a_scale ( self ) - > bool : return ( self . bytes & 0x0008 ) != 0
def more_components ( self ) - > bool : return ( self . bytes & 0x0020 ) != 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 . bytes & 0x0080 ) != 0
def we_have_instructions ( self ) - > bool : return ( self . bytes & 0x0100 ) != 0
def use_my_metrics ( self ) - > bool : return ( self . bytes & 0x0200 ) != 0
def overlap_compound ( self ) - > bool : return ( self . bytes & 0x0400 ) != 0
def scaled_component_offset ( self ) - > bool : return ( self . bytes & 0x0800 ) != 0
def unscaled_component_offset ( self ) - > bool : return ( self . bytes & 0x1000 ) != 0
2024-05-03 21:02:58 +10:00
def __repr__ ( self ) - > str :
2024-09-15 16:10:41 +10:00
return repr_hex ( self . bytes , 4 )
def __post_init__ ( self ) :
assert self . bytes & 0xE010 == 0 , " Reserved "
2024-05-03 21:02:58 +10:00
def parse_compound_glyph_flag ( f : BinaryIO ) - > CompoundGlyphFlag :
flags = read_u16 ( f )
2024-09-15 16:10:41 +10:00
2024-05-03 21:02:58 +10:00
return CompoundGlyphFlag ( flags )
@dataclass
class Scaling ( ABD ) : pass
@dataclass
class ScaleScaling ( Scaling ) :
scale : float
@dataclass
class XYScaleScaling ( Scaling ) :
xscale : float
yscale : float
@dataclass
class TwoByTwoScaling ( Scaling ) :
xscale : float
scale01 : float
scale10 : float
yscale : float
@dataclass
class Component :
flag : CompoundGlyphFlag
glyphIndex : int
argument1 : int
argument2 : int
scaling : Optional [ Scaling ]
def parse_component ( f : BinaryIO ) - > Component :
flag = parse_compound_glyph_flag ( f )
glyphIndex = read_u16 ( f )
2024-09-15 16:23:49 +10:00
assert flag . args_are_xy_values ( ) , " TODO: Handle point indexes "
2024-05-03 21:02:58 +10:00
if flag . arg_1_and_2_are_words ( ) :
argument1 , argument2 = read_i16 ( f ) , read_i16 ( f )
else :
2024-09-15 16:23:49 +10:00
argument1 , argument2 = read_i8 ( f ) , read_i8 ( f )
2024-05-03 21:02:58 +10:00
scaling : Optional [ Scaling ] = None
if flag . we_have_a_scale ( ) :
scale = read_F2DOT14 ( f )
scaling = ScaleScaling ( scale )
elif flag . we_have_an_x_and_y_scale ( ) :
xscale = read_F2DOT14 ( f )
yscale = read_F2DOT14 ( f )
scaling = XYScaleScaling ( xscale , yscale )
elif flag . we_have_a_two_by_two ( ) :
xscale = read_F2DOT14 ( f )
scale01 = read_F2DOT14 ( f )
scale10 = read_F2DOT14 ( f )
yscale = read_F2DOT14 ( f )
scaling = TwoByTwoScaling ( xscale , scale01 , scale10 , yscale )
return Component ( flag , glyphIndex , argument1 , argument2 , scaling )
@dataclass
class CompoundGlyph ( Glyph ) :
components : List [ Component ]
@dataclass
class CompoundGlyphWithInstr ( CompoundGlyph ) :
numInstr : int
instructions : List [ int ]
def parse_simple_glyph_flags ( f : BinaryIO , total_points : int ) - > Tuple [ List [ SimpleGlyphFlag ] , Tuple [ int , int ] ] :
"""
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
x_len = 0
y_len = 0
points_left = total_points
flags : List [ SimpleGlyphFlag ] = [ ]
while points_left > 0 :
flag = parse_simple_glyph_flag ( f )
repeats = read_u8 ( f ) + 1 if flag . repeat_flag ( ) else 1
for _ in range ( repeats ) : flags . append ( flag )
if flag . x_short ( ) : x_len + = repeats # 1 byte long
elif not flag . x_is_same_or_positive_short ( ) : x_len + = repeats * 2 # 2 bytes long
if flag . y_short ( ) : y_len + = repeats # 1 byte long
elif not flag . y_is_same_or_positive_short ( ) : y_len + = repeats * 2 # 2 bytes long
points_left - = repeats
assert points_left == 0
assert len ( flags ) == total_points
return flags , ( x_len , y_len )
def parse_glyph ( f : BinaryIO , length : int ) - > Glyph :
start_tell = f . tell ( )
numberOfContours = read_i16 ( f )
xMin , yMin , xMax , yMax = [ read_i16 ( f ) for _ in range ( 4 ) ]
if numberOfContours > = 0 : # simple glyph
endPtsOfContours = [ read_u16 ( f ) for _ in range ( numberOfContours ) ]
instructionLength = read_u16 ( f )
instructions = [ read_u8 ( f ) for _ in range ( instructionLength ) ]
total_points = endPtsOfContours [ - 1 ] + 1
flags , ( total_x_len , total_y_len ) = parse_simple_glyph_flags ( f , total_points )
xCoordinates = BytesIO ( f . read ( total_x_len ) )
yCoordinates = BytesIO ( f . read ( total_y_len ) )
coordinates : List [ Tuple [ int , int ] ] = [ ( 0 , 0 ) ]
for flag in flags :
match flag . x_short ( ) , flag . x_is_same_or_positive_short ( ) :
case True , sign : xDelta = ( 2 * sign - 1 ) * read_u8 ( xCoordinates )
case False , True : xDelta = 0
case False , False : xDelta = read_i16 ( xCoordinates )
match flag . y_short ( ) , flag . y_is_same_or_positive_short ( ) :
case True , sign : yDelta = ( 2 * sign - 1 ) * read_u8 ( yCoordinates )
case False , True : yDelta = 0
case False , False : yDelta = read_i16 ( yCoordinates )
coordinates . append ( ( coordinates [ - 1 ] [ 0 ] + xDelta , coordinates [ - 1 ] [ 1 ] + yDelta ) )
2024-09-15 16:10:41 +10:00
# TODO: Do I need to read the padding bytes?
2024-05-03 21:02:58 +10:00
assert length - 4 < f . tell ( ) - start_tell < = length # there might be padding bytes
2024-09-15 16:10:41 +10:00
assert is_at_end ( xCoordinates ) and is_at_end ( yCoordinates ) , ( len_to_end ( xCoordinates ) , len_to_end ( yCoordinates ) )
2024-05-03 21:02:58 +10:00
return SimpleGlyph ( numberOfContours , xMin , yMin , xMax , yMax , endPtsOfContours , instructionLength , instructions , flags , coordinates [ 1 : ] )
else :
components = [ parse_component ( f ) ]
while components [ - 1 ] . flag . more_components ( ) :
components . append ( parse_component ( f ) )
if components [ - 1 ] . flag . we_have_instructions ( ) :
numInstr = read_u16 ( f )
instructions = [ read_u8 ( f ) for _ in range ( numInstr ) ]
assert length - 4 < f . tell ( ) - start_tell < = length # there might be padding bytes
return CompoundGlyphWithInstr ( numberOfContours , xMin , yMin , xMax , yMax , components , numInstr , instructions )
assert length - 4 < f . tell ( ) - start_tell < = length # there might be padding bytes
return CompoundGlyph ( numberOfContours , xMin , yMin , xMax , yMax , components )
assert False , numberOfContours
@dataclass
class GlyfTable ( Table ) :
glyphs : List [ Optional [ Glyph ] ] # None means no outline
def parse_glyf_table ( f : BinaryIO , offsets : List [ int ] ) - > GlyfTable :
start_tell = f . tell ( )
glyphs = parse_at_offsets_using_length ( f , start_tell , offsets , lambda f , _ , length : parse_glyph ( f , length ) , zero_is_null = False )
return GlyfTable ( glyphs )
@dataclass
class LocaTable ( Table ) :
offsets : List [ int ]
def parse_loca_table ( f : BinaryIO , indexToLocFormat : int , numGlyphs : int ) - > LocaTable :
match indexToLocFormat :
case 0 : offsets = [ read_u16 ( f ) * 2 for _ in range ( numGlyphs + 1 ) ]
case 1 : offsets = [ read_u32 ( f ) for _ in range ( numGlyphs + 1 ) ]
case _ :
assert False , f " Unimplemented: indexToLocFormat: { indexToLocFormat } "
return LocaTable ( offsets )
@dataclass
class PrepTable ( Table ) :
instructions : List [ int ]
def parse_prep_table ( f : BinaryIO , length : int ) - > PrepTable :
instructions = [ read_u8 ( f ) for _ in range ( length ) ]
return PrepTable ( instructions )
@dataclass
class GaspRange :
rangeMaxPPEM : int
rangeGaspBehavior : int
2024-09-15 16:10:41 +10:00
def __post_init__ ( self ) :
assert self . rangeGaspBehavior & 0xFFF0 == 0 , " Reserved "
def parse_gasp_range ( f : BinaryIO , version : int ) - > GaspRange :
2024-05-03 21:02:58 +10:00
rangeMaxPPEM = read_u16 ( f )
rangeGaspBehavior = read_u16 ( f )
if version == 0 : assert rangeGaspBehavior & 0x000C == 0 , " Only supported in version 1 "
return GaspRange ( rangeMaxPPEM , rangeGaspBehavior )
@dataclass
class GaspTable ( Table ) :
version : int
numRanges : int
gaspRanges : List [ GaspRange ]
def parse_gasp_table ( f : BinaryIO ) - > GaspTable :
version = read_u16 ( f )
assert version in [ 0 , 1 ]
numRanges = read_u16 ( f )
gaspRanges = [ parse_gasp_range ( f , version ) for _ in range ( numRanges ) ]
return GaspTable ( version , numRanges , gaspRanges )
@dataclass
class TrueTypeOutlines :
control_value_table : Optional [ CvtTable ]
font_program : Optional [ FpgmTable ]
glyph_data : GlyfTable
2024-09-15 16:10:41 +10:00
index_to_location : LocaTable # Parsing only
2024-05-03 21:02:58 +10:00
CV_program : Optional [ PrepTable ]
grid_fitting_and_scan_conversion : Optional [ GaspTable ]
@dataclass
class CompactFontOutlines :
pass
@dataclass
class TupleRecord :
coordinates : List [ float ]
def parse_tuple_record ( f : BinaryIO , axisCount : int ) - > TupleRecord :
coordinates = [ read_F2DOT14 ( f ) for _ in range ( axisCount ) ]
return TupleRecord ( coordinates )
def parse_tuple_record_fixed ( f : BinaryIO , axisCount : int ) - > TupleRecord :
coordinates = [ read_fixed ( f ) for _ in range ( axisCount ) ]
return TupleRecord ( coordinates )
@dataclass
class TupleIndex :
bytes : int
def embedded_peak_tuple ( self ) - > bool : return ( self . bytes & 0x8000 ) != 0
def intermediate_region ( self ) - > bool : return ( self . bytes & 0x4000 ) != 0
def private_point_numbers ( self ) - > bool : return ( self . bytes & 0x2000 ) != 0
def tuple_index ( self ) - > int : return self . bytes & 0x0fff
2024-09-15 16:10:41 +10:00
def __post_init__ ( self ) :
assert self . bytes & 0x1000 == 0 , " Reserved "
2024-05-03 21:02:58 +10:00
def parse_tuple_index ( f : BinaryIO ) - > TupleIndex :
tupleIndex = read_u16 ( f )
return TupleIndex ( tupleIndex )
@dataclass
class TupleVariationHeader :
variationDataSize : int
tupleIndex : TupleIndex
peakTuple : Optional [ TupleRecord ]
intermediateStartTuple : Optional [ TupleRecord ]
intermediateEndTuple : Optional [ TupleRecord ]
def parse_tuple_variation_header ( f : BinaryIO , axisCount : int , must_have_peak : bool = False ) - > TupleVariationHeader :
variationDataSize = read_u16 ( f )
tupleIndex = parse_tuple_index ( f )
peakTuple = parse_tuple_record ( f , axisCount ) if tupleIndex . embedded_peak_tuple ( ) else None
if must_have_peak : assert peakTuple , f " Must have a peakTuple "
intermediateStartTuple = parse_tuple_record ( f , axisCount ) if tupleIndex . intermediate_region ( ) else None
intermediateEndTuple = parse_tuple_record ( f , axisCount ) if tupleIndex . intermediate_region ( ) else None
return TupleVariationHeader ( variationDataSize , tupleIndex , peakTuple , intermediateStartTuple , intermediateEndTuple )
@dataclass
class PackedPointNumbers :
count : Optional [ int ] # None means that all numbers are included. I.e., All points numbers or all CVT value indices
point_numbers : List [ int ]
def parse_packed_point_numbers ( f : BinaryIO ) - > PackedPointNumbers :
POINTS_ARE_WORDS = 0x80
POINT_RUN_COUNT_MASK = 0x7f
first_byte = read_u8 ( f )
if first_byte == 0 :
return PackedPointNumbers ( None , [ ] )
elif first_byte & 0x80 != 0 :
f . seek ( - 1 , 1 )
count = read_u16 ( f ) & 0x7fff # high bit masked out
else :
count = first_byte
curr = 0
point_numbers : List [ int ] = [ ]
while len ( point_numbers ) < count :
control_byte = read_u8 ( f )
run_count = ( control_byte & POINT_RUN_COUNT_MASK ) + 1
for _ in range ( run_count ) :
number_diff = read_u16 ( f ) if ( control_byte & POINTS_ARE_WORDS ) != 0 else read_u8 ( f )
curr + = number_diff
point_numbers . append ( curr )
assert len ( point_numbers ) == count
return PackedPointNumbers ( count , point_numbers )
def parse_packed_deltas ( f : BinaryIO , num_deltas : int ) - > List [ int ] :
DELTAS_ARE_ZERO = 0x80
DELTAS_ARE_WORDS = 0x40
DELTA_RUN_COUNT_MASK = 0x3f
def parse_run ( ) - > List [ int ] :
control_byte = read_u8 ( f )
count = ( control_byte & DELTA_RUN_COUNT_MASK ) + 1
if ( control_byte & DELTAS_ARE_ZERO ) != 0 :
return [ 0 ] * count
elif ( control_byte & DELTAS_ARE_WORDS ) != 0 :
return [ read_i16 ( f ) for _ in range ( count ) ]
else :
return [ read_i8 ( f ) for _ in range ( count ) ]
deltas : List [ int ] = [ ]
while len ( deltas ) < num_deltas : deltas + = parse_run ( )
assert len ( deltas ) == num_deltas
return deltas
@dataclass
class GvarPerTupleVariationData :
# TODO: Maybe instead of using PackedPointNumbers, use just a List[int] instead?
private_point_numbers : Optional [ PackedPointNumbers ]
x_coordinate_deltas : List [ int ]
y_coordinate_deltas : List [ int ]
def parse_gvar_per_tuple_variation_data ( f : BinaryIO , tupleVariationHeader : TupleVariationHeader , shared_point_numbers : Optional [ PackedPointNumbers ] , numPoints : int ) - > GvarPerTupleVariationData :
start_tell = f . tell ( )
private_point_numbers = parse_packed_point_numbers ( f ) if tupleVariationHeader . tupleIndex . private_point_numbers ( ) else None
point_numbers = private_point_numbers if private_point_numbers else shared_point_numbers
numPoints = point_numbers . count if point_numbers and point_numbers . count is not None else numPoints
deltas = parse_packed_deltas ( f , numPoints * 2 ) # because there are two deltas for each point
assert len ( deltas ) % 2 == 0
deltas_count = len ( deltas ) / / 2
x_coordinate_deltas = deltas [ : deltas_count ]
y_coordinate_deltas = deltas [ deltas_count : ]
assert f . tell ( ) - start_tell == tupleVariationHeader . variationDataSize , ( f . tell ( ) - start_tell , tupleVariationHeader )
return GvarPerTupleVariationData ( private_point_numbers , x_coordinate_deltas , y_coordinate_deltas )
@dataclass
class CvarPerTupleVariationData :
private_point_numbers : Optional [ PackedPointNumbers ]
CVT_value_deltas : List [ int ]
def parse_cvar_per_tuple_variation_data ( f : BinaryIO , tupleVariationHeader : TupleVariationHeader , shared_point_numbers : Optional [ PackedPointNumbers ] , numValues : int ) - > CvarPerTupleVariationData :
start_tell = f . tell ( )
private_point_numbers = parse_packed_point_numbers ( f ) if tupleVariationHeader . tupleIndex . private_point_numbers ( ) else None
point_numbers = private_point_numbers if private_point_numbers else shared_point_numbers
numValues = point_numbers . count if point_numbers and point_numbers . count is not None else numValues
CVT_value_deltas = parse_packed_deltas ( f , numValues )
assert f . tell ( ) - start_tell == tupleVariationHeader . variationDataSize , ( f . tell ( ) - start_tell , tupleVariationHeader )
return CvarPerTupleVariationData ( private_point_numbers , CVT_value_deltas )
2024-09-15 16:10:41 +10:00
def parse_tuple_variation_count ( f : BinaryIO ) - > Tuple [ bool , int ] : # TODO: Maybe do this like the other flags?
2024-05-03 21:02:58 +10:00
SHARED_POINT_NUMBERS = 0x8000
COUNT_MASK = 0x0fff
tupleVariationCount = read_u16 ( f )
assert tupleVariationCount & 0x7000 == 0 , " Reserved "
return ( tupleVariationCount & SHARED_POINT_NUMBERS ) != 0 , tupleVariationCount & COUNT_MASK
PerTupleVariationData = TypeVar ( ' PerTupleVariationData ' , GvarPerTupleVariationData , CvarPerTupleVariationData )
@dataclass
class SerialisedData ( Generic [ PerTupleVariationData ] ) :
shared_point_numbers : Optional [ PackedPointNumbers ]
per_tuple_variation_data : List [ PerTupleVariationData ]
def parse_serialised_data ( f : BinaryIO , has_shared_point_numbers : bool , per_tuple_variation_data_parser : Callable [ [ BinaryIO , TupleVariationHeader , Optional [ PackedPointNumbers ] , int ] , PerTupleVariationData ] , tupleVariationHeaders : List [ TupleVariationHeader ] , numPoints : int ) - > SerialisedData [ PerTupleVariationData ] :
shared_point_numbers = parse_packed_point_numbers ( f ) if has_shared_point_numbers else None
per_tuple_variation_data : List [ PerTupleVariationData ] = [ ]
for tupleVariationHeader in tupleVariationHeaders :
curr_tell = f . tell ( )
per_tuple_variation_data . append ( per_tuple_variation_data_parser ( f , tupleVariationHeader , shared_point_numbers , numPoints ) )
assert f . tell ( ) - curr_tell == tupleVariationHeader . variationDataSize , ( f . tell ( ) - curr_tell , tupleVariationHeader . variationDataSize , tupleVariationHeader . tupleIndex . private_point_numbers ( ) )
return SerialisedData ( shared_point_numbers , per_tuple_variation_data )
@dataclass
class ItemVariationStoreTable ( Table ) :
format : int
@dataclass
class RegionAxisCoordinates :
startCoord : float
peakCoord : float
endCoord : float
def parse_region_axis_coordinates ( f : BinaryIO ) - > RegionAxisCoordinates :
startCoord = read_F2DOT14 ( f )
peakCoord = read_F2DOT14 ( f )
endCoord = read_F2DOT14 ( f )
return RegionAxisCoordinates ( startCoord , peakCoord , endCoord )
@dataclass
class VariationRegion :
regionAxes : List [ RegionAxisCoordinates ]
def parse_variation_region ( f : BinaryIO , axisCount : int ) - > VariationRegion :
regionAxes = [ parse_region_axis_coordinates ( f ) for _ in range ( axisCount ) ]
return VariationRegion ( regionAxes )
@dataclass
class VariationRegionList :
axisCount : int
regionCount : int
variationRegions : List [ VariationRegion ]
def parse_variation_region_list ( f : BinaryIO ) - > VariationRegionList :
axisCount = read_u16 ( f )
regionCount = read_u16 ( f )
variationRegions = [ parse_variation_region ( f , axisCount ) for _ in range ( regionCount ) ]
return VariationRegionList ( axisCount , regionCount , variationRegions )
@dataclass
class DeltaSetRecord :
deltaData : List [ int ]
def parse_delta_set_record ( f : BinaryIO , shortDeltaCount : int , regionIndexCount : int ) - > DeltaSetRecord :
deltaData = [ read_i16 ( f ) for _ in range ( shortDeltaCount ) ] + [ read_i8 ( f ) for _ in range ( regionIndexCount ) ]
return DeltaSetRecord ( deltaData )
@dataclass
class ItemVariationDataSubtable ( Table ) :
itemCount : int
shortDeltaCount : int
regionIndexCount : int
regionIndexes : List [ int ]
deltaSets : List [ DeltaSetRecord ]
def parse_item_variation_data_subtable ( f : BinaryIO ) - > ItemVariationDataSubtable :
itemCount = read_u16 ( f )
shortDeltaCount = read_u16 ( f )
regionIndexCount = read_u16 ( f )
regionIndexes = [ read_u16 ( f ) for _ in range ( regionIndexCount ) ] # ISO is wrong about the number of regionIndexes. They say it's `regionCount`, when it's actualyl `regionIndexCount`
deltaSets = [ parse_delta_set_record ( f , shortDeltaCount , regionIndexCount ) for _ in range ( itemCount ) ]
return ItemVariationDataSubtable ( itemCount , shortDeltaCount , regionIndexCount , regionIndexes , deltaSets )
@dataclass
class ItemVariationStoreTable_Format_1 ( ItemVariationStoreTable ) :
variationRegionList : VariationRegionList
itemVariationDataCount : int
itemVariationData : List [ ItemVariationDataSubtable ]
def parse_item_variation_store_table ( f : BinaryIO ) - > ItemVariationStoreTable :
start_tell = f . tell ( )
format = read_u16 ( f )
assert format in [ 1 ]
match format :
case 1 :
variationRegionListOffset = read_u32 ( f )
itemVariationDataCount = read_u16 ( f )
itemVariationDataOffsets = [ read_u32 ( f ) for _ in range ( itemVariationDataCount ) ]
with SaveTell ( f ) :
variationRegionList = parse_at_offset ( f , start_tell , variationRegionListOffset , parse_variation_region_list )
itemVariationData = parse_at_offsets ( f , start_tell , itemVariationDataOffsets , parse_item_variation_data_subtable )
return ItemVariationStoreTable_Format_1 ( format , variationRegionList , itemVariationDataCount , itemVariationData )
case _ :
assert False , f " Unimplemented: format: { format } "
class AxisTag ( ABE ) : pass
class RegisteredAxisTag ( AxisTag , Enum ) :
Italic = ' ital '
OpticalSize = ' opsz '
Slant = ' slnt '
Width = ' wdth '
Weight = ' wght '
is_letter : Callable [ [ str ] , bool ] = lambda s : 0x41 < = ord ( s ) < = 0x5a or 0x61 < = ord ( s ) < = 0x7a
is_digit : Callable [ [ str ] , bool ] = lambda s : 0x30 < = ord ( s ) < = 0x39
is_space : Callable [ [ str ] , bool ] = lambda s : ord ( s ) == 0x20
is_upper : Callable [ [ str ] , bool ] = lambda s : 0x41 < = ord ( s ) < = 0x5a
def is_valid_possible_registed_axis_tag ( tag : str ) - > bool :
return len ( tag ) == 4 and is_letter ( tag [ 0 ] ) and all ( map ( lambda s : is_letter ( s ) or is_digit ( s ) or is_space ( s ) , tag [ 1 : ] ) ) and len ( tag . rstrip ( ' ' ) ) == len ( tag ) - tag . count ( ' ' ) and not is_valid_possible_foundry_defined_axis_tag ( tag )
@dataclass
class FoundryDefinedAxisTag ( AxisTag ) :
tag : str
def is_valid_possible_foundry_defined_axis_tag ( tag : str ) - > bool :
return len ( tag ) == 4 and is_upper ( tag [ 0 ] ) and all ( map ( lambda s : is_upper ( s ) or is_digit ( s ) , tag [ 1 : ] ) )
def parse_axis_tag ( f : BinaryIO ) - > AxisTag :
2024-05-04 11:34:37 +10:00
return read_tag_with_conditions ( f , ( is_valid_possible_registed_axis_tag , RegisteredAxisTag ) , ( is_valid_possible_foundry_defined_axis_tag , FoundryDefinedAxisTag ) , umbrellaTagCls = AxisTag )
2024-05-03 21:02:58 +10:00
@dataclass
class AxisValueMapRecord :
fromCoordinate : float
toCoordinate : float
def parse_axis_value_map_record ( f : BinaryIO ) - > AxisValueMapRecord :
fromCoordinate = read_F2DOT14 ( f )
toCoordinate = read_F2DOT14 ( f )
return AxisValueMapRecord ( fromCoordinate , toCoordinate )
@dataclass
class SegmentMapsRecord :
positionMapCount : int
axisValueMaps : List [ AxisValueMapRecord ]
def parse_segment_maps_record ( f : BinaryIO ) - > SegmentMapsRecord :
positionMapCount = read_u16 ( f )
axisValueMaps = [ parse_axis_value_map_record ( f ) for _ in range ( positionMapCount ) ]
return SegmentMapsRecord ( positionMapCount , axisValueMaps )
@dataclass
class AvarTable ( Table , ABD ) :
majorVersion : int
minorVersion : int
axisCount : int
axisSegmentMaps : List [ SegmentMapsRecord ]
@dataclass
class AvarTable_Ver_1_0 ( AvarTable ) : pass
def parse_avar_table ( f : BinaryIO ) - > AvarTable :
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 ]
assert read_u16 ( f ) == 0 , " Permanently reserved "
axisCount = read_u16 ( f )
axisSegmentMaps = [ parse_segment_maps_record ( f ) for _ in range ( axisCount ) ]
if minorVersion == 0 :
return AvarTable_Ver_1_0 ( majorVersion , minorVersion , axisCount , axisSegmentMaps )
assert False , f " Unimplemented: minorVersion: { minorVersion } "
@dataclass
class CvarTable ( Table , ABD ) :
majorVersion : int
minorVersion : int
tupleVariationCount : int
data : SerialisedData [ CvarPerTupleVariationData ]
tupleVariationHeaders : List [ TupleVariationHeader ]
@dataclass
class CvarTable_Ver_1_0 ( CvarTable ) : pass
def parse_cvar_table ( f : BinaryIO , axisCount : int , control_value_table : CvtTable ) - > CvarTable :
start_tell = f . tell ( )
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 ]
has_shared_point_numbers , tupleVariationCount = parse_tuple_variation_count ( f )
dataOffset = read_u16 ( f )
tupleVariationHeaders = [ parse_tuple_variation_header ( f , axisCount , must_have_peak = True ) for _ in range ( tupleVariationCount ) ]
with SaveTell ( f ) :
data = parse_at_offset ( f , start_tell , dataOffset , lambda f : parse_serialised_data ( f , has_shared_point_numbers , parse_cvar_per_tuple_variation_data , tupleVariationHeaders , len ( control_value_table . fwords ) ) )
if minorVersion == 0 :
return CvarTable_Ver_1_0 ( majorVersion , minorVersion , tupleVariationCount , data , tupleVariationHeaders )
assert False , f " Unimplemented: minorVersion: { minorVersion } "
@dataclass
class VariationAxisRecord :
axisTag : AxisTag
minValue : float
defaultValue : float
maxValue : float
flags : int
axisNameID : int
2024-09-15 16:10:41 +10:00
def __post_init__ ( self ) :
assert self . flags & 0xfffe == 0 , " Reserved "
2024-05-03 21:02:58 +10:00
def parse_variation_axis_record ( f : BinaryIO , axisSize : int ) - > VariationAxisRecord :
start_tell = f . tell ( )
axisTag = parse_axis_tag ( f )
minValue = read_fixed ( f )
defaultValue = read_fixed ( f )
maxValue = read_fixed ( f )
flags = read_u16 ( f )
axisNameID = read_u16 ( f )
assert f . tell ( ) - start_tell == axisSize , ( f . tell ( ) - start_tell , axisSize )
return VariationAxisRecord ( axisTag , minValue , defaultValue , maxValue , flags , axisNameID )
@dataclass
class InstanceRecord :
subfamilyNameID : int
flags : int
coordinates : TupleRecord
postscriptNameID : Optional [ int ]
def parse_instance_record ( f : BinaryIO , axisCount : int , instanceSize : int ) - > InstanceRecord :
start_tell = f . tell ( )
subfamilyNameID = read_u16 ( f )
flags = read_u16 ( f )
assert flags == 0 , " Reserved "
coordinates = parse_tuple_record_fixed ( f , axisCount )
postscriptNameID : Optional [ int ] = None
assert f . tell ( ) - start_tell < = instanceSize
if f . tell ( ) - start_tell < instanceSize :
postscriptNameID = read_u16 ( f )
if postscriptNameID == 0xffff : postscriptNameID = None
assert f . tell ( ) - start_tell == instanceSize , ( f . tell ( ) - start_tell , instanceSize )
return InstanceRecord ( subfamilyNameID , flags , coordinates , postscriptNameID )
@dataclass
class FvarTable ( Table , ABD ) :
majorVersion : int
minorVersion : int
axisCount : int
axisSize : int
instanceCount : int
instanceSize : int
axes : List [ VariationAxisRecord ]
instances : List [ InstanceRecord ]
@dataclass
class FvarTable_Ver_1_0 ( FvarTable ) : pass
def parse_fvar_table ( f : BinaryIO ) - > FvarTable :
start_tell = f . tell ( )
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 ]
axesArrayOffset = read_u16 ( f )
assert axesArrayOffset == 16
assert read_u16 ( f ) == 2 , " Reserved "
axisCount = read_u16 ( f )
axisSize = read_u16 ( f )
assert axisSize == 20
instanceCount = read_u16 ( f )
instanceSize = read_u16 ( f )
with SaveTell ( f ) :
# currently, axesArrayOffset points to where we are now, but if axesArrayOffset changes, this might change.
axes = [ parse_variation_axis_record ( f , axisSize ) for _ in range ( axisCount ) ]
instances = [ parse_instance_record ( f , axisCount , instanceSize ) for _ in range ( instanceCount ) ]
if minorVersion == 0 :
return FvarTable_Ver_1_0 ( majorVersion , minorVersion , axisCount , axisSize , instanceCount , instanceSize , axes , instances )
assert False , f " Unimplemented: minorVersion: { minorVersion } "
@dataclass
class GlyphVariationDataTable ( Table ) :
tupleVariationCount : int
data : SerialisedData [ GvarPerTupleVariationData ]
tupleVariationHeaders : List [ TupleVariationHeader ]
PHANTOM_POINTS = 4
def parse_glyph_variation_data_table ( f : BinaryIO , axisCount : int , numPoints : int ) - > GlyphVariationDataTable :
start_tell = f . tell ( )
has_shared_point_numbers , tupleVariationCount = parse_tuple_variation_count ( f )
dataOffset = read_u16 ( f )
tupleVariationHeaders = [ parse_tuple_variation_header ( f , axisCount ) for _ in range ( tupleVariationCount ) ]
with SaveTell ( f ) :
data = parse_at_offset ( f , start_tell , dataOffset , lambda f : parse_serialised_data ( f , has_shared_point_numbers , parse_gvar_per_tuple_variation_data , tupleVariationHeaders , numPoints + PHANTOM_POINTS ) )
return GlyphVariationDataTable ( tupleVariationCount , data , tupleVariationHeaders )
@dataclass
class GvarTable ( Table , ABD ) :
majorVersion : int
minorVersion : int
axisCount : int
sharedTupleCount : int
sharedTuples : List [ TupleRecord ]
glyphCount : int
flags : int
glyphVariationData : List [ Optional [ GlyphVariationDataTable ] ]
2024-09-15 16:10:41 +10:00
def __post_init__ ( self ) :
assert self . flags & 0xfffe == 0 , " Reserved? " # Maybe not?
2024-05-03 21:02:58 +10:00
@dataclass
class GvarTable_Ver_1_0 ( GvarTable ) : pass
def parse_gvar_table ( f : BinaryIO , glyph_data : GlyfTable ) - > GvarTable :
start_tell = f . tell ( )
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 ]
axisCount = read_u16 ( f )
sharedTupleCount = read_u16 ( f )
sharedTuplesOffset = read_u32 ( f )
glyphCount = read_u16 ( f )
2024-09-15 16:10:41 +10:00
flags = read_u16 ( f ) # TODO: Maybe make a method parse_gvar_flags
2024-05-03 21:02:58 +10:00
long = ( flags & 0x0001 ) != 0
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?
with SaveTell ( f ) :
sharedTuples = parse_list_at_offset ( f , start_tell , sharedTuplesOffset , sharedTupleCount , lambda f : parse_tuple_record ( f , axisCount ) )
def get_num_points ( glyphID : int ) - > int :
match glyph_data . glyphs [ glyphID ] :
# TODO: Maybe make an issue on ttf-parser because they don't handle the case of there being no outline for gvar data
case None : return 0
case SimpleGlyph ( coordinates = coordinates ) : return len ( coordinates )
case CompoundGlyph ( components = components ) : return len ( components )
case glyph : assert False , f " Unimplemented: glyph class: { glyph . __class__ . __name__ } "
glyphVariationData = parse_at_offsets_using_length ( f , start_tell + glyphVariationDataArrayOffset , glyphVariationDataOffsets , lambda f , glyphID , _ : parse_glyph_variation_data_table ( f , axisCount , get_num_points ( glyphID ) ) , zero_is_null = False )
if minorVersion == 0 :
return GvarTable_Ver_1_0 ( majorVersion , minorVersion , axisCount , sharedTupleCount , sharedTuples , glyphCount , flags , glyphVariationData )
assert False , f " Unimplemented: minorVersion: { minorVersion } "
@dataclass
class DeltaSetIndexMapTable ( Table ) :
entryFormat : int
mapCount : int
mapData : List [ Tuple [ int , int ] ] # (outerIndex, innerIndex)
2024-09-15 16:10:41 +10:00
def __post_init__ ( self ) :
assert self . entryFormat & 0xffc0 == 0 , " Reserved "
2024-05-03 21:02:58 +10:00
def parse_delta_set_index_map_table ( f : BinaryIO ) - > DeltaSetIndexMapTable :
INNER_INDEX_BIT_COUNT_MASK = 0x000f
MAP_ENTRY_SIZE_MASK = 0x0030
2024-09-15 16:10:41 +10:00
entryFormat = read_u16 ( f ) # TODO: Maybe make all flags like this? If something is reserved, it could just be future things
2024-05-03 21:02:58 +10:00
map_entry_size = ( ( entryFormat & MAP_ENTRY_SIZE_MASK ) >> 4 ) + 1
mapCount = read_u16 ( f )
def parse_entry ( ) - > Tuple [ int , int ] :
entry = read_int ( f , map_entry_size )
return (
entry >> ( entryFormat & INNER_INDEX_BIT_COUNT_MASK ) ,
entry & ( ( 1 << ( entryFormat & INNER_INDEX_BIT_COUNT_MASK ) ) - 1 )
)
mapData : List [ Tuple [ int , int ] ] = [ parse_entry ( ) for _ in range ( mapCount ) ]
return DeltaSetIndexMapTable ( entryFormat , mapCount , mapData )
@dataclass
class HVARTable ( Table , ABD ) :
majorVersion : int
minorVersion : int
itemVariationStore : ItemVariationStoreTable
advanceWidthMapping : Optional [ DeltaSetIndexMapTable ]
lsbMapping : Optional [ DeltaSetIndexMapTable ]
rsbMapping : Optional [ DeltaSetIndexMapTable ]
@dataclass
class HVARTable_Ver_1_0 ( HVARTable ) : pass
def parse_HVAR_table ( f : BinaryIO ) - > HVARTable :
start_tell = f . tell ( )
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 ]
itemVariationStoreOffset = read_u32 ( f )
advanceWidthMappingOffset = read_u32 ( f )
lsbMappingOffset = read_u32 ( f )
rsbMappingOffset = read_u32 ( f )
with SaveTell ( f ) :
itemVariationStore = parse_at_offset ( f , start_tell , itemVariationStoreOffset , parse_item_variation_store_table )
advanceWidthMapping = parse_at_optional_offset ( f , start_tell , advanceWidthMappingOffset , parse_delta_set_index_map_table )
lsbMapping = parse_at_optional_offset ( f , start_tell , lsbMappingOffset , parse_delta_set_index_map_table )
rsbMapping = parse_at_optional_offset ( f , start_tell , rsbMappingOffset , parse_delta_set_index_map_table )
if minorVersion == 0 :
return HVARTable_Ver_1_0 ( majorVersion , minorVersion , itemVariationStore , advanceWidthMapping , lsbMapping , rsbMapping )
assert False , f " Unimplemented: minorVersion: { minorVersion } "
@dataclass
class MVARTable ( Table ) :
pass
def parse_MVAR_table ( f : BinaryIO ) - > MVARTable :
assert False
@dataclass
class AxisRecord :
axisTag : AxisTag
axisNameID : int
axisOrdering : int
def parse_axis_record ( f : BinaryIO , designAxisSize : int ) - > AxisRecord :
start_tell = f . tell ( )
axisTag = parse_axis_tag ( f )
axisNameID = read_u16 ( f )
axisOrdering = read_u16 ( f )
assert f . tell ( ) - start_tell == designAxisSize , ( f . tell ( ) - start_tell , designAxisSize )
return AxisRecord ( axisTag , axisNameID , axisOrdering )
@dataclass
class AxisValueTable ( Table ) :
format : int
@dataclass
class AxisValueTableFlags :
bytes : int
def older_sibling_font_attribute ( self ) - > bool : return ( self . bytes & 0x0001 ) != 0
def elidable_axis_value_name ( self ) - > bool : return ( self . bytes & 0x0002 ) != 0
def parse_axis_value_table_flags ( f : BinaryIO ) - > AxisValueTableFlags :
flags = read_u16 ( f )
# I think bits 2-15 are just empty? They aren't reserved, but there is nothing defined for them.
return AxisValueTableFlags ( flags )
@dataclass
class AxisValueTable_Format_1 ( AxisValueTable ) :
axisIndex : int
flags : AxisValueTableFlags
valueNameID : int
value : float
@dataclass
class AxisValueTable_Format_2 ( AxisValueTable ) :
axisIndex : int
flags : AxisValueTableFlags
valueNameID : int
nominalValue : float
rangeMinValue : float
rangeMaxValue : float
@dataclass
class AxisValueTable_Format_3 ( AxisValueTable ) :
axisIndex : int
flags : AxisValueTableFlags
valueNameID : int
value : float
linkedValue : float
def parse_axis_value_table ( f : BinaryIO ) - > AxisValueTable :
format = read_u16 ( f )
assert format in [ 1 , 2 , 3 , 4 ] , f " Invalid format: { format } "
match format :
case 1 :
axisIndex = read_u16 ( f )
flags = parse_axis_value_table_flags ( f )
valueNameID = read_u16 ( f )
value = read_fixed ( f )
return AxisValueTable_Format_1 ( format , axisIndex , flags , valueNameID , value )
2024-09-15 16:17:20 +10:00
case 2 :
axisIndex = read_u16 ( f )
flags = parse_axis_value_table_flags ( f )
valueNameID = read_u16 ( f )
nominalValue = read_fixed ( f )
rangeMinValue = read_fixed ( f )
rangeMaxValue = read_fixed ( f )
return AxisValueTable_Format_2 ( format , axisIndex , flags , valueNameID , nominalValue , rangeMinValue , rangeMaxValue )
2024-05-03 21:02:58 +10:00
case 3 :
axisIndex = read_u16 ( f )
flags = parse_axis_value_table_flags ( f )
valueNameID = read_u16 ( f )
value = read_fixed ( f )
linkedValue = read_fixed ( f )
return AxisValueTable_Format_3 ( format , axisIndex , flags , valueNameID , value , linkedValue )
case _ :
assert False , f " Unimplemented: format: { format } "
assert False , format
@dataclass
class STATTable ( Table ) :
majorVersion : int
minorVersion : int
designAxisSize : int
designAxisCount : int
axisValueCount : int
designAxes : List [ AxisRecord ]
axisValues : List [ AxisValueTable ]
@dataclass
class STATTable_Ver_1_0 ( STATTable ) : pass
@dataclass
class STATTable_Ver_1_1 ( STATTable_Ver_1_0 ) :
elidedFallbackNameID : int
@dataclass
class STATTable_Ver_1_2 ( STATTable_Ver_1_1 ) : pass
def parse_STAT_table ( f : BinaryIO ) - > STATTable :
start_tell = f . tell ( )
majorVersion = read_u16 ( f )
assert majorVersion == 1
minorVersion = read_u16 ( f )
assert minorVersion in [ 0 , 1 , 2 ] , f " Invalid minorVersion: { minorVersion } "
designAxisSize = read_u16 ( f )
designAxisCount = read_u16 ( f )
offsetToDesignAxes = read_u32 ( f )
axisValueCount = read_u16 ( f )
offsetToAxisValueOffsets = read_u32 ( f )
with SaveTell ( f ) :
designAxes = parse_list_at_offset ( f , start_tell , offsetToDesignAxes , designAxisCount , lambda f : parse_axis_record ( f , designAxisSize ) )
axisValueOffsets = parse_list_at_offset ( f , start_tell , offsetToAxisValueOffsets , axisValueCount , read_u16 )
axisValues = parse_at_offsets ( f , start_tell + offsetToAxisValueOffsets , axisValueOffsets , parse_axis_value_table )
if minorVersion == 0 :
return STATTable_Ver_1_0 ( majorVersion , minorVersion , designAxisSize , designAxisCount , axisValueCount , designAxes , axisValues )
elidedFallbackNameID = read_u16 ( f )
if minorVersion == 1 :
return STATTable_Ver_1_1 ( majorVersion , minorVersion , designAxisSize , designAxisCount , axisValueCount , designAxes , axisValues , elidedFallbackNameID )
if minorVersion == 2 :
return STATTable_Ver_1_2 ( majorVersion , minorVersion , designAxisSize , designAxisCount , axisValueCount , designAxes , axisValues , elidedFallbackNameID )
assert False , minorVersion
@dataclass
class VVARTable ( Table ) :
pass
def parse_VVAR_table ( f : BinaryIO ) - > VVARTable :
assert False
@dataclass
class FontVariations :
axis_variations : Optional [ AvarTable ]
CVT_variations : Optional [ CvarTable ]
font_variations : FvarTable
glyph_variations : Optional [ GvarTable ]
horizontal_metrics_variations : Optional [ HVARTable ]
metrics_variations : Optional [ MVARTable ]
style_attributes : STATTable
vertical_metrics_variations : Optional [ VVARTable ]
@dataclass
2024-05-03 22:46:29 +10:00
class OpenFontFile :
2024-05-03 21:02:58 +10:00
# required tables
character_to_glyph_mapping : CmapTable
font_header : HeadTable
horizontal_header : HheaTable
horizontal_metrics : HmtxTable
maximum_profile : MaxpTable
naming_table : NameTable
OS2_and_Windows_specific_metrics : OS2Table
PostScript_information : PostTable
2024-05-03 22:46:29 +10:00
# TFF/CFF, SVG
2024-05-03 21:02:58 +10:00
outlines : TrueTypeOutlines | CompactFontOutlines
2024-05-03 22:46:29 +10:00
scalar_vector_graphics : Optional [ SvgTable ]
2024-05-03 21:02:58 +10:00
# optional tables
digital_signature : Optional [ DSIGTable ]
horizontal_device_metrics : Optional [ HdmxTable ]
kerning : Optional [ KernTable ]
linear_threshold_data : Optional [ LTSHTable ]
PCL5_data : Optional [ PCLTTable ]
vertical_device_metrics : Optional [ VDMXTable ]
vertical_metrics_header : Optional [ VheaTable ]
vertical_metrics : Optional [ VmtxTable ]
colour_table : Optional [ COLRTable ]
colour_palette_table : Optional [ CPALTable ]
# Other optional things
advanced_features : Optional [ AdvancedFeatures ]
font_variations : Optional [ FontVariations ]
# TODO: Maybe have better error reporting if table is missing
def parse_at_table_directory_entry ( f : BinaryIO , table : Optional [ TableDirectoryEntry ] , parser : Parser [ SomeTable ] ) - > SomeTable :
assert table , " Table not found "
return parse_at_offset ( f , 0 , table . offset , parser )
def possibly_parse_at_table_directory_entry ( f : BinaryIO , table : Optional [ TableDirectoryEntry ] , parser : Parser [ SomeTable ] ) - > Optional [ SomeTable ] :
return parse_at_table_directory_entry ( f , table , parser ) if table else None
def parse_at_table_directory_entry_with_length ( f : BinaryIO , table : Optional [ TableDirectoryEntry ] , parser : Callable [ [ BinaryIO , int ] , SomeTable ] ) - > SomeTable :
assert table , " Table not found "
return parse_at_table_directory_entry ( f , table , lambda f : parser ( f , table . length ) )
def possibly_parse_at_table_directory_entry_with_length ( f : BinaryIO , table : Optional [ TableDirectoryEntry ] , parser : Callable [ [ BinaryIO , int ] , SomeTable ] ) - > Optional [ SomeTable ] :
return parse_at_table_directory_entry_with_length ( f , table , parser ) if table else None
2024-05-03 22:46:29 +10:00
def parse_open_font_file ( f : BinaryIO ) - > OpenFontFile :
2024-05-03 21:02:58 +10:00
font_directory = parse_font_directory ( f )
2024-09-15 16:10:41 +10:00
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 . get_entry ( TableTag . Hhea ) , parse_hhea_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 . 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 . get_entry ( TableTag . Name ) , parse_name_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 . get_entry ( TableTag . Cmap ) , parse_cmap_table )
PostScript_information = parse_at_table_directory_entry_with_length ( f , font_directory . get_entry ( TableTag . Post ) , parse_post_table )
2024-05-03 21:02:58 +10:00
# optional
2024-09-15 16:10:41 +10:00
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 . get_entry ( TableTag . Hdmx ) , lambda f : parse_hdmx_table ( f , maximum_profile . numGlyphs ) )
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 . get_entry ( TableTag . LTSH ) , parse_LTSH_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 . get_entry ( TableTag . VDMX ) , parse_VDMX_table )
vertical_metrics_header = possibly_parse_at_table_directory_entry ( f , font_directory . get_entry ( TableTag . Vhea ) , parse_vhea_table )
if font_directory . get_entry ( TableTag . Vmtx ) :
2024-05-03 21:02:58 +10:00
assert vertical_metrics_header , f " Must have vertical_metrics_header to parse vertical_metrics "
2024-09-15 16:10:41 +10:00
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 ) )
2024-05-03 21:02:58 +10:00
else :
vertical_metrics = None
2024-09-15 16:10:41 +10:00
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 . get_entry ( TableTag . CPAL ) , parse_CPAL_table )
2024-05-03 21:02:58 +10:00
# TTF / CFF
match font_directory . offset_table . sfntVersion :
case 0x00010000 : # TTF
2024-09-15 16:10:41 +10:00
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 . get_entry ( TableTag . Glyf ) , lambda f : parse_glyf_table ( f , index_to_location . offsets ) )
2024-05-03 21:02:58 +10:00
2024-09-15 16:10:41 +10:00
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 . get_entry ( TableTag . Fpgm ) , parse_fpgm_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 . get_entry ( TableTag . Gasp ) , parse_gasp_table )
2024-05-03 21:02:58 +10:00
outlines = TrueTypeOutlines ( control_value_table , font_program , glyph_data , index_to_location , CV_program , grid_fitting_and_scan_conversion )
case _ :
assert False , f " Unimplemented: sfntVersion: { hex ( font_directory . offset_table . sfntVersion ) } "
# SVG
2024-09-15 16:10:41 +10:00
scalar_vector_graphics = possibly_parse_at_table_directory_entry ( f , font_directory . get_entry ( TableTag . Svg ) , parse_svg_table )
2024-05-03 21:02:58 +10:00
2024-09-15 16:10:41 +10:00
# Advanced
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 . get_entry ( TableTag . GDEF ) , parse_GDEF_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 . get_entry ( TableTag . GSUB ) , parse_GSUB_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 . get_entry ( TableTag . MATH ) , parse_MATH_table )
2024-05-03 21:02:58 +10:00
advanced_features = AdvancedFeatures ( baseline_data , glyph_definition_data , glyph_positioning_data , glyph_substitution_data , justification_data , math_layout_data )
font_variations : Optional [ FontVariations ] = None
2024-09-15 16:10:41 +10:00
if font_directory . has_entry ( TableTag . Fvar ) :
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 . get_entry ( TableTag . STAT ) , parse_STAT_table )
axis_variations = possibly_parse_at_table_directory_entry ( f , font_directory . get_entry ( TableTag . Avar ) , parse_avar_table )
if font_directory . has_entry ( TableTag . Cvar ) :
assert isinstance ( outlines , TrueTypeOutlines )
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 ) )
2024-05-03 21:02:58 +10:00
else :
CVT_variations = None
2024-09-15 16:10:41 +10:00
if font_directory . has_entry ( TableTag . Gvar ) :
2024-05-03 21:02:58 +10:00
assert isinstance ( outlines , TrueTypeOutlines )
2024-09-15 16:10:41 +10:00
glyph_variations = parse_at_table_directory_entry ( f , font_directory . get_entry ( TableTag . Gvar ) , lambda f : parse_gvar_table ( f , outlines . glyph_data ) )
2024-05-03 21:02:58 +10:00
else :
glyph_variations = None
2024-09-15 16:10:41 +10:00
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 . get_entry ( TableTag . MVAR ) , parse_MVAR_table )
vertical_metrics_variations = possibly_parse_at_table_directory_entry ( f , font_directory . get_entry ( TableTag . VVAR ) , parse_VVAR_table )
2024-05-03 21:02:58 +10:00
font_variations = FontVariations ( axis_variations , CVT_variations , font_variations_ , glyph_variations , horizontal_metrics_variations , metrics_variations , style_attributes , vertical_metrics_variations )
2024-09-15 16:10:41 +10:00
2024-05-03 22:46:29 +10:00
return OpenFontFile ( character_to_glyph_mapping , font_header , horizontal_header , horizontal_metrics , maximum_profile , naming_table , OS2_and_Windows_specific_metrics , PostScript_information , outlines , scalar_vector_graphics , digital_signature , horizontal_device_metrics , kerning , linear_threshold_data , PCL5_data , vertical_device_metrics , vertical_metrics_header , vertical_metrics , colour_table , colour_palette_table , advanced_features , font_variations )
2024-05-03 21:02:58 +10:00
2024-07-28 14:29:32 +10:00
def open_font_file ( file_path : str ) - > OpenFontFile : # as in `open (verb) font file (noun)`, not OpenFontFile
2024-05-03 21:02:58 +10:00
with open ( file_path , ' rb ' ) as f :
2024-05-03 22:46:29 +10:00
return parse_open_font_file ( f )
2024-09-15 16:22:01 +10:00
def get_name ( font : OpenFontFile , nameID : NameID ) - > str :
assert isinstance ( font . naming_table , NameTable_Format_0 )
for nameRecord in font . naming_table . nameRecord :
if nameRecord . nameID == nameID :
return nameRecord . string
assert False , f " Name not found: { nameID } "
def get_full_name ( font : OpenFontFile ) - > str :
return get_name ( font , PredefinedNameID . FULL_NAME )
def get_glyph_index ( font : OpenFontFile , char : str ) - > int :
for encoding_record in font . character_to_glyph_mapping . encodingRecords :
code_point = encoding_record . encodingID . get_code_point ( char )
if code_point is not None :
match encoding_record . subtable :
case CmapSubtable_Format_4 (
segCountX2 = segCountX2 ,
endCode = end_code ,
startCode = start_code ,
idDelta = id_delta ,
idRangeOffset = id_range_offset ,
glyphIdArray = glyph_id_array ) :
for i , ( start , end ) in enumerate ( zip ( start_code , end_code ) ) :
if start < = code_point < = end :
if start == end == 0xFFFF : return 0
if id_range_offset [ i ] == 0 : return ( id_delta [ i ] + code_point ) % 0xFFFF
index = glyph_id_array [ code_point - start + id_range_offset [ i ] / / 2 + i - segCountX2 / / 2 ]
if index == 0 : return 0
return ( index + id_delta [ i ] ) % 0xFFFF
case CmapSubtable_Format_12 ( groups = groups ) :
for group in groups :
if group . startCharCode < = code_point < = group . endCharCode :
assert False , ( group , code_point )
case _ : assert False , encoding_record . subtable . __class__
return 0
def glyph_count ( font : OpenFontFile ) - > int :
return font . maximum_profile . numGlyphs