|
# narrow.py |
|
# Usage: |
|
# fontforge -script narrow.py In.ttf Out.ttf sx blend "Family" "Style" [tracking] |
|
# Examples: |
|
# fontforge -script narrow.py "Courier Prime.ttf" "CourierPrime-Condensed.ttf" 0.85 0.5 "Courier Prime" "Condensed" 15 |
|
|
|
import sys, re, fontforge, psMat |
|
|
|
if len(sys.argv) < 7: |
|
raise SystemExit("Usage: fontforge -script narrow.py In.ttf Out.ttf sx blend \"Family\" \"Style\" [tracking]") |
|
|
|
in_path = sys.argv[-6] |
|
out_path = sys.argv[-5] |
|
sx = float(sys.argv[-4]) # horizontal scale, e.g. 0.85 |
|
t = float(sys.argv[-3]) # blend: 0 keep widths, 1 fully scale widths |
|
family = sys.argv[-2] # new Family (SFNT Family, Preferred Family) |
|
style = sys.argv[-1] if len(sys.argv) == 7 else sys.argv[-1] # for clarity in usage |
|
# Optional tracking if provided as 7th arg after style |
|
track = 0 |
|
if len(sys.argv) >= 8: |
|
try: |
|
track = int(sys.argv[-1]) |
|
style = sys.argv[-2] |
|
family= sys.argv[-3] |
|
except ValueError: |
|
pass |
|
|
|
# Open font |
|
f = fontforge.open(in_path) |
|
|
|
# Cache original metrics for spacing logic |
|
origW = {} |
|
origLSB = {} |
|
for g in f.glyphs(): |
|
origW[g.glyphname] = g.width |
|
origLSB[g.glyphname] = g.left_side_bearing |
|
|
|
# Scale outlines horizontally (no vertical change) |
|
f.selection.all() |
|
f.transform(psMat.scale(sx, 1.0)) |
|
|
|
# Blend spacing toward scaled width, then re-center outlines to keep bearings balanced |
|
for g in f.glyphs(): |
|
W0 = origW.get(g.glyphname, g.width) |
|
if W0 <= 0: |
|
continue |
|
# blended target advance width |
|
Wt = int(round(((1.0 - t) + t * sx) * W0)) + track |
|
# keep original LSB proportion if possible (fallback to centering) |
|
r = origLSB.get(g.glyphname, 0) / float(W0) if W0 else 0.5 |
|
r = min(max(r, 0.0), 1.0) |
|
target_lsb = int(round(r * Wt)) |
|
|
|
# translate outline to achieve target LSB (balances bearings visually) |
|
dx = target_lsb - g.left_side_bearing |
|
if dx: |
|
g.transform(psMat.translate(dx, 0)) |
|
g.width = Wt |
|
|
|
# Build names |
|
full = f"{family} {style}" |
|
ps_name = re.sub(r'[^A-Za-z0-9-]', '', full.replace(' ', '-')) # ASCII, no spaces |
|
|
|
# Set PostScript-style fields |
|
f.familyname = family |
|
f.fullname = full |
|
f.fontname = ps_name |
|
|
|
# Set key SFNT name records for better app compatibility |
|
def setname(lang, key, val): |
|
# appendSFNTName adds/overwrites name records by key and language |
|
try: |
|
f.appendSFNTName(lang, key, val) |
|
except Exception: |
|
pass |
|
|
|
lang = "English (US)" |
|
setname(lang, "Family", family) # nameID 1 |
|
setname(lang, "SubFamily", style) # nameID 2 |
|
setname(lang, "Fullname", full) # nameID 4 |
|
setname(lang, "Preferred Family", family) # nameID 16 |
|
setname(lang, "Preferred Styles", style) # nameID 17 |
|
# Optional extra name records that some apps read: |
|
setname(lang, "WWS Family", family) # nameID 21 |
|
setname(lang, "WWS Subfamily", style) # nameID 22 |
|
setname(lang, "Compatible Full", full) # nameID 18 |
|
|
|
# Generate the output TTF |
|
f.generate(out_path) |