<commit_before>#!/usr/bin/env python3
"""
Add a STAT table to a weight only variable font.

This script can also add STAT tables to a variable font family which
consists of two fonts, one for Roman, the other for Italic.
Both of these fonts must also only contain a weight axis.

For variable fonts with multiple axes, use DaMa statmake:
https://github.com/daltonmaag/statmake


Usage:

Single family:
gftools fix-vf-meta FontFamily[wght].ttf

Roman + Italic family:
gftools fix-vf-meta FontFamily[wght].ttf FontFamily-Italic[wght].ttf
"""
from gftools.util.google_fonts import _KNOWN_WEIGHTS
from fontTools.otlLib.builder import buildStatTable
from fontTools.ttLib import TTFont
import argparse


WGHT_NAMES = {v:k for k,v in _KNOWN_WEIGHTS.items() if k != "Hairline"}
WGHT_NAMES[400] = "Regular"
WGHT_NAMES[1000] = "ExtraBlack"


def font_is_italic(ttfont):
    stylename = ttfont["name"].getName(2, 3, 1, 0x409).toUnicode()
    return True if "Italic" in stylename else False


def font_has_mac_names(ttfont):
    for record in ttfont['name'].names:
        if record.platformID == 1:
            return True
    return False


def build_stat(roman_font, italic_font=None):
    roman_wght_axis = dict(
        tag="wght",
        name="Weight",
        values=build_axis_values(roman_font),
    )
    roman_axes = [roman_wght_axis]
    if italic_font:
    # We need to create a new Italic axis in the Roman font
        roman_axes.append(
            dict(
                tag="ital",
                name="Italic",
                values=[
                    dict(
                        name="Roman",
                        flags=2,
                        value=0.0,
                        linkedValue=1.0,
                    )
                    
                ]
            )
        )
        italic_wght_axis = dict(
            tag="wght",
            name="Weight",
            values=build_axis_values(italic_font),
        )
        italic_axes = [italic_wght_axis]
        italic_axes.append(
            dict(
                tag="ital",
                name="Italic",
                values=[
                    dict(
                        name="Italic",
                        value=1.0,
                    )
                ]
            )
        )
        buildStatTable(italic_font, italic_axes)
    buildStatTable(roman_font, roman_axes)


def build_axis_values(ttfont):
    results = []
    nametable = ttfont['name']
    instances = ttfont['fvar'].instances
    has_bold = any([True for i in instances if i.coordinates['wght'] == 700])
    for instance in instances:
        name = nametable.getName(
            instance.subfamilyNameID,
            3,
            1,
            1033
        ).toUnicode()
        name = name.replace("Italic", "").strip()
        if name == "":
            name = "Regular"
        inst = {
            "name": name,
            "value": instance.coordinates['wght'],
        }
        if inst["value"] == 400:
            inst["flags"] = 0x2
            if has_bold:
                inst["linkedValue"] = 700
        results.append(inst)
    return results


def update_nametable(ttfont):
    """
    - Add nameID 25
    - Update fvar instance names and add fvar instance postscript names
    """
    is_italic = font_is_italic(ttfont)
    has_mac_names = font_has_mac_names(ttfont)

    # Add nameID 25
    # https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
    vf_ps_name = _add_nameid_25(ttfont, is_italic, has_mac_names)

    # Update fvar instances
    instances = ttfont["fvar"].instances
    for inst in instances:
        wght_val = inst.coordinates["wght"]
        if wght_val not in WGHT_NAMES:
            raise ValueError(f"Fvar instance coord {wght_val} needs to be "
                              "within range 0-1000 and be a multiple of 100")

        # Update instance subfamilyNameID
        inst_name = WGHT_NAMES[wght_val]
        if is_italic:
            inst_name = f"{inst_name} Italic"
            inst_name = inst_name.replace("Regular Italic", "Italic")
        ttfont['name'].setName(inst_name, inst.subfamilyNameID, 3, 1, 0x409)
        if has_mac_names:
            ttfont['name'].setName(inst_name, inst.subfamilyNameID, 1, 0, 0)

        # Add instance psName
        ps_name = f"{vf_ps_name}-{WGHT_NAMES[wght_val]}"
        ps_name_id = ttfont['name'].addName(ps_name)<commit_msg>fix-vf-meta: Use format2 AxisValues<commit_after>#!/usr/bin/env python3
"""
Add a STAT table to a weight only variable font.

This script can also add STAT tables to a variable font family which
consists of two fonts, one for Roman, the other for Italic.
Both of these fonts must also only contain a weight axis.

For variable fonts with multiple axes, write a python script which
uses fontTools.otlLib.builder.buildStatTable e.g
https://github.com/googlefonts/literata/blob/master/sources/gen_stat.py

The generated STAT tables use format 2 Axis Values. These are needed in
order for Indesign to work.

Special mention to <NAME> for reviewing the output of this script.


Usage:

Single family:
gftools fix-vf-meta FontFamily[wght].ttf

Roman + Italic family:
gftools fix-vf-meta FontFamily[wght].ttf FontFamily-Italic[wght].ttf
"""
from fontTools.otlLib.builder import buildStatTable
from fontTools.ttLib import TTFont
import argparse


WGHT = {
    100: "Thin",
    200: "ExtraLight",
    300: "Light",
    400: "Regular",
    500: "Medium",
    600: "SemiBold",
    700: "Bold",
    800: "ExtraBold",
    900: "Black",
    1000: "ExtraBlack",
}


def font_is_italic(ttfont):
    stylename = ttfont["name"].getName(2, 3, 1, 0x409).toUnicode()
    return True if "Italic" in stylename else False


def font_has_mac_names(ttfont):
    for record in ttfont['name'].names:
        if record.platformID == 1:
            return True
    return False


def build_stat(roman_font, italic_font=None):
    roman_wght_axis = dict(
        tag="wght",
        name="Weight",
        values=build_axis_values(roman_font),
    )
    roman_axes = [roman_wght_axis]
    if italic_font:
    # We need to create a new Italic axis in the Roman font
        roman_axes.append(
            dict(
                tag="ital",
                name="Italic",
                values=[
                    dict(
                        name="Roman",
                        flags=2,
                        value=0.0,
                        linkedValue=1.0,
                    )
                    
                ]
            )
        )
        italic_wght_axis = dict(
            tag="wght",
            name="Weight",
            values=build_axis_values(italic_font),
        )
        italic_axes = [italic_wght_axis]
        italic_axes.append(
            dict(
                tag="ital",
                name="Italic",
                values=[
                    dict(
                        name="Italic",
                        value=1.0,
                    )
                ]
            )
        )
        buildStatTable(italic_font, italic_axes)
    buildStatTable(roman_font, roman_axes)


def build_axis_values(ttfont):
    results = []
    nametable = ttfont['name']
    instances = ttfont['fvar'].instances
    has_bold = any([True for i in instances if i.coordinates['wght'] == 700])
    for instance in instances:
        wght_val = instance.coordinates["wght"]
        desired_inst_info = WGHT[wght_val]
        name = nametable.getName(
            instance.subfamilyNameID,
            3,
            1,
            1033
        ).toUnicode()
        name = name.replace("Italic", "").strip()
        if name == "":
            name = "Regular"
        inst = {
            "name": name,
            "nominalValue": wght_val,
        }
        if inst["nominalValue"] == 400:
            inst["flags"] = 0x2
        results.append(inst)

    # Dynamically generate rangeMinValues and rangeMaxValues
    entries = [results[0]["nominalValue"]] + \
              [i["nominalValue"] for i in results] + \
              [results[-1]["nominalValue"]]
    for i, entry in enumerate(results):
        entry["rangeMinValue"] = (entries[i] + entries[i+1]) / 2
        entry["rangeMaxValue"] = (entries[i+1] + entries[i+2]) / 2

    # Format 2 doesn't support linkedValues so we have to append another
    # Axis Value (format 3) for Reg which does support linkedValues
    if has_bold:
        inst = {
            "name": "Regular",
            "value": 400,
            "flags": 0x2,
            "linkedValue": 700
        }
        results.append(inst)
    return results


def update_nametable(ttfont):
    """
    - Add nameID 25
    - Update fvar instance names and add fvar instance postscript names
    """
    is_italic = font_is_italic(ttfont)
    has_mac_names = font_has_mac_names(ttfont)

    # Add nameID 25
    # https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
    vf_ps_name = _add_nameid_25(ttfont, is_italic, has_mac_names)

    # Update fvar instances
    instances = ttfont["fvar"].instances
    for inst in instances:
        wght_val = inst.coordinates["wght"]
        if wght_val not in WGHT:
            raise ValueError(f"Unsupported wght coord '{wght_val}'. Coord "
                              "needs to be in {WGHT.keys()}")

        # Update instance subfamilyNameID
        wght_name = WGHT[wght_val]
        inst_name = wght_name
        if is_italic:
            inst_name = f"{inst_name} Italic"
            inst_name = inst_name.replace("Regular Italic", "Italic")
        ttfont['name'].setName(inst_name, inst.subfamilyNameID, 3, 1, 0x409)
        if has_mac_names:
            ttfont['name'].setName(inst_name, inst.subfamilyNameID, 1, 0, 0)

        # Add instance psName
        ps_name = f"{vf_ps_name}-{wght_name}"
        ps_name_id = ttfont['name'].addName(ps_name)
{'fraction_non_alphanumeric': 0.10053823499543008, 'fraction_numerical': 0.01604549609017975, 'mean_word_length': 1.9851470142467413, 'pattern_counts': {'":': 10, '<': 4, '<?xml version=': 0, '>': 4, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '327440', 'n_tokens_mistral': 3233, 'n_tokens_neox': 3031, 'n_words': 739}