129 lines
3.8 KiB
Python
129 lines
3.8 KiB
Python
#!/usr/bin/env fontforge
|
|
"""
|
|
Apply arch cut to uppercase A-Z and digit 2-9.
|
|
Arch = planet rising from below. The arc curves UPWARD into the bottom of the glyph.
|
|
The cut area is removed (transparent), not filled.
|
|
"""
|
|
import fontforge
|
|
import os
|
|
import math
|
|
|
|
SRC_DIR = "/tmp/aifont_meslo"
|
|
DST_DIR = "/tmp/aifont_arch"
|
|
os.makedirs(DST_DIR, exist_ok=True)
|
|
|
|
chars = []
|
|
for c in range(ord('A'), ord('Z')+1):
|
|
chars.append(chr(c))
|
|
for c in range(ord('0'), ord('9')+1):
|
|
chars.append(chr(c))
|
|
|
|
# Skip arch for round-bottom characters
|
|
skip_arch = set()
|
|
|
|
WIDTH = 602 # Meslo monospace width
|
|
|
|
workfont = fontforge.font()
|
|
workfont.em = 1000
|
|
workfont.encoding = "UnicodeFull"
|
|
|
|
for g in chars:
|
|
cp = ord(g)
|
|
src = os.path.join(SRC_DIR, g + ".svg")
|
|
if not os.path.exists(src):
|
|
continue
|
|
|
|
glyph = workfont.createChar(cp, g)
|
|
glyph.importOutlines(src)
|
|
glyph.width = WIDTH
|
|
|
|
if g not in skip_arch:
|
|
bb = glyph.boundingBox()
|
|
xmin, ymin, xmax, ymax = bb
|
|
h = ymax - ymin
|
|
w = xmax - xmin
|
|
|
|
# How much of the glyph bottom to cut (15%)
|
|
cut_depth = h * 0.07
|
|
|
|
# Large arc radius - the "planet" rising from below
|
|
# Larger radius = gentler curve
|
|
radius = w * 2.5
|
|
|
|
# The arc center is below the glyph
|
|
cx = (xmin + xmax) / 2.0
|
|
# Position so the top of the arc reaches cut_depth into the glyph
|
|
cy = ymin - radius + cut_depth
|
|
|
|
# We need to intersect the glyph with the area ABOVE the arc.
|
|
# Strategy: create a large clockwise (additive) shape representing
|
|
# "everything above the arc line", then intersect with the glyph.
|
|
|
|
# Step 1: Clear glyph, we'll rebuild via intersection
|
|
# Save current contours by using a temp approach
|
|
|
|
# Better strategy using fontforge:
|
|
# 1. Create a second glyph with a huge filled area that has the arc as its bottom edge
|
|
# 2. Use intersect to keep only where glyph and mask overlap
|
|
|
|
# Create mask glyph: a large rectangle with arc-shaped bottom
|
|
mask_cp = 0xE900
|
|
mask = workfont.createChar(mask_cp, "mask")
|
|
mask.clear()
|
|
|
|
margin = 200
|
|
left = xmin - margin
|
|
right = xmax + margin
|
|
top = ymax + margin
|
|
|
|
# The arc: we need points along the arc from left to right
|
|
# Arc equation: (x - cx)^2 + (y - cy)^2 = radius^2
|
|
# y = cy + sqrt(radius^2 - (x-cx)^2) (upper half of circle)
|
|
|
|
# Calculate arc endpoints
|
|
def arc_y(x):
|
|
dx = x - cx
|
|
if abs(dx) > radius:
|
|
return ymin
|
|
return cy + math.sqrt(radius * radius - dx * dx)
|
|
|
|
# Generate points along the arc
|
|
n_points = 40
|
|
arc_points = []
|
|
for idx in range(n_points + 1):
|
|
x = left + (right - left) * idx / n_points
|
|
y = arc_y(x)
|
|
arc_points.append((x, y))
|
|
|
|
pen = mask.glyphPen()
|
|
# Draw clockwise: top-left -> top-right -> arc from right to left -> close
|
|
pen.moveTo((left, top))
|
|
pen.lineTo((right, top))
|
|
# Go down right side to arc start
|
|
pen.lineTo((right, arc_points[-1][1]))
|
|
# Follow arc from right to left (reverse order)
|
|
for idx in range(n_points - 1, -1, -1):
|
|
pen.lineTo(arc_points[idx])
|
|
pen.closePath()
|
|
pen = None
|
|
|
|
# Now intersect: keep only the part of glyph inside the mask
|
|
glyph.correctDirection()
|
|
mask.correctDirection()
|
|
|
|
# Copy mask into glyph's layer and intersect
|
|
workfont.selection.select(mask_cp)
|
|
workfont.copy()
|
|
workfont.selection.select(cp)
|
|
workfont.pasteInto()
|
|
glyph.intersect()
|
|
|
|
# Clean up mask
|
|
mask.clear()
|
|
|
|
dst = os.path.join(DST_DIR, g + ".svg")
|
|
glyph.export(dst)
|
|
print("Done: " + g)
|
|
|
|
print("\nAll done! Results: " + DST_DIR)
|