Last active
April 5, 2023 00:40
-
-
Save moi15moi/3f7704696aa16c64a2224888c032277b to your computer and use it in GitHub Desktop.
Create Font Name for NamedInstance. Warning. It only consider the Axis value table, format 1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
from dataclasses import dataclass | |
from fontTools import ttLib | |
from fontTools.ttLib import ttFont | |
from fontTools.ttLib.tables._f_v_a_r import NamedInstance | |
from fontTools.ttLib.tables._n_a_m_e import NameRecord | |
from typing import Dict, List | |
@dataclass | |
class FontInfo: | |
name: str | |
coordinates: Dict[str, float] | |
@dataclass | |
class AxisRecord: | |
""" | |
https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-records | |
""" | |
AxisTag: str | |
AxisNameID: int | |
AxisOrdering: int | |
@dataclass | |
class AxisValueFormat1: | |
""" | |
https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-1 | |
""" | |
OLDER_SIBLING_FONT_ATTRIBUTE = 0x0001 | |
ELIDABLE_AXIS_VALUE_NAME = 0x0002 | |
axisIndex: int | |
flags: int | |
valueNameID: int | |
value: float | |
def sort_naming_table(names: List[NameRecord]) -> List[NameRecord]: | |
""" | |
Parameters: | |
names (List[NameRecord]): Naming table | |
Returns: | |
The sorted naming table. | |
Based on FontConfig: | |
- https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/d863f6778915f7dd224c98c814247ec292904e30/src/fcfreetype.c#L1127-1140 | |
""" | |
def isEnglish(name: NameRecord) -> bool: | |
# From: https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/d863f6778915f7dd224c98c814247ec292904e30/src/fcfreetype.c#L1111-1125 | |
return (name.platformID, name.langID) in ((1, 0), (3, 0x409)) | |
# From: https://github.com/freetype/freetype/blob/b98dd169a1823485e35b3007ce707a6712dcd525/include/freetype/ttnameid.h#L86-L91 | |
PLATFORM_ID_APPLE_UNICODE = 0 | |
PLATFORM_ID_MACINTOSH = 1 | |
PLATFORM_ID_ISO = 2 | |
PLATFORM_ID_MICROSOFT = 3 | |
# From: https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/d863f6778915f7dd224c98c814247ec292904e30/src/fcfreetype.c#L1078 | |
PLATFORM_ID_ORDER = [ | |
PLATFORM_ID_MICROSOFT, | |
PLATFORM_ID_APPLE_UNICODE, | |
PLATFORM_ID_MACINTOSH, | |
PLATFORM_ID_ISO, | |
] | |
return sorted( | |
names, | |
key=lambda name: ( | |
PLATFORM_ID_ORDER.index(name.platformID), | |
name.nameID, | |
name.platEncID, | |
-isEnglish(name), | |
name.langID, | |
), | |
) | |
def get_first_decoded_name(nameID: int, names: List[NameRecord]) -> str: | |
""" | |
Parameters: | |
names (List[NameRecord]): Naming table | |
Returns: | |
The first decoded name. | |
""" | |
for name in names: | |
if name.nameID != nameID: | |
continue | |
try: | |
unistr = name.toUnicode() | |
except UnicodeDecodeError: | |
continue | |
return unistr | |
def get_family(names: List[NameRecord]): | |
""" | |
Parameters: | |
names (List[NameRecord]): Naming table | |
Returns: | |
The family of an variation font. | |
""" | |
family_name = get_first_decoded_name(16, names) | |
# If nameID 16 is None, try nameID 1https://learn.microsoft.com/en-us/typography/opentype/spec/otvaroverview#terminology | |
if family_name is None: | |
family_name = get_first_decoded_name(1, names) | |
return family_name | |
def get_axis_records(stat_table) -> List[AxisRecord]: | |
""" | |
Parameters: | |
stat_table: The STAT table: https://learn.microsoft.com/en-us/typography/opentype/spec/stat | |
Returns: | |
A list of each AxisRecord: https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-records | |
""" | |
axis_records: List[AxisRecord] = [] | |
if stat_table.DesignAxisRecord: | |
for axis in stat_table.DesignAxisRecord.Axis: | |
axis_records.append( | |
AxisRecord(str(axis.AxisTag), axis.AxisNameID, axis.AxisOrdering) | |
) | |
return axis_records | |
def get_axis_values_format_1(stat_table) -> List[AxisValueFormat1]: | |
""" | |
Parameters: | |
stat_table: The STAT table: https://learn.microsoft.com/en-us/typography/opentype/spec/stat | |
Returns: | |
A list of each AxisValue from table format 1: https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-1 | |
""" | |
axis_values_format_1_table: List[AxisValueFormat1] = [] | |
if stat_table.AxisValueArray: | |
for value in stat_table.AxisValueArray.AxisValue: | |
if value.Format == 1: | |
axis_values_format_1_table.append( | |
AxisValueFormat1( | |
value.AxisIndex, value.Flags, value.ValueNameID, value.Value | |
) | |
) | |
return axis_values_format_1_table | |
def get_named_instance_and_axis_value( | |
font: ttFont.TTFont, | |
axis_values_format_1_table: List[AxisValueFormat1], | |
axis_records: List[AxisRecord], | |
) -> Dict[NamedInstance, List[AxisValueFormat1]]: | |
""" | |
Parameters: | |
font (ttFont.TTFont): Fonttools font object | |
axis_values_format_1_table (List[AxisValueFormat1]): List of each AxisValue from table format 1: https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-1 | |
axis_records (List[AxisRecord]): List of each AxisRecord: https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-records | |
Returns: | |
An dictionnary that map an NamedInstance for an AxisValue | |
""" | |
named_instances: Dict[NamedInstance, AxisValueFormat1] = {} | |
for named_instance in font["fvar"].instances: | |
axis_value_for_named_instance: List[AxisValueFormat1] = [] | |
for ( | |
named_instance_tag, | |
named_instance_value, | |
) in named_instance.coordinates.items(): | |
axis_value_for_coordinate: AxisValueFormat1 = None | |
for axis_value in axis_values_format_1_table: | |
if named_instance_tag == axis_records[axis_value.axisIndex].AxisTag: | |
if axis_value_for_coordinate is None: | |
axis_value_for_coordinate = axis_value | |
elif abs(named_instance_value - axis_value.value) < abs( | |
named_instance_value - axis_value_for_coordinate.value | |
) or ( | |
abs(named_instance_value - axis_value.value) | |
== abs(named_instance_value - axis_value_for_coordinate.value) | |
and axis_value_for_coordinate.value < axis_value.value | |
): | |
axis_value_for_coordinate = axis_value | |
if axis_value_for_coordinate is not None: | |
axis_value_for_named_instance.append(axis_value_for_coordinate) | |
if len(axis_value_for_named_instance) != 0: | |
named_instances[named_instance] = axis_value_for_named_instance | |
return named_instances | |
def get_default_instance( | |
font: ttFont.TTFont, stat_table, names: List[NameRecord], family: str | |
): | |
""" | |
Parameters: | |
font (ttFont.TTFont): Fonttools font object | |
stat_table: The STAT table: https://learn.microsoft.com/en-us/typography/opentype/spec/stat | |
names (List[NameRecord]): Naming table | |
family (str): The family of the font | |
Returns: | |
The default instance. It is created from each VariationAxisRecord.DefaultValue | |
""" | |
default_name = get_first_decoded_name(stat_table.ElidedFallbackNameID, names) | |
coordinates = {} | |
for variation_axis_record in font["fvar"].axes: | |
coordinates[variation_axis_record.axisTag] = variation_axis_record.defaultValue | |
return FontInfo(f"{family} {default_name}".strip(), coordinates) | |
def get_font_infos(font: ttFont.TTFont) -> List[FontInfo]: | |
""" | |
Parameters: | |
font (ttFont.TTFont): Fonttools font object | |
Returns: | |
A list of FontInfo. | |
""" | |
font_infos: List[FontInfo] = [] | |
stat_table = font["STAT"].table | |
axis_records: List[AxisRecord] = get_axis_records(stat_table) | |
axis_value_format_1_table: List[AxisValueFormat1] = get_axis_values_format_1( | |
stat_table | |
) | |
named_instances = get_named_instance_and_axis_value( | |
font, axis_value_format_1_table, axis_records | |
) | |
names = sort_naming_table(font["name"].names) | |
family = get_family(names) | |
for named_instance, axis_values in named_instances.items(): | |
# [[AxisOrdering, AxisValue], [AxisOrdering, AxisValue]] | |
styles = [] | |
for axis_value in axis_values: | |
styles.append([axis_records[axis_value.axisIndex].AxisOrdering, axis_value]) | |
styles.sort(key=lambda style: style[0]) | |
styles_str = "" | |
for style in styles: | |
axis_value = style[1] | |
if axis_value.flags != AxisValueFormat1.ELIDABLE_AXIS_VALUE_NAME: | |
styles_str += ( | |
get_first_decoded_name(axis_value.valueNameID, names) + " " | |
) | |
styles_str = styles_str.strip() | |
font_infos.append( | |
FontInfo(f"{family} {styles_str}".strip(), named_instance.coordinates) | |
) | |
font_infos.append(get_default_instance(font, stat_table, names, family)) | |
return font_infos | |
def main(): | |
font_path = r"bahnschrift (original).ttf" | |
font = ttLib.TTFont(font_path) | |
font_infos = get_font_infos(font) | |
print(font_infos) | |
if __name__ == "__main__": | |
sys.exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Don't use this gist.
Use this one: https://gist.github.com/moi15moi/dd0fd510c03c7d80a274d69bf2edfb29