"""Generate the repeating manuscript border: .venv/bin/python docs/dialects/generate_border.py.""" from math import atan2, cos, degrees, pi, sin from pathlib import Path import xml.etree.ElementTree as ET OUTPUT = Path(__file__).with_name("jurassic-border.svg") GOLD = "#c6a45f" def generate(): parts = [''' Illuminated manuscript foliage border Procedural gold scrollwork, acanthus leaves, raptor medallions, rosettes, and pearl ornament on deep green. '''] def path(d, stroke=GOLD, width=.65, fill="none"): parts.append(f'') def circle(x, y, r, fill=GOLD): parts.append(f'') def use(name, x, y, angle=0, scale=1): parts.append(f'') # All vertical periods divide 960, including the scrolls across the tile joins. for y in range(-24, 985, 24): path(f"M12 {y-9} 16 {y} 12 {y+9} 8 {y}Z", width=.5) circle(12, y, 1.2, "#dec58e") for y in range(-240, 961, 240): d = f"M88 {y}C150 {y+60} 26 {y+180} 88 {y+240}" path(d, "#594525", 5) path(d, "url(#gold)", 3) path(d, "#f0dab0", .55) for side, cy in ((1, y+63), (-1, y+183)): cx = 88 + side*20 points = [] for i in range(97): t = i / 96 angle = -pi/2 + side*t*2*pi*1.35 radius = 42*(1-t)+2 points.append((cx+radius*cos(angle), cy+radius*sin(angle))) d = "M" + "L".join(f"{x:.2f} {yy:.2f}" for x, yy in points) path(d, "#594525", 2.8) path(d, "url(#gold)", 1.6) for i in (8, 22, 36, 50): x, yy = points[i] nx, ny = points[i+1] angle = degrees(atan2(ny-yy, nx-x)) + (145 if side == 1 else 35) use("leaf", x, yy, angle, .48 if i == 50 else .63) x, yy = points[-1] circle(x, yy, 3.3, "#542f2e") circle(x, yy, 1.5, "#e4c783") for offset, side in ((16, -1), (108, 1), (136, 1), (228, -1)): yy = y+offset x = 88 + 18*sin(2*pi*offset/240) use("leaf", x, yy, side*56, .84) tx, ty = x+side*39, yy-20 path(f"M{x:.2f} {yy}Q{tx:.2f} {yy+6} {tx:.2f} {ty}", width=.8) use("bud", tx, ty, side*25, .55) # Small rosettes and pen flourishes fill the spaces between the main scrolls. for x, yy in ((45, y+91), (139, y+211)): for angle in range(0, 360, 45): a = angle*pi/180 parts.append(f'') circle(x+13*cos(a), yy+13*sin(a), .65) circle(x, yy, 3.2, "#d7b66e") circle(x, yy, 1.1, "#f5e0ac") for side in (-1, 1): path(f"M{x+side*3} {yy+15}q{side*12} 10 {side*6} 20q{-side*5} 5 {-side*7}-1q0-4 {side*4}-2", width=.55) for i in range(24): yy = y+i*10 x = 158+5*sin(i*pi/6) circle(x, yy, .7 if i % 3 else 1.1, "#a58951") for yy in (y+20, y+140): path(f"M151 {yy-5}V{yy+5}M147 {yy}H155", width=.7) for y in (120, 600): use("raptor", 92, y, scale=.92) for angle in range(0, 360, 30): a = angle*pi/180 circle(92+45*cos(a), y+45*sin(a), 1.1, "#e4c783") parts.append("\n") return "\n".join(parts) if __name__ == "__main__": document = generate() root = ET.fromstring(document) assert root.attrib["viewBox"] == "0 0 180 960" ids = {element.attrib["id"] for element in root.iter() if "id" in element.attrib} for element in root.iter(): if "href" in element.attrib: assert element.attrib["href"][1:] in ids assert document == generate(), "Border generation must be deterministic" OUTPUT.write_text(document)