Files

95 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""Turn the HotDog CMS wordmark (concept X2) into outlines: source/word.json.
"Hot" Recursive, plain mono (MONO 1, CASL 0, wght 700) the markup half
"Dog" Recursive, casual sans (MONO 0, CASL 1, wght 900) the rendered half
"cms" Recursive, casual mono (MONO 1, CASL 1, wght 700) the pill
plus the social card's tagline and window text (TAGLINE below).
Needs fonttools, skia-pathops and uharfbuzz, plus the Recursive variable font
(OFL, github.com/google/fonts ofl/recursive). Only needed to change the type;
gen.py reads word.json and needs neither.
python3 source/word.py /path/to/Recursive[CASL,CRSV,MONO,slnt,wght].ttf
"""
import io
import json
import os
import sys
import pathops
import uharfbuzz as hb
from fontTools.pens.svgPathPen import SVGPathPen
from fontTools.pens.transformPen import TransformPen
from fontTools.ttLib import TTFont
from fontTools.varLib.instancer import instantiateVariableFont
SRC = os.path.dirname(os.path.abspath(__file__))
EM = 100.0 # outlines are written at a 100-unit em, baseline at y=0, y down
CODE = lambda w: {"MONO": 1, "CASL": 0, "wght": w, "slnt": 0, "CRSV": 0.5} # the markup voice
PAGE = lambda w: {"MONO": 0, "CASL": 1, "wght": w, "slnt": 0, "CRSV": 0.5} # the rendered voice
# The social card's tagline. Change it here, run word.py, then gen.py.
TAGLINE = "Write in Markdown. See it live. Publish from git."
RUNS = {
"hot": ("Hot", CODE(700), -0.03),
"dog": ("Dog", PAGE(900), -0.02),
"cms": ("cms", {"MONO": 1, "CASL": 1, "wght": 700, "slnt": 0, "CRSV": 0.5}, 0.0),
# social card: tagline, the Markdown source pane and the rendered pane
"tagline": (TAGLINE, CODE(600), 0.0),
"md_fence": ("---", CODE(500), 0.0),
"md_title": ("title: Hello", CODE(500), 0.0),
"md_h1": ("# Hello, world", CODE(500), 0.0),
"md_p1": ("Write in **Markdown**,", CODE(500), 0.0),
"md_p2": ("see it rendered live.", CODE(500), 0.0),
"page_h1": ("Hello, world", PAGE(900), 0.0),
"page_p1a": ("Write in ", PAGE(400), 0.0),
"page_p1b": ("Markdown", PAGE(800), 0.0),
"page_p1c": (",", PAGE(400), 0.0),
"page_p2": ("see it rendered live.", PAGE(400), 0.0),
}
def run(vf_path, text, loc, tracking):
font = instantiateVariableFont(TTFont(vf_path), loc)
buf = io.BytesIO()
font.save(buf)
upem = font["head"].unitsPerEm
face = hb.Face(buf.getvalue())
hfont = hb.Font(face)
b = hb.Buffer()
b.add_str(text)
b.guess_segment_properties()
hb.shape(hfont, b, {"kern": True, "liga": False})
gs = font.getGlyphSet()
order = font.getGlyphOrder()
s = EM / upem
path = pathops.Path()
x = 0.0
for i, (info, pos) in enumerate(zip(b.glyph_infos, b.glyph_positions)):
name = order[info.codepoint]
p = pathops.Path()
gs[name].draw(TransformPen(p.getPen(glyphSet=gs), (s, 0, 0, -s, x + pos.x_offset * s, -pos.y_offset * s)))
path = pathops.op(path, p, pathops.PathOp.UNION)
x += pos.x_advance * s
if i < len(text) - 1:
x += tracking * EM
pen = SVGPathPen(None, ntos=lambda v: f"{v:.2f}".rstrip("0").rstrip("."))
path.draw(pen)
os2 = font["OS/2"]
xmin, ymin, xmax, ymax = path.bounds
return {"d": pen.getCommands(), "advance": round(x, 2),
"bounds": [round(v, 2) for v in (xmin, ymin, xmax, ymax)],
"cap": round(os2.sCapHeight * s, 2), "xh": round(os2.sxHeight * s, 2)}
if __name__ == "__main__":
vf = sys.argv[1]
out = {k: run(vf, *v) for k, v in RUNS.items()}
with open(os.path.join(SRC, "word.json"), "w") as f:
json.dump(out, f, indent=1)
for k, v in out.items():
print(k, v["advance"], v["bounds"], "cap", v["cap"], "xh", v["xh"])