%% florilegium.sty -- a procedurally generated floral page margin, drawn by Lua %% https://github.com/kevinraymond/florilegium %% Copyright (c) 2026 Kevin Raymond. MIT License, in full at the end of these notes, for %% every file of florilegium but florilegium-pgfmath.lua, which reimplements part of pgf and %% is under the LaTeX Project Public License 1.3c, as its header says. %% %% LuaLaTeX only. The geometry of every petal, leaf and stem is computed in Lua %% (florilegium-ink.lua, florilegium-flora.lua, florilegium-native.lua) and written straight %% into the page as PDF path operators; TeX does none of the drawing's arithmetic. A page %% takes a few seconds. %% %% Usage %% \usepackage{florilegium} authored composition (hand-placed, florilegium-authored.lua) %% \usepackage[grown]{florilegium} composition grown to fit the page (florilegium-grow.lua), %% each main stem a plant of its own species, sampled %% from a genome (florilegium-species.lua), four to a %% page from a pool of eight per document %% \usepackage[nomargin]{florilegium} no margin, only \florilegiumdraw %% \usepackage[grown,novisitors]{florilegium} no visitors (insects, snails, birds) %% \usepackage[grown,sections]{florilegium} a flower beside every numbered \section %% and \subsection (standard classes) %% \usepackage[grown,aging]{florilegium} the garden ages from spring on the first %% page to autumn on the last (two runs: the page %% count is read from the .aux of the run before) %% \usepackage[grown,theme=geometric]{florilegium} v1.3's garden cut from flat shapes %% with straight edges (florilegium-theme-geometric.lua) %% \usepackage[grown,theme=math]{florilegium} no plant: figures, symbols and equations %% cascading down the margin (florilegium-theme-math.lua) %% \usepackage[grown,density=0.4]{florilegium} less in the margin: 1 is full, 0.05 a %% single vine %% \usepackage[intensity=0.5]{florilegium} a paler margin: 1 as painted, 0.3 faint %% \florilegiumbloom[motif] (grown) a flower, or any motif of florilegium-grow.lua, in %% the margin beside the current line %% \florilegiumage{a} (grown) the age of the following pages: 0 spring, %% 0.5 summer, 1 autumn %% \florilegiumtheme{name} the theme from the page this stands on (flora: %% none); in the preamble, for the whole document %% \florilegiuminitial[lines]{L} at the start of a paragraph: the letter L as a %% decorated initial, a tile `lines' lines tall %% (default 4), the text wrapped around it %% \florilegiuminitialfont{file.otf} the OpenType font the initial's outline is read %% from (default lmroman10-bold.otf) %% \def\florilegiumseed{7} before \usepackage: a fixed seed (any positive %% integer) instead of the page-number seed; on the %% command line: lualatex "\def\florilegiumseed{7}\input{doc}" %% \florilegiumtextend at the very end of the text (grown only): the %% garden then also fills the space below it %% \florilegiumframe[explain]{k}{n} grown only: the following pages show frame k of %% n of their garden's growth (animation.tex) %% \definecolor{ink}{HTML}{...} after loading: the hue of the whole palette %% \definecolor{accent}{HTML}{...} the copper of the hairline accents %% \florilegiumornament[size=1.5em,ink=5B2A1F,angle=10,seed=random]{leaf} %% one primitive in the text, sized like a letter %% (default 1em); each one in a document differs, %% seed=random gives a new shape on every build %% \florilegiumdraw{flower}{1.2} any primitive at the origin of the current TikZ %% scope; see florilegium-flora.lua for the list and options, %% e.g. \florilegiumdraw{leaf}{2.0, {depth=0.6}}; %% with a theme its primitives too, e.g. {rose}{1.5} %% %% The garden grows in the wider side margin: in a two-sided document (twoside, %% a wide outer margin) it changes sides with the page and is drawn mirrored. %% %% MIT License %% %% Copyright (c) 2026 Kevin Raymond %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal %% in the Software without restriction, including without limitation the rights %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %% copies of the Software, and to permit persons to whom the Software is %% furnished to do so, subject to the following conditions: %% %% The above copyright notice and this permission notice shall be included in all %% copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %% SOFTWARE. %% \NeedsTeXFormat{LaTeX2e}[2020-10-01] \ProvidesPackage{florilegium}[2026/11/05 v2.0.1 procedural floral margin, Lua-drawn] \ifdefined\directlua\else \PackageError{florilegium}{florilegium needs LuaLaTeX}{Compile with lualatex.}\endinput\fi \RequirePackage{tikz} \RequirePackage{eso-pic} \newif\ifflorilegium@grown \newif\ifflorilegium@margin \florilegium@margintrue \newif\ifflorilegium@visitors \florilegium@visitorstrue \newif\ifflorilegium@sections \newif\ifflorilegium@aging \DeclareOption{authored}{\florilegium@grownfalse} \DeclareOption{grown}{\florilegium@growntrue} \DeclareOption{nomargin}{\florilegium@marginfalse} \DeclareOption{novisitors}{\florilegium@visitorsfalse} \DeclareOption{sections}{\florilegium@sectionstrue} \DeclareOption{aging}{\florilegium@agingtrue} %% key=value options: theme=, density=<0..1>, intensity=<0..1> \def\florilegium@theme{} \DeclareOption*{\expandafter\florilegium@option\CurrentOption==\@nil} \def\florilegium@option#1=#2=#3\@nil{% \ifcsname florilegium@set@#1\endcsname\csname florilegium@set@#1\endcsname{#2}% \else\PackageWarning{florilegium}{Unknown option `\CurrentOption'}\fi} \def\florilegium@set@theme#1{\def\florilegium@theme{#1}} \def\florilegium@set@density#1{\def\florilegium@density{#1}} \def\florilegium@set@intensity#1{\def\florilegium@intensity{#1}} \ProcessOptions\relax \ifdefined\florilegiumseed\else\def\florilegiumseed{}\fi %% ---------------------------------------------------------------- palette %% `ink` gives the hue and saturation; the ramp from navy to mist is derived %% from it in OKLCH (florilegium-ink.lua). `accent` is used for the fine copper lines. \definecolor{ink}{HTML}{2456C4} \definecolor{accent}{HTML}{B4662F} \directlua{florilegium = require("florilegium")} %% Every Lua file of a release carries its version, and florilegium.lua compares them %% and this file's \ProvidesPackage as it loads. A florilegium.lua from before that %% has no version at all, so this file says so itself. \directlua{if not florilegium.version then tex.error("florilegium: florilegium.lua is from an earlier release than florilegium.sty", {"Copy florilegium.sty and every florilegium*.lua from one release."}) end} \ifflorilegium@visitors\else\directlua{florilegium.visitors = false}\fi %% a number that is not one warns in the log and takes the default (florilegium.lua: number) \ifdefined\florilegium@density \directlua{florilegium.density = florilegium.number("density", "\luaescapestring{\florilegium@density}", 1)}\fi \ifdefined\florilegium@intensity \directlua{florilegium.intensity = florilegium.number("intensity", "\luaescapestring{\florilegium@intensity}", 1)}\fi %% the theme: in the preamble for the whole document; in the text a mark, so that %% the theme changes from the page that the mark is on (florilegium.lua) \newcommand\florilegiumtheme[1]{% \ifx\@nodocument\relax\directlua{florilegium.mark("theme", "\luaescapestring{#1}")}% \else\directlua{florilegium.theme = "\luaescapestring{#1}"}\fi} \ifx\florilegium@theme\@empty\else\expandafter\florilegiumtheme\expandafter{\florilegium@theme}\fi %% pass the current colors to Lua, and whether the pages are two-sided \def\florilegium@twoside{\directlua{florilegium.twoside = \if@twoside true\else false\fi}} \def\florilegium@colors{% \extractcolorspecs{ink}{\flo@colm}{\flo@cols}\convertcolorspec{\flo@colm}{\flo@cols}{rgb}\flo@ink \extractcolorspecs{accent}{\flo@colm}{\flo@cols}\convertcolorspec{\flo@colm}{\flo@cols}{rgb}\flo@accrgb \directlua{florilegium.colors(\flo@ink, \flo@accrgb)}} %% one primitive inside a tikzpicture, under the current transformation \newcommand\florilegiumdraw[2]{\florilegium@colors\directlua{florilegium.draw("#1", #2)}} %% one primitive in the text, in a box the size of a letter (florilegium.lua): %% \florilegiumornament{leaf}, \florilegiumornament[size=2em,ink=5B2A1F,angle=20,seed=random]{flower} \newdimen\flo@orn@size \pgfqkeys{/florilegium/ornament}{ size/.store in=\flo@orn@sz, size=1em, ink/.store in=\flo@orn@ink, ink=, accent/.store in=\flo@orn@acc, accent=, angle/.store in=\flo@orn@angle, angle=0, seed/.store in=\flo@orn@seed, seed=} \newcommand\florilegiumornament[2][]{\leavevmode\begingroup \pgfqkeys{/florilegium/ornament}{#1}% \flo@orn@size=\flo@orn@sz\relax \florilegium@colors \hbox to\flo@orn@size{\directlua{florilegium.ornament("\luaescapestring{#2}", \number\flo@orn@size, "\luaescapestring{\flo@orn@ink}", "\luaescapestring{\flo@orn@acc}", florilegium.number("angle", "\luaescapestring{\flo@orn@angle}", 0), "\luaescapestring{\flo@orn@seed}")}% \hss\vrule height .85\flo@orn@size depth .15\flo@orn@size width 0pt}% \endgroup} %% ---------------------------------------------------------------- page seed \def\florilegium@seed{% \directlua{tex.sprint(florilegium.seed(\number\value{page}\ifx\florilegiumseed\@empty\else, florilegium.number("\string\\florilegiumseed", "\luaescapestring{\florilegiumseed}", nil, "the page's own seed")\fi))}} %% ---------------------------------------------------------------- compositions \newcommand\florilegium@authoredgarden{% \begin{tikzpicture}[overlay] \florilegium@colors\florilegium@twoside \edef\flo@seed{\florilegium@seed}% \directlua{florilegium.authored(\flo@seed, \number\ShipoutBox)}% \end{tikzpicture}} %% where the text ends, measured in the same pass: \pagetotal is the height of %% the material already on the current page \def\flo@endpage{-1}\def\flo@endtotal{0pt} \newcommand\florilegium@growngarden{% \begin{tikzpicture}[overlay] \florilegium@colors\florilegium@twoside \edef\flo@seed{\florilegium@seed}% % aging: the sheet's place among all the sheets of the last run, whatever the page numbers \ifflorilegium@aging\ifnum\PreviousTotalPages>0 \directlua{florilegium.auto_age(\number\ReadonlyShipoutCounter, \PreviousTotalPages)}\fi\fi \ifnum\value{page}=\flo@endpage\relax \directlua{florilegium.grown(\flo@seed, 1, florilegium.geometry().ytop - \number\dimexpr\flo@endtotal\relax * 2.54 / (72.27 * 65536), \number\ShipoutBox)}% \else \directlua{florilegium.grown(\flo@seed, 0, 0, \number\ShipoutBox)}% \fi \end{tikzpicture}} \newcommand\florilegiumtextend{\par \xdef\flo@endpage{\number\value{page}}% \xdef\flo@endtotal{\the\dimexpr\pagetotal+\pagedepth\relax}} %% the margin follows the text (grown only): a whatsit marks the line, and the %% shipout hook finds it in the page box (florilegium.lua) \newcommand\florilegiumbloom[1][]{\directlua{florilegium.mark("bloom", "\luaescapestring{#1}")}} \protected\def\florilegium@headmark#1{\ifnum#1<3 \directlua{florilegium.mark("heading", \number#1)}\fi} \ifflorilegium@sections % standard classes build every heading with \@sect; its last argument is the % text that is typeset (the TOC and marks get the optional one), so the mark % goes there, after the number \AtBeginDocument{% \let\flo@sect\@sect \def\@sect#1#2#3#4#5#6[#7]#8{\flo@sect{#1}{#2}{#3}{#4}{#5}{#6}[{#7}]{\florilegium@headmark{#2}#8}}} \fi \newcommand\florilegiumage[1]{\directlua{florilegium.age = florilegium.number("\string\\florilegiumage", "\luaescapestring{#1}", nil, "no age")}} %% a decorated initial: a square tile from the cap height of the first line to %% the baseline of line #1, the paragraph's first #1 lines indented beside it \newdimen\flo@ini@tile \newdimen\flo@ini@cap \newcommand\florilegiuminitial[2][4]{\par \global\flo@ini@cap=\fontcharht\font`\H \global\flo@ini@tile=\dimexpr\baselineskip*\numexpr#1-1\relax+\flo@ini@cap\relax \hangindent=\dimexpr\flo@ini@tile+0.6em\relax \hangafter=-#1\relax \noindent\florilegium@colors \directlua{florilegium.initial("\luaescapestring{\csname text_purify:n\endcsname{#2}}", \number\flo@ini@tile, \number\hangindent, \number\flo@ini@cap)}% \llap{\directlua{florilegium.hidden(true)}#2\directlua{florilegium.hidden(false)}}% \ignorespaces} \newcommand\florilegiuminitialfont[1]{\directlua{florilegium.initial_font = "\luaescapestring{#1}"}} \ifflorilegium@aging % the kernel records the number of sheets in the .aux, and the next run reads it back % (\PreviousTotalPages); after the last sheet, a run whose count changed asks for another \AddToHook{enddocument/afterlastpage}{% \ifnum\PreviousTotalPages=\ReadonlyShipoutCounter\else \PackageWarningNoLine{florilegium}{The page count changed. Rerun to get the aging right}\fi} \fi %% growth animation (grown only; animation.tex, tools/animate.py): the pages %% that follow show frame #2 of #3 of their garden's growth; the option %% [explain] also marks the free disc that each branch grows to \newcommand\florilegiumframe[3][]{\directlua{florilegium.frame(#2, #3, "#1")}} %% ---------------------------------------------------------------- plumbing \ifflorilegium@margin \ifflorilegium@grown \AddToShipoutPictureBG{\AtPageLowerLeft{\put(-\hoffset,\voffset){\florilegium@growngarden}}} \else \AddToShipoutPictureBG{\AtPageLowerLeft{\put(-\hoffset,\voffset){\florilegium@authoredgarden}}} \fi \fi \endinput