Skip to content

Instantly share code, notes, and snippets.

@simoncozens
Created October 6, 2022 10:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simoncozens/91bd6525f5c653b87c82b59822e12685 to your computer and use it in GitHub Desktop.
Save simoncozens/91bd6525f5c653b87c82b59822e12685 to your computer and use it in GitHub Desktop.
Three-dimensional colour variable fonts
import numpy as np
import matplotlib.pyplot as plt
import math
from babelfont import Font, Layer, Shape, Master, Node, Axis, Glyph
import uuid
from lottie2vf.paintcompiler import compile_paints
from fontTools.ttLib import TTFont
glyph_pts = {
"H": np.array(
[
[-216, -350, 100, 1],
[-216, 350, 100, 1],
[-113, 350, 100, 1],
[-113, 39, 100, 1],
[184, 39, 100, 1],
[184, 350, 100, 1],
[287, 350, 100, 1],
[287, -350, 100, 1],
[184, -350, 100, 1],
[184, -61, 100, 1],
[-113, -61, 100, 1],
[-113, -350, 100, 1],
]
),
"H.side": np.array(
[
[-216, -350, 100, 1],
[-216, 350, 100, 1],
[-216, 350, 150, 1],
[-216, -350, 150, 1],
]
),
"H.insideL": np.array(
[
[-113, -350, 100, 1],
[-113, 350, 100, 1],
[-113, 350, 150, 1],
[-113, -350, 150, 1],
]
),
"H.insideR": np.array(
[
[184, -350, 100, 1],
[184, 350, 100, 1],
[184, 350, 150, 1],
[184, -350, 150, 1],
]
),
"H.side2": np.array(
[
[287, -350, 100, 1],
[287, 350, 100, 1],
[287, 350, 150, 1],
[287, -350, 150, 1],
]
),
"H.bar1": np.array(
[
[-113, 39, 100, 1],
[184, 39, 100, 1],
[184, 39, 150, 1],
[-113, 39, 150, 1],
]
),
"H.bar2": np.array(
[
[-113, -61, 100, 1],
[184, -61, 100, 1],
[184, -61, 150, 1],
[-113, -61, 150, 1],
]
),
}
glyph_pts["H.back"] = glyph_pts["H"] + np.array([0, 0, 50, 0])
in_t = np.array(
[
[1, 0, 0, 0],
[0, 1, 0, -150],
[0, 0, 1, -125],
[0, 0, 0, 1],
]
)
out_t = np.array(
[
[1, 0, 0, 0],
[0, 1, 0, 150],
[0, 0, 1, 125],
[0, 0, 0, 1],
]
)
def xrot(theta):
theta = math.radians(theta)
return np.array(
[
[1, 0, 0, 0],
[0, math.cos(theta), -math.sin(theta), 0],
[0, math.sin(theta), math.cos(theta), 0],
[0, 0, 0, 1],
]
)
def yrot(theta):
theta = math.radians(theta)
ct = math.cos(theta)
st = math.sin(theta)
return np.array([[ct, 0, st, 0], [0, 1, 0, 0], [-st, 0, ct, 0], [0, 0, 0, 1]])
def zrot(theta):
theta = math.radians(theta)
ct = math.cos(theta)
st = math.sin(theta)
return np.array([[ct, -st, 0, 0], [st, ct, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
font = Font()
steps = range(-10, 10, 2)
font.axes = [
Axis(tag="XROT", min=min(steps), max=max(steps), default=0, name="X Rotation"),
Axis(tag="YROT", min=min(steps), max=max(steps), default=0, name="Y Rotation"),
Axis(tag="ZROT", min=min(steps), max=max(steps), default=0, name="Z Rotation"),
]
def np_to_layer(points, master):
layer = Layer(width=1000, id=str(uuid.uuid1()))
layer._master = master.id
shape = Shape(nodes=[])
for (x, y, z, _) in points:
shape.nodes.append(
Node(
x=min(1000, max(-1000, int(100 * x / z))),
y=min(1000, max(-1000, int(100 * y / z))),
type="l",
)
)
layer.shapes.append(shape)
return layer
master_map = {}
for x_angle in steps:
for y_angle in steps:
for z_angle in steps:
location = {"XROT": x_angle, "YROT": y_angle, "ZROT": z_angle}
m = Master(
location=location,
name="_".join(["%s%i" % (k, v) for k, v in location.items()]),
id=str(uuid.uuid1()),
)
master_map[tuple(location.items())] = m
m.font = font
font.masters.append(m)
for glyph_name, pts in glyph_pts.items():
cps = []
if glyph_name == "H":
cps = [ord("H")]
glyph = Glyph(name=glyph_name, codepoints=cps)
font.glyphs.append(glyph)
for x_angle in steps:
for y_angle in steps:
for z_angle in steps:
location = {"XROT": x_angle, "YROT": y_angle, "ZROT": z_angle}
points = [
out_t
@ ((xrot(x_angle) @ yrot(y_angle) @ zrot(z_angle)) @ (in_t @ row))
for row in pts
]
m = master_map[tuple(location.items())]
glyph.layers.append(np_to_layer(points, m))
font.save("threed.ttf")
font = TTFont("threed.ttf")
compile_paints(
font,
"""
toplight = PaintRadialGradient(
(0, 0),
500,
(550, 600),
250,
ColorLine(
"#333333FF",
"#FFFFFFFF",
extend="pad",
))
light = PaintRadialGradient(
(0, 0),
500,
(550, 600),
250,
ColorLine(
"#333333CC",
"#FFFFFFCC",
extend="pad",
))
darkerlight = PaintRadialGradient(
(0, 0),
500,
(550, 600),
250,
ColorLine(
"#111111EE",
"#777777EE",
extend="pad",
))
brighterlight = PaintRadialGradient(
(0, 0),
500,
(550, 600),
250,
ColorLine(
"#999999CC",
"#FFFFFFCC",
extend="pad",
))
testlight = PaintRadialGradient(
(0, 0),
500,
(550, 600),
250,
ColorLine(
"#999999AA",
"#333333AA",
extend="pad",
))
glyphs["H"] = PaintTranslate(500,400,
PaintComposite(
"src_over",
PaintColrLayers([
PaintGlyph("H.bar2",testlight),
PaintGlyph("H.bar1",testlight),
]),
PaintComposite(
"src_over",
PaintColrLayers([
PaintGlyph("H.side",light),
PaintGlyph("H.side2",light),
PaintGlyph("H.insideL",brighterlight),
PaintGlyph("H.insideR",darkerlight),
PaintGlyph("H",toplight),
]),
PaintGlyph("H.back",light)
)
))
""",
)
font.save("threed.ttf")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment