feat: add superfig paper-figure toolkit

Standalone LaTeX/TikZ skill for non-tensor paper figures: node/edge
macros, lint-on-warning build, golden examples, and negative fixtures.
This commit is contained in:
dela
2026-08-17 09:40:12 +08:00
commit db5598fbf7
39 changed files with 1732 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
% Cursor extras: gap, leftover rail, a tracked absolute node.
\documentclass[border=8pt]{standalone}
\usepackage[en]{superfig}
\sfsetrole{x}{sfTeal}
\sfsetrole{y}{sfViolet}
\begin{document}
\begin{tikzpicture}
\sfleftrail{4mm}
\sfrow{R1}{12mm}
\sfnode[role=x]{A}{$A$}{12mm}{10mm}
\sfgap{8mm}
\sfnode[role=y]{B}{$B$}{12mm}{10mm}
\sfrowend
\sfnode[role=x, at={($(B.south)+(0,-14mm)$)}]{C}{$C$}{12mm}{10mm}
\sfarrow{B.south}{C.north}
\sflane{R1}
\sfcaption{A}{$A$}{rail}
\sfnolane
\sfcaptiontop{B}{after gap}
\sfcaption{C}{$C$}{absolute, tracked}
\sfbbox{all}
\sftopformula{F}{$C \leftarrow B$}
\end{tikzpicture}
\end{document}
+32
View File
@@ -0,0 +1,32 @@
% Group expands the last row so captions clear the outline.
% One callout hangs off the finished band.
\documentclass[border=8pt]{standalone}
\usepackage[en]{superfig}
\sfsetrole{q}{sfTeal}
\sfsetrole{w}{sfOrange}
\begin{document}
\begin{tikzpicture}
\sfstage{S}{composite plus aside}
\sfrow{R1}{12mm}
\sfnode[role=q]{A}{$A$}{14mm}{10mm}
\sfconn{e1}{}
\sfnode[role=q]{B}{$B$}{14mm}{10mm}
\sfgap{\sflinklen}
\sfnode[role=w]{C}{$C$}{14mm}{10mm}
\sfrowend
\sfgroup[role=q]{G}{(A)(B)}{}
\sfarrow{G.east}{C.west}
\sflane{R1}
\sfcaption{G}{$AB$}{one composite}
\sfcaption{C}{$C$}{consumer}
\sfcallout{N1}{32mm}{R1}{aside}{The card hangs off the band, not off $C$.}
\sfbbox{all}
\sftopformula{F}{$C = f(AB)$}
\sfmeaningbox{mb}{108mm}{all}
{a group is a real composite}
{pair $AB$, consumer $C$}
{captions sit below the outline because the group refits the row}
\end{tikzpicture}
\end{document}
+10
View File
@@ -0,0 +1,10 @@
% A callout hanging off a node, not a band, must dirty the build.
\documentclass[border=2pt]{standalone}
\usepackage[en]{superfig}
\sfsetrole{x}{sfTeal}
\begin{document}\begin{tikzpicture}
\sfrow{R1}{10mm}
\sfnode[role=x]{A}{A}{12mm}{8mm}
\sfrowend
\sfcallout{N}{3cm}{A}{aside}{anchored to a node}
\end{tikzpicture}\end{document}
+14
View File
@@ -0,0 +1,14 @@
% A second callout in the figure must dirty the build.
\documentclass[border=2pt]{standalone}
\usepackage[en]{superfig}
\sfsetrole{x}{sfTeal}
\begin{document}\begin{tikzpicture}
\sfrow{R1}{10mm}
\sfnode[role=x]{A}{A}{12mm}{8mm}
\sfrowend
\sfrow{R2}{10mm}
\sfnode[role=x]{B}{B}{12mm}{8mm}
\sfrowend
\sfcallout{N1}{3cm}{R1}{one}{first}
\sfcallout{N2}{3cm}{R2}{two}{second}
\end{tikzpicture}\end{document}
+10
View File
@@ -0,0 +1,10 @@
% A callout inside an open row is a package error.
\documentclass[border=2pt]{standalone}
\usepackage[en]{superfig}
\sfsetrole{x}{sfTeal}
\begin{document}\begin{tikzpicture}
\sfrow{R1}{10mm}
\sfnode[role=x]{A}{A}{12mm}{8mm}
\sfcallout{N}{3cm}{R1}{t}{b}
\sfrowend
\end{tikzpicture}\end{document}
+7
View File
@@ -0,0 +1,7 @@
% An empty band must dirty the build.
\documentclass[border=2pt]{standalone}
\usepackage[en]{superfig}
\begin{document}\begin{tikzpicture}
\sfrow{R1}{10mm}
\sfrowend
\end{tikzpicture}\end{document}
+8
View File
@@ -0,0 +1,8 @@
% A group around one node is decoration and must dirty the build.
\documentclass[border=2pt]{standalone}
\usepackage[en]{superfig}
\sfsetrole{x}{sfTeal}
\begin{document}\begin{tikzpicture}
\sfnode[role=x, at={(0,0)}]{A}{A}{12mm}{8mm}
\sfgroup[role=x]{G}{(A)}{}
\end{tikzpicture}\end{document}
+9
View File
@@ -0,0 +1,9 @@
% A node taller than its band by more than 4mm must dirty the build.
\documentclass[border=2pt]{standalone}
\usepackage[en]{superfig}
\sfsetrole{x}{sfTeal}
\begin{document}\begin{tikzpicture}
\sfrow{R1}{8mm}
\sfnode[role=x]{A}{tall}{16mm}{16mm}
\sfrowend
\end{tikzpicture}\end{document}
+8
View File
@@ -0,0 +1,8 @@
% Remapping a role to a different color must dirty the build.
\documentclass[border=2pt]{standalone}
\usepackage[en]{superfig}
\sfsetrole{x}{sfTeal}
\sfsetrole{x}{sfOrange}
\begin{document}\begin{tikzpicture}
\sfnode[role=x, at={(0,0)}]{A}{A}{12mm}{8mm}
\end{tikzpicture}\end{document}
+6
View File
@@ -0,0 +1,6 @@
% An undeclared role falls back to gray and must dirty the build.
\documentclass[border=2pt]{standalone}
\usepackage[en]{superfig}
\begin{document}\begin{tikzpicture}
\sfnode[role=ghost, at={(0,0)}]{A}{A}{12mm}{8mm}
\end{tikzpicture}\end{document}
+9
View File
@@ -0,0 +1,9 @@
\documentclass{standalone}
\usepackage[en]{superfig}
\sfsetrole{x}{sfTeal}
\begin{document}\begin{tikzpicture}
\sfrow{R1}{10mm}
\sfnode[role=x]{A}{A}{8mm}{8mm}
\sfrowend
\sfcallout{N}{3cm}{A}{aside}{on a node}
\end{tikzpicture}\end{document}
+13
View File
@@ -0,0 +1,13 @@
\documentclass{standalone}
\usepackage[en]{superfig}
\sfsetrole{x}{sfTeal}
\begin{document}\begin{tikzpicture}
\sfrow{R1}{10mm}
\sfnode[role=x]{A}{A}{8mm}{8mm}
\sfrowend
\sfrow{R2}{10mm}
\sfnode[role=x]{B}{B}{8mm}{8mm}
\sfrowend
\sfcallout{N1}{3cm}{R1}{one}{first}
\sfcallout{N2}{3cm}{R2}{two}{second}
\end{tikzpicture}\end{document}
+9
View File
@@ -0,0 +1,9 @@
\documentclass{standalone}
\usepackage[en]{superfig}
\sfsetrole{x}{sfTeal}
\begin{document}\begin{tikzpicture}
\sftopformula{F}{$x$}
\sfrow{R1}{10mm}
\sfnode[role=x]{A}{A}{8mm}{8mm}
\sfrowend
\end{tikzpicture}\end{document}
+7
View File
@@ -0,0 +1,7 @@
\documentclass{standalone}
\usepackage[en]{superfig}
\sfsetrole{x}{sfTeal}
\begin{document}\begin{tikzpicture}
\sfnode[role=x, at={(0,0)}]{A}{A}{8mm}{8mm}
\sfgroup[role=x]{G}{(A)}{}
\end{tikzpicture}\end{document}
+14
View File
@@ -0,0 +1,14 @@
\documentclass{standalone}
\usepackage[en]{superfig}
\sfsetrole{a}{sfTeal}
\sfsetrole{b}{sfOrange}
\sfsetrole{c}{sfCoral}
\sfsetrole{d}{sfViolet}
\sfsetrole{e}{sfInk}
\begin{document}\begin{tikzpicture}
\sfnode[role=a, at={(0,0)}]{A}{A}{8mm}{8mm}
\sfnode[role=b, at={(12mm,0)}]{B}{B}{8mm}{8mm}
\sfnode[role=c, at={(24mm,0)}]{C}{C}{8mm}{8mm}
\sfnode[role=d, at={(36mm,0)}]{D}{D}{8mm}{8mm}
\sfnode[role=e, at={(48mm,0)}]{E}{E}{8mm}{8mm}
\end{tikzpicture}\end{document}
+5
View File
@@ -0,0 +1,5 @@
\documentclass{standalone}
\usepackage[en]{superfig}
\begin{document}\begin{tikzpicture}
\draw (0,0) -- (1,0);
\end{tikzpicture}\end{document}
+5
View File
@@ -0,0 +1,5 @@
\documentclass{standalone}
\usepackage[en]{superfig}
\begin{document}\begin{tikzpicture}
\sfnode[role=ghost, at={(0,0)}]{A}{A}{8mm}{8mm}
\end{tikzpicture}\end{document}
+27
View File
@@ -0,0 +1,27 @@
% English-label smoke: cursor row, operator, meaning box, formula last.
\documentclass[border=8pt]{standalone}
\usepackage[en]{superfig}
\sfsetrole{x}{sfTeal}
\sfsetrole{y}{sfOrange}
\begin{document}
\begin{tikzpicture}
\sfstage{S}{one band}
\sfrow{R1}{12mm}
\sfnode[role=x]{A}{$A$}{14mm}{10mm}
\sfop{op}{$\circ$}
\sfnode[role=y]{B}{$B$}{14mm}{10mm}
\sfrowend
\sflane{R1}
\sfcaption{A}{$A$}{left}
\sfcaption{B}{$B$}{right}
\sfbbox{all}
\sftopformula{F}{$B = A \circ f$}
\sfmeaningbox{mb}{92mm}{all}
{one composition}
{two objects}
{the operator sits in the flow and reserves its own width}
\sfsignature{Smoke test}{mb}
\end{tikzpicture}
\end{document}