mirror of
https://github.com/rsms/inter.git
synced 2024-12-12 15:22:36 +03:00
3f174fcef6
This removes the slant/italic variable axis and breaks up the font in two: roman and italic. This change will allow diverging designs for italic (for example single-storey a). It also addresses the fact that most software, including web browsers, doesn't handle VFs with slnt or ital well.
333 lines
8.9 KiB
Python
Executable File
333 lines
8.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# from gftools
|
|
# https://github.com/googlefonts/gftools/blob/
|
|
# 8b53f595a08d1b3f86f86eb97e07b15b1f52f671/bin/gftools-fix-vf-meta.py
|
|
#
|
|
"""
|
|
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/main/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 Thomas Linard 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
|
|
# from gftools.utils import font_is_italic
|
|
import argparse, re
|
|
|
|
|
|
whitespace_re = re.compile(r'\s+')
|
|
|
|
|
|
def remove_whitespace(s):
|
|
return whitespace_re.sub("", s)
|
|
|
|
|
|
def font_is_italic(ttfont):
|
|
if ttfont['head'].macStyle & 0b10:
|
|
return True
|
|
return False
|
|
# # Check if the font has the word "Italic" in its stylename
|
|
# 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_wght_axis_values(roman_font),
|
|
)
|
|
roman_opsz_axis = dict(
|
|
tag="opsz",
|
|
name="Optical size",
|
|
values=build_opsz_axis_values(roman_font),
|
|
)
|
|
roman_axes = [roman_wght_axis, roman_opsz_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_wght_axis_values(italic_font),
|
|
)
|
|
italic_opsz_axis = dict(
|
|
tag="opsz",
|
|
name="Optical size",
|
|
values=build_opsz_axis_values(italic_font),
|
|
)
|
|
italic_ital_axis = dict(
|
|
tag="ital",
|
|
name="Italic",
|
|
values=[
|
|
dict(
|
|
name="Italic",
|
|
value=1.0,
|
|
)
|
|
]
|
|
)
|
|
italic_axes = [italic_wght_axis, italic_opsz_axis, italic_ital_axis]
|
|
#print("buildStatTable(italic_font)", italic_axes)
|
|
buildStatTable(italic_font, italic_axes)
|
|
#print("buildStatTable(roman_font)", roman_axes)
|
|
buildStatTable(roman_font, roman_axes)
|
|
|
|
|
|
def build_stat_v2(roman_font, italic_font=None):
|
|
roman_wght_axis = dict(
|
|
tag="wght",
|
|
name="Weight",
|
|
)
|
|
roman_opsz_axis = dict(
|
|
tag="opsz",
|
|
name="Optical size",
|
|
)
|
|
roman_axes = [roman_wght_axis, roman_opsz_axis]
|
|
locations = [
|
|
dict(name='Regular', location=dict(wght=400, opsz=14)),
|
|
dict(name='Regular Display', location=dict(wght=400, opsz=32)),
|
|
dict(name='Bold', location=dict(wght=700, opsz=14)),
|
|
dict(name='Bold Display', location=dict(wght=700, opsz=32)),
|
|
]
|
|
buildStatTable(roman_font, roman_axes, locations)
|
|
|
|
|
|
def build_opsz_axis_values(ttfont):
|
|
nametable = ttfont['name']
|
|
instances = ttfont['fvar'].instances
|
|
|
|
val_min = 0.0
|
|
val_max = 0.0
|
|
for instance in instances:
|
|
opsz_val = instance.coordinates["opsz"]
|
|
if val_min == 0.0 or opsz_val < val_min:
|
|
val_min = opsz_val
|
|
if val_max == 0.0 or opsz_val > val_max:
|
|
val_max = opsz_val
|
|
|
|
return [
|
|
{
|
|
"name": "Regular",
|
|
"value": val_min,
|
|
"linkedValue": val_max,
|
|
"flags": 2,
|
|
},
|
|
{
|
|
"name": "Display",
|
|
"value": val_max,
|
|
},
|
|
]
|
|
|
|
# results = []
|
|
# for instance in instances:
|
|
# opsz_val = instance.coordinates["opsz"]
|
|
# name = nametable.getName(instance.subfamilyNameID, 3, 1, 1033).toUnicode()
|
|
# name = name.replace("Italic", "").strip()
|
|
# if name == "":
|
|
# name = "Regular"
|
|
# inst = {
|
|
# "name": name,
|
|
# "value": opsz_val,
|
|
# }
|
|
# if int(opsz_val) == val_min:
|
|
# inst["flags"] = 0
|
|
# inst["linkedValue"] = val_max
|
|
# else:
|
|
# inst["linkedValue"] = val_min
|
|
# results.append(inst)
|
|
|
|
# return results
|
|
|
|
|
|
def build_wght_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"]
|
|
name = nametable.getName(instance.subfamilyNameID, 3, 1, 1033).toUnicode()
|
|
#print(nametable.getName(instance.postscriptNameID, 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)
|
|
|
|
nametable = ttfont['name']
|
|
instances = ttfont["fvar"].instances
|
|
|
|
print("%d instances of %s:" % (len(instances), "Italic" if is_italic else "Roman"))
|
|
|
|
# find opsz max
|
|
opsz_val_max = 0.0
|
|
for instance in instances:
|
|
opsz_val = instance.coordinates["opsz"]
|
|
if opsz_val_max == 0.0 or opsz_val > opsz_val_max:
|
|
opsz_val_max = opsz_val
|
|
|
|
# Update fvar instances
|
|
i = 0
|
|
for inst in instances:
|
|
inst_name = nametable.getName(inst.subfamilyNameID, 3, 1, 1033).toUnicode()
|
|
print("instance %2d" % i, inst_name)
|
|
i += 1
|
|
|
|
# Update instance subfamilyNameID
|
|
if is_italic:
|
|
inst_name = inst_name.strip()
|
|
inst_name = inst_name.replace("Regular Italic", "Italic")
|
|
inst_name = inst_name.replace("Italic", "").strip()
|
|
inst_name = inst_name + " 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 = vf_ps_name + remove_whitespace(inst_name)
|
|
ps_name_id = ttfont['name'].addName(ps_name)
|
|
inst.postscriptNameID = ps_name_id
|
|
|
|
|
|
def _add_nameid_25(ttfont, is_italic, has_mac_names):
|
|
name = ttfont['name'].getName(16, 3, 1, 1033) or \
|
|
ttfont['name'].getName(1, 3, 1, 1033).toUnicode()
|
|
# if is_italic:
|
|
# name = f"{name}Italic"
|
|
# else:
|
|
# name = f"{name}Roman"
|
|
# ttfont['name'].setName(name, 25, 3, 1, 1033)
|
|
if is_italic:
|
|
name = f"{name}Italic"
|
|
ttfont['name'].setName(name, 25, 3, 1, 1033)
|
|
if has_mac_names:
|
|
ttfont['name'].setName(name, 25, 1, 0, 0)
|
|
return name
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
description=__doc__
|
|
)
|
|
parser.add_argument("fonts", nargs="+", help=(
|
|
"Paths to font files. Fonts must be part of the same family."
|
|
)
|
|
)
|
|
args = parser.parse_args()
|
|
paths = args.fonts
|
|
|
|
# This monstrosity exists so we don't break the v1 api.
|
|
italic_font = None
|
|
if len(paths) > 2:
|
|
raise Exception(
|
|
"Can only add STAT tables to a max of two fonts. "
|
|
"Run gftools fix-vf-meta --help for usage instructions"
|
|
)
|
|
elif len(paths) == 2:
|
|
if "Italic" in paths[0]:
|
|
tmp = paths[0]
|
|
paths[0] = paths[1]
|
|
paths[1] = tmp
|
|
elif "Italic" not in paths[1]:
|
|
raise Exception("No Italic font found!")
|
|
roman_font = TTFont(paths[0])
|
|
italic_font = TTFont(paths[1])
|
|
else:
|
|
roman_font = TTFont(paths[0])
|
|
|
|
update_nametable(roman_font)
|
|
if italic_font:
|
|
update_nametable(italic_font)
|
|
|
|
build_stat(roman_font, italic_font)
|
|
|
|
roman_font.save(paths[0] + "-fixed.ttf")
|
|
if italic_font:
|
|
italic_font.save(paths[1] + "-fixed.ttf")
|
|
|
|
# roman_font.save(paths[0])
|
|
# if italic_font:
|
|
# italic_font.save(paths[1])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|