-
Notifications
You must be signed in to change notification settings - Fork 55
Expand file tree
/
Copy pathinstantiate-code-fonts.py
More file actions
254 lines (185 loc) · 8.63 KB
/
instantiate-code-fonts.py
File metadata and controls
254 lines (185 loc) · 8.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
"""
A script to generate Recursive fonts for code with Regular, Italic, Bold, & Bold Italic,
as configured in config.yaml. See Readme for usage instructions.
Run from the directory above, pointing to a config and a variable font path, e.g.
python3 scripts/instantiate-code-fonts.py <premade-configs/casual.yaml>
"""
import os
import pathlib
import glob
from fontTools import ttLib
import subprocess
import shutil
import yaml
import sys
import ttfautohint
from fontTools.varLib import instancer
from fontTools.varLib.instancer import OverlapMode
from fontTools.varLib.instancer.featureVars import instantiateFeatureVariations
from dlig2calt import dlig2calt
from mergePowerlineFont import mergePowerlineFont
from ttfautohint.options import USER_OPTIONS as ttfautohint_options
from fontfreeze_activation import freeze_features
# if you provide a custom config path, this picks it up
try:
configPath = sys.argv[1]
except IndexError:
configPath = './config.yaml'
# gets font path passed in
try:
fontPath = sys.argv[2] # allows custom path to be passed in, helpful for generating new release from arrowtype/recursive dir
except IndexError:
fontPath = glob.glob('./font-data/Recursive_VF_*.ttf')[0] # allows script to run without font path passed in.
# read yaml config
with open(configPath, encoding='utf-8') as file:
fontOptions = yaml.load(file, Loader=yaml.FullLoader)
# GET / SET NAME HELPER FUNCTIONS
def getFontNameID(font, ID, platformID=3, platEncID=1):
name = str(font["name"].getName(ID, platformID, platEncID))
return name
def setFontNameID(font, ID, newName):
print(f"\n\t• name {ID}:")
macIDs = {"platformID": 3, "platEncID": 1, "langID": 0x409}
winIDs = {"platformID": 1, "platEncID": 0, "langID": 0x0}
oldMacName = font["name"].getName(ID, *macIDs.values())
oldWinName = font["name"].getName(ID, *winIDs.values())
if oldMacName != newName:
print(f"\t\t Mac name was '{oldMacName}'")
font["name"].setName(newName, ID, *macIDs.values())
print(f"\t\t Mac name now '{newName}'")
if oldWinName != newName:
print(f"\t\t Win name was '{oldWinName}'")
font["name"].setName(newName, ID, *winIDs.values())
print(f"\t\t Win name now '{newName}'")
# ----------------------------------------------
# MAIN FUNCTION
oldName = "Recursive"
def splitFont(
outputDirectory=f"RecMono{fontOptions['Family Name']}".replace(" ",""),
newName="Rec Mono",
):
# access font as TTFont object
varfont = ttLib.TTFont(fontPath)
fontFileName = os.path.basename(fontPath)
outputSubDir = f"fonts/{outputDirectory}"
for instance in fontOptions["Fonts"]:
print("\n--------------------------------------------------------------------------------------\n" + instance)
axisLocation = {
"wght": fontOptions["Fonts"][instance]["wght"],
"CASL": fontOptions["Fonts"][instance]["CASL"],
"MONO": fontOptions["Fonts"][instance]["MONO"],
"slnt": fontOptions["Fonts"][instance]["slnt"],
"CRSV": fontOptions["Fonts"][instance]["CRSV"],
}
instanceFont = instancer.instantiateVariableFont(
varfont,
axisLocation,
overlap=OverlapMode.REMOVE
)
instantiateFeatureVariations(instanceFont, axisLocation)
# UPDATE NAME ID 6, postscript name
currentPsName = getFontNameID(instanceFont, 6)
newPsName = (currentPsName\
.replace("Sans", "")\
.replace(oldName,newName.replace(" ", "") + fontOptions['Family Name'].replace(" ",""))\
.replace("LinearLight", instance.replace(" ", "")))
setFontNameID(instanceFont, 6, newPsName)
# UPDATE NAME ID 4, full font name
currentFullName = getFontNameID(instanceFont, 4)
newFullName = (currentFullName\
.replace("Sans", "")\
.replace(oldName, newName + " " + fontOptions['Family Name'])\
.replace(" Linear Light", instance))\
.replace(" Regular", "")
setFontNameID(instanceFont, 4, newFullName)
# UPDATE NAME ID 3, unique font ID
currentUniqueName = getFontNameID(instanceFont, 3)
newUniqueName = (currentUniqueName.replace(currentPsName, newPsName))
setFontNameID(instanceFont, 3, newUniqueName)
# ADD name 2, style linking name
newStyleLinkingName = instance
setFontNameID(instanceFont, 2, newStyleLinkingName)
setFontNameID(instanceFont, 17, newStyleLinkingName)
# UPDATE NAME ID 1, Font Family name
currentFamName = getFontNameID(instanceFont, 1)
newFamName = (newFullName.replace(f" {instance}", ""))
setFontNameID(instanceFont, 1, newFamName)
setFontNameID(instanceFont, 16, newFamName)
newFileName = fontFileName\
.replace(oldName, (newName + fontOptions['Family Name']).replace(" ", ""))\
.replace("_VF_", "-" + instance.replace(" ", "") + "-")
# make dir for new fonts
pathlib.Path(outputSubDir).mkdir(parents=True, exist_ok=True)
# -------------------------------------------------------
# save instance font
outputPath = f"{outputSubDir}/{newFileName}"
# save font
instanceFont.save(outputPath)
# -------------------------------------------------------
# Code font special stuff in post processing
# Freeze rvrn and stylistic set features
# Note: ss04 and similar features use Type 2 (Multiple Substitution) lookups
features_to_freeze = ["rvrn"] + fontOptions["Features"]
freeze_features(
outputPath,
features_to_freeze,
target_feature="calt",
single_sub=True,
)
if fontOptions['Code Ligatures']:
# swap dlig2calt to make code ligatures work in old code editor apps
dlig2calt(outputPath, inplace=True)
# if casual, merge with casual PL; if linear merge w/ Linear PL
if fontOptions["Fonts"][instance]["CASL"] > 0.5:
mergePowerlineFont(outputPath, "./font-data/NerdfontsPL-Regular Casual.ttf")
else:
mergePowerlineFont(outputPath, "./font-data/NerdfontsPL-Regular Linear.ttf")
# TODO, maybe: make VF for powerline font, then instantiate specific CASL instance before merging
# -------------------------------------------------------
# OpenType Table fixes
monoFont = ttLib.TTFont(outputPath)
# drop STAT table to allow RIBBI style naming & linking on Windows
try:
del monoFont["STAT"]
except KeyError:
print("Font has no STAT table.")
# In the post table, isFixedPitched flag must be set in the code fonts
monoFont['post'].isFixedPitch = 1
# In the OS/2 table Panose bProportion must be set to 9
monoFont["OS/2"].panose.bProportion = 9
# Also in the OS/2 table, xAvgCharWidth should be set to 600 rather than 612 (612 is an average of glyphs in the "Mono" files which include wide ligatures).
monoFont["OS/2"].xAvgCharWidth = 600
# Code to fix fsSelection adapted from:
# https://github.com/googlefonts/gftools/blob/a0b516d71f9e7988dfa45af2d0822ec3b6972be4/Lib/gftools/fix.py#L764
old_selection = fs_selection = monoFont["OS/2"].fsSelection
# turn off all bits except for bit 7 (USE_TYPO_METRICS)
fs_selection &= 1 << 7
if instance == "Italic":
monoFont["head"].macStyle = 0b10
# In the OS/2 table Panose bProportion must be set to 11 for "oblique boxed" (this is partially a guess)
monoFont["OS/2"].panose.bLetterForm = 11
# set Italic bit
fs_selection |= 1 << 0
if instance == "Bold":
monoFont['OS/2'].fsSelection = 0b100000
monoFont["head"].macStyle = 0b1
# set Bold bit
fs_selection |= 1 << 5
if instance == "Bold Italic":
monoFont['OS/2'].fsSelection = 0b100001
monoFont["head"].macStyle = 0b11
# set Italic & Bold bits
fs_selection |= 1 << 0
fs_selection |= 1 << 5
monoFont["OS/2"].fsSelection = fs_selection
monoFont.save(outputPath)
# TTF autohint
ttfautohint_options.update(
in_file=outputPath,
out_file=outputPath,
hint_composites=True
)
ttfautohint.ttfautohint()
print(f"\n→ Font saved to '{outputPath}'\n")
print('Features are ', fontOptions['Features'])
splitFont()