-- florilegium-authored.lua -- the authored composition of florilegium. -- Copyright (c) 2026 Kevin Raymond. MIT License: see LICENSE or florilegium.sty. -- -- Everything here is placed by hand, for A4 with a 68 mm left margin (the -- margin column is x in [0.3, 6.4] cm, the page 29.7 cm tall): three stems -- through hand-picked control points, leaves at chosen fractions of their -- length, and motifs at chosen points, each joined to a chosen stem by a -- branch. Only the parameters are sampled (every petal, lobe and vein, and a -- jitter of a millimeter or two on the control points), so every page gets -- the same garden, differently painted. The grown composition (florilegium-grow.lua) -- uses the same primitives and the same motif table. local ink = require("florilegium-ink") local grow = require("florilegium-grow") local M = {} M.version = "2.0.1" -- the release; florilegium.lua checks that every file has the same local sqrt, cos, sin, atan, pi, min, floor = math.sqrt, math.cos, math.sin, math.atan, math.pi, math.min, math.floor local rad = pi / 180 local function norm(x, y) local L = sqrt(x * x + y * y) if L < 1e-9 then return 0, 1 end return x / L, y / L end local function deg(x, y) return atan(y, x) / rad end local LY = grow.layers -- ---------------------------------------------------------------- the score local ROOT = { 1.35, 0.75 } -- stems: control points (cm), base and tip width, what grows at the tip local STEMS = { A = { pts = { { 1.35, 0.75 }, { 2.3, 3.2 }, { 1.7, 6.6 }, { 2.9, 10.2 }, { 2.1, 13.8 }, { 3.2, 17.6 }, { 2.3, 21.2 }, { 2.9, 24.4 }, { 2.7, 25.6 } }, w = { 0.085, 0.03 }, tip = { "flower", 1.7, 90 } }, B = { pts = { { 1.55, 0.75 }, { 3.1, 4.2 }, { 3.7, 8.3 }, { 2.7, 12.2 }, { 3.9, 15.8 }, { 4.4, 18.4 } }, w = { 0.065, 0.028 }, tip = { "lily", 1.15, 70 } }, C = { pts = { { 1.15, 0.75 }, { 0.95, 3.6 }, { 2.1, 7.3 }, { 1.3, 10.6 }, { 1.4, 12.0 } }, w = { 0.06, 0.028 }, tip = { "flower", 0.95, 90 } }, } -- leaves at the nodes: stem, fraction of its length, side (+1 left), length, lobed local LEAVES = { { "A", 0.08, -1, 2.3, true }, { "A", 0.20, 1, 2.2, false }, { "A", 0.33, -1, 2.0, true }, { "A", 0.47, 1, 1.9, true }, { "A", 0.60, -1, 1.8, false }, { "A", 0.72, 1, 1.6, true }, { "A", 0.84, -1, 1.4, false }, { "B", 0.15, 1, 2.1, true }, { "B", 0.33, -1, 1.9, false }, { "B", 0.52, 1, 1.8, true }, { "B", 0.72, -1, 1.5, true }, { "C", 0.22, 1, 1.8, false }, { "C", 0.46, -1, 1.7, true }, { "C", 0.70, 1, 1.4, false }, } -- motifs on branches: stem, fraction where the branch leaves, target center (cm), motif, size local BRANCHES = { { "A", 0.90, 0.95, 27.9, "sprig", 1.6 }, { "A", 0.95, 4.75, 24.9, "bud", 0.7 }, { "A", 0.86, 4.85, 23.1, "flower", 1.0 }, { "A", 0.78, 0.95, 22.4, "umbel", 1.3 }, { "A", 0.70, 0.9, 19.3, "blossoms", 0.75 }, { "A", 0.74, 5.35, 21.4, "blossoms", 0.65 }, { "B", 0.86, 5.55, 16.6, "cyme", 1.9 }, { "A", 0.55, 0.8, 16.0, "bud", 0.7 }, { "C", 0.95, 0.75, 14.6, "cyme", 1.5 }, { "A", 0.60, 5.25, 13.4, "blossoms", 0.8 }, { "C", 0.55, 0.65, 11.4, "bud", 0.6 }, { "B", 0.45, 5.35, 10.6, "umbel", 1.4 }, { "C", 0.62, 0.75, 9.0, "bells", 0.9 }, { "B", 0.28, 5.2, 6.6, "flower", 1.05 }, { "C", 0.30, 0.7, 5.6, "sprig", 2.0 }, { "A", 0.22, 4.7, 3.2, "cyme", 1.6 }, { "B", 0.62, 5.6, 12.0, "tendril", 0.4 }, { "A", 0.40, 1.0, 13.2, "tendril", 0.35 }, { "A", 0.97, 3.9, 28.3, "cyme", 1.2 }, { "A", 0.93, 5.25, 25.4, "blossoms", 0.6 }, { "A", 0.82, 1.0, 24.7, "leaf", 1.4 }, { "B", 0.80, 5.7, 19.9, "bud", 0.6 }, { "B", 0.64, 5.05, 14.9, "leaf", 1.4 }, { "B", 0.38, 5.35, 8.8, "cyme", 1.4 }, { "C", 0.50, 0.75, 7.4, "blossoms", 0.6 }, { "A", 0.10, 0.85, 3.1, "leaf", 1.5 }, { "B", 0.16, 5.6, 4.4, "bud", 0.6 }, { "C", 0.85, 2.6, 11.5, "bud", 0.55 }, } local GHOSTS = { { 3.4, 5.4, 1.5 }, { 2.7, 16.9, 1.25 } } -- visitors: kind, x, y (cm), size, heading (degrees); the snail: stem, fraction, side local VISITORS = { { "butterfly", 4.3, 20.6, 1.3, 100 }, { "bee", 1.6, 26.6, 0.75, 30 } } local SNAIL = { "C", 0.35, 1, 0.8 } local ACCENTS = { { "rosette", 0.45, 4.2 }, { "sparkle", 0.5, 9.9 }, { "sparkle", 0.45, 18.3 }, { "rosette", 0.5, 24.3 }, { "sparkle", 5.9, 1.6 } } -- the footer: the stem runs from the root to this fraction of the text width; -- motifs along it: x (cm), motif, size local FOOT_END = 0.45 local FOOT = { { 3.6, "cyme", 1.1 }, { 5.3, "blossoms", 0.5 }, { 7.6, "cyme", 1.0 }, { 9.6, "blossoms", 0.42 } } -- ---------------------------------------------------------------- drawing -- points along a polyline at a fraction of its length, with the unit tangent local function at(pts, f) local s, tot = ink.arclen(pts) local want = f * tot for i = 2, #pts do if s[i] >= want then local tx, ty = norm(pts[i][1] - pts[i - 1][1], pts[i][2] - pts[i - 1][2]) return pts[i][1], pts[i][2], tx, ty, i end end local n = #pts local tx, ty = norm(pts[n][1] - pts[n - 1][1], pts[n][2] - pts[n - 1][2]) return pts[n][1], pts[n][2], tx, ty, n end -- the score fits A4 with a 68 mm left margin; on any other page its edges are clipped -- at the text, so the first such page says why in the log (LuaLaTeX's; the SVG is silent) local warned = false local function check_paper(g) if warned or not (luatexbase and luatexbase.module_warning) then return end if g.colright < 6.2 or math.abs(g.ph - 29.7) > 0.5 then warned = true luatexbase.module_warning("florilegium", string.format("the authored margin is made for A4 with a 68 mm left margin;" .. " this page's margin column is %.1f cm and its paper %.1f cm tall. Use the grown option to fit the garden to this page.", g.colright, g.ph)) end end function M.page(g) local rng = ink.rng(g.seed * 7919 + 17) local jit = rng.fork() local F, own = grow.theme(g.theme) -- the primitives: flora, or a theme's if own then return own(g, grow) end -- a theme with a composition of its own check_paper(g) ink.set_transform(ink.page_transform(g.mirror and g.pw)) local function place(layer, x, y, a, fn) ink.layer(layer) ink.push() ink.translate(x, y) ink.rotate(a - 90) fn() ink.pop() end -- stems local stems = {} for _, name in ipairs({ "A", "B", "C" }) do -- a fixed order: pairs() is not local st = STEMS[name] local cp = {} for i, p in ipairs(st.pts) do cp[i] = { p[1] + (i > 1 and jit.sym(0.12) or 0), p[2] + (i > 1 and jit.sym(0.12) or 0) } end stems[name] = ink.catmull(cp, 10) end ink.layer(LY.stem) for _, name in ipairs({ "C", "B", "A" }) do F.stem(stems[name], STEMS[name].w[1], STEMS[name].w[2]) end -- the scroll at the root do local pts = {} for i = 0, 40 do local t = i / 40 local a = pi * 0.5 + t * 1.4 * 2 * pi local r = 0.55 * (1 - 0.85 * t) ^ 1.3 pts[#pts + 1] = { ROOT[1] - 0.55 - r * cos(a), ROOT[2] + 0.05 + r * sin(a) } end F.stem(pts, 0.06, 0.012) end -- footer stem local fx1 = g.textleft + (g.textright - g.textleft) * FOOT_END local foot = {} do local f = ink.fbm1(jit, 2) for x = ROOT[1], fx1, 0.2 do local u = math.min(1, (x - ROOT[1]) / 1.5) u = u * u * (3 - 2 * u) foot[#foot + 1] = { x, ROOT[2] + 0.1 + (0.95 - ROOT[2] - 0.1 + 0.2 * f(x / 3.5)) * u } end F.stem(foot, 0.065, 0.01) end -- terminal flowers for _, name in ipairs({ "A", "B", "C" }) do local st = STEMS[name] local pts = stems[name] local p, q = pts[#pts], pts[#pts - 3] local ax, ay = norm(p[1] - q[1], p[2] - q[2]) local kind, s, a = st.tip[1], st.tip[2], st.tip[3] local m = grow.motifs[kind] local r = rng.fork() place(m.layer, p[1] + ax * 0.2, p[2] + ay * 0.2, a, function() m.draw(r, s) end) end -- ghosts behind everything for _, gh in ipairs(GHOSTS) do local r = rng.fork() place(LY.ghost, gh[1], gh[2], 90, function() F.ghost(0.62, function() F.flower(r, gh[3]) end) end) end -- leaves at the nodes for _, lf in ipairs(LEAVES) do local x, y, tx, ty = at(stems[lf[1]], lf[2]) local a = deg(tx, ty) + lf[3] * jit.range(40, 55) local L = lf[4] * 1.12 * jit.range(0.92, 1.05) local r = rng.fork() local bend = lf[3] * jit.range(0.08, 0.3) place(LY.leaf, x, y, a, function() ink.scale(L) F.leaf(r, { w = 0.26, depth = lf[5] and jit.range(0.45, 0.75) or 0, bend = bend }) end) end -- leaflets along the footer, smaller toward its end do local s, tot = ink.arclen(foot) local side, nexts = 1, 0.7 for i = 2, #foot - 2 do if s[i] >= nexts then local u = s[i] / tot nexts = nexts + (0.55 + 0.8 * u) * jit.range(0.85, 1.15) side = -side local tx, ty = norm(foot[i + 1][1] - foot[i - 1][1], foot[i + 1][2] - foot[i - 1][2]) local L = (0.9 - 0.45 * u) * jit.range(0.85, 1.1) local a = deg(tx, ty) + side * jit.range(35, 55) local r = rng.fork() local bend = -side * 0.2 place(LY.leaf, foot[i][1], foot[i][2], a, function() ink.scale(L) F.leaflet(r, { bend = bend }) end) end end end -- branches and their motifs local function grow_branch(sx, sy, tx, ty, px, py, kind, s, w0) local m = grow.motifs[kind] local dx, dy = norm(px - sx, py - sy) local ax, ay = norm(dx, dy + (m.upright or 0)) local ox, oy = px - ax * m.off(s), py - ay * m.off(s) if m.hang then ox, oy = px - dx * 0.2 * s, py + 0.45 * s end local bpts = F.branch_path(jit, sx, sy, tx, ty, ox, oy, m.hang and dx or ax, m.hang and -0.3 or ay) ink.layer(LY.stem) F.stem(bpts, w0, 0.014) local d = sqrt((ox - sx) ^ 2 + (oy - sy) ^ 2) if d > 1.0 then local i = floor(#bpts * 0.4) local p, q = bpts[i], bpts[i + 1] local a = deg(q[1] - p[1], q[2] - p[2]) + (jit.chance(0.5) and 1 or -1) * jit.range(40, 60) local ls = jit.range(0.45, 0.7) local r = rng.fork() place(LY.leaf, p[1], p[2], a, function() ink.scale(ls) F.leaflet(r) end) end local r = rng.fork() place(m.layer, ox, oy, m.hang and 90 or deg(ax, ay), function() m.draw(r, s) end) end for _, b in ipairs(BRANCHES) do local x, y, tx, ty = at(stems[b[1]], b[2]) grow_branch(x, y, tx, ty, b[3] + jit.sym(0.1), b[4] + jit.sym(0.1), b[5], b[6], 0.035) end for _, b in ipairs(FOOT) do if b[1] < fx1 - 0.5 then local best, bi = math.huge, 1 for i, p in ipairs(foot) do local d = math.abs(p[1] - (b[1] - 0.4)) if d < best then best, bi = d, i end end local p, q = foot[bi], foot[min(#foot, bi + 1)] local tx, ty = norm(q[1] - p[1], q[2] - p[2]) grow_branch(p[1], p[2], tx, ty, b[1], p[2] + 0.45, b[2], b[3], 0.025) end end -- accents and the footer's copper tail for _, ac in ipairs(ACCENTS) do local r = rng.fork() place(LY.accent, ac[2], ac[3], 90, function() F[ac[1]](r, ac[1] == "sparkle" and 0.13 or 0.12) end) end do local e = foot[#foot] local x2 = e[1] + 1.9 ink.layer(LY.accent) ink.moveto(e[1], e[2]) ink.curveto(e[1] + 0.6, e[2] + 0.05, x2 - 0.6, e[2] - 0.05, x2, e[2]) ink.stroke(ink.accent(0), 0.3, 0.8) place(LY.accent, x2 + 0.18, e[2], 90, function() F.sparkle(rng.fork(), 0.12, { rays = false }) end) ink.circle(x2 + 0.45, e[2], 0.025) ink.fill(ink.accent(0)) end -- visitors last, from their own stream, so the rest of the page does not change if g.visitors ~= false then local vr = ink.rng(g.seed * 7919 + 103) for _, v in ipairs(VISITORS) do local r = vr.fork() place(LY.accent, v[2], v[3], v[5], function() F[v[1]](r, v[4]) end) end local x, y, tx, ty = at(stems[SNAIL[1]], SNAIL[2]) local r = vr.fork() place(LY.accent, x + ty * SNAIL[3] * 0.03, y - tx * SNAIL[3] * 0.03, deg(tx, ty), function() ink.scale(SNAIL[3], 1) F.snail(r, SNAIL[4]) end) end -- a safety clip: the column (a little past its edge) and the footer band, reaching -- g.bleed past the paper's edge (florilegium-svg.lua's --bleed) ink.layer(0) local b = g.bleed or 0 ink.poly({ { -b, -b }, { g.colright + 0.25, -b }, { g.colright + 0.25, g.ph + b }, { -b, g.ph + b } }, true) ink.poly({ { g.colright, -b }, { fx1 + 3, -b }, { fx1 + 3, min(2.0, g.footy - 0.2) }, { g.colright, min(2.0, g.footy - 0.2) } }, true) ink.clip() ink.layer(99) ink.unclip() ink.flush() end return M