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:
@@ -0,0 +1,21 @@
|
||||
# Anti-patterns
|
||||
|
||||
Each of these can compile cleanly and still teach the reader something false.
|
||||
Rendered pairs: `examples/antipatterns.tex`.
|
||||
|
||||
- **A decorative arrow.** The arrow has no data/control/dependency meaning. Remove it or
|
||||
make the relationship explicit.
|
||||
- **A group that is only a visual border.** The outline does not correspond to a real
|
||||
module/composite in the paper.
|
||||
- **One node doing two jobs.** "attention + layer norm + residual" in one block hides the
|
||||
mechanism the figure was meant to explain.
|
||||
- **A new hue for a slightly different view of the same object.** Reuse the role or use
|
||||
lightness, not a fresh color family.
|
||||
- **A caption repeated in the meaning box.** Captions name the objects; the meaning box
|
||||
explains what they do and why.
|
||||
- **Solving crowding by shrinking type.** The type hierarchy is a hard floor. Move the
|
||||
stage to another row or split the figure.
|
||||
- **Magic offsets.** Every hand-tuned coordinate is valid only for the current label. The
|
||||
next edit will land a longer label on a neighbor.
|
||||
- **A floating commentary card between objects.** If it is not an object or edge, it belongs
|
||||
in the stage subtitle, meaning box, or one `\sfcallout` beside a finished band.
|
||||
@@ -0,0 +1,122 @@
|
||||
# superfig.sty API
|
||||
|
||||
```tex
|
||||
\documentclass[border=10pt]{standalone}
|
||||
\usepackage[cjk]{superfig} % cjk: ctex + fandol (XeLaTeX). en: English rail labels.
|
||||
```
|
||||
|
||||
Build with `./scripts/build.sh fig.tex` — it puts `assets/` on `TEXINPUTS`.
|
||||
|
||||
The build first runs `scripts/lint.py`. It rejects ledger changes, more than four
|
||||
active hues, raw `\draw`/`\fill`/`\path`, a formula placed before the last row, a
|
||||
callout that is not an aside on a `\sfrow` band, a second callout, and a group
|
||||
with fewer than two members. Intentional galleries may put
|
||||
`% superfig-lint: allow-raw-tikz, allow-multiple-callouts` near the top; do not
|
||||
add an exemption to a deliverable merely to make it pass.
|
||||
|
||||
Rules for *when* to use each primitive live in `grammar.md` and `layout.md`.
|
||||
|
||||
## Ledgers
|
||||
|
||||
```tex
|
||||
\sfsetrole{input}{sfTeal} % role -> color. Macros take a ROLE, never a color.
|
||||
\sfsetlabels{A}{O}{M} % override the three meaning-box rail labels
|
||||
\sfsetrail{5.4em} % width of the bold label rail (`[en]` defaults wider)
|
||||
```
|
||||
|
||||
Colors: `sfTeal sfOrange sfCoral sfViolet sfGray sfInk`. An unknown role falls
|
||||
back to gray **and emits a package warning**, which `build.sh` turns into a
|
||||
failed build. Redeclaring a role with a different color warns and keeps the
|
||||
original mapping.
|
||||
|
||||
Lengths: `\sfgutter` (6 mm), `\sfrowgap` (3.5 mm), `\sfblockgap` (9 mm),
|
||||
`\sflinklen` (10 mm).
|
||||
|
||||
## Flow layout
|
||||
|
||||
Empty `at` places the object at the cursor. Hand-written offsets are for
|
||||
genuinely non-linear topology.
|
||||
|
||||
```tex
|
||||
\sfstage{SA}{stage heading}
|
||||
\sfrow{R1}{16mm}
|
||||
\sfnode[role=input]{x}{输入 $x$}{16mm}{12mm}
|
||||
\sfconn{e1}{预处理}
|
||||
\sfnode[role=model]{f}{模型 $f_\theta$}{18mm}{12mm}
|
||||
\sfrowend
|
||||
\sflane{R1}
|
||||
\sfcaption{x}{$x$}{原始输入}
|
||||
```
|
||||
|
||||
| macro | does |
|
||||
|---|---|
|
||||
| `\sfstage{name}{text}` | stage heading on the left rail, below all ink so far |
|
||||
| `\sfrow{name}{height}` | open a band; `height` is a length |
|
||||
| `\sfrowend` | `fit` the band into `name` |
|
||||
| `\sfnode[keys]{name}{label}{w}{h}` | one semantic object |
|
||||
| `\sfop{name}{glyph}` | operator in the flow; reserves its own width |
|
||||
| `\sfconn{name}{label}` | connector; empty label reserves `\sflinklen` of bare arrow |
|
||||
| `\sfgap{4mm}` / `\sfvgap{4mm}` | extra space, horizontal / vertical |
|
||||
| `\sfleftrail{x}` | move the left rail |
|
||||
| `\sfbbox{all}` | everything drawn so far, as one node |
|
||||
| `\sftopformula{F}{math}` | claim/formula, centered on what was actually drawn |
|
||||
| `\sftrack{node}` | fold a hand-placed TikZ node into the bbox and vertical cursor |
|
||||
| `\sflayoutreset` | start over |
|
||||
|
||||
`\sfnode` keys: `role=` (default `neutral`), `level=` (`1/2/3` → `!30/!55/!80`),
|
||||
`gap=` (space *before* this object in a row), `bracket=` (matrix-style arms),
|
||||
`at={}` (empty = cursor; a coordinate bypasses the row).
|
||||
|
||||
Call `\sftopformula` **after** the last `\sfrowend`. A formula placed first is
|
||||
centered on a figure whose width is not yet known.
|
||||
|
||||
## Fixed topology
|
||||
|
||||
```tex
|
||||
\sfnode[role=enc, at={(0,-22mm)}]{in}{输入}{16mm}{10mm}
|
||||
\sfarrow{in.east}{attn.west}
|
||||
\sfarrowlabel[bend left=18]{in.north}{add.north}{残差}
|
||||
```
|
||||
|
||||
`\sfarrow` / `\sfarrowlabel` take the same `to[]` options as TikZ (`bend left`,
|
||||
`out=south, in=north`). The label uses `auto` so a vertical edge does not sit
|
||||
the text on the shaft. Route is on the background layer.
|
||||
|
||||
Keep `at=` on a coarse grid or derive it from existing anchors
|
||||
(`at={($(te.east)!0.5!(ve.east)+(20mm,0)$)}`).
|
||||
|
||||
## Groups, captions, callouts
|
||||
|
||||
```tex
|
||||
\sfgroup[role=attn]{block}{(attn)(ffn)}{} % empty overlay; caption the group
|
||||
\sflane{R1}
|
||||
\sfcaption{block}{主路模块}{注意力 + 前馈}
|
||||
\sfcallout{N1}{38mm}{R1}{title}{body}
|
||||
```
|
||||
|
||||
- `\sfgroup` needs at least two members in the fit list. A single-node outline
|
||||
is a package warning. After a row, the group refits that row so `\sflane{R1}`
|
||||
hangs captions below the outline.
|
||||
- Prefer an empty overlay caption and `\sfcaption{group}{...}{...}`. An overlay
|
||||
sits on the north-west corner and collides with skip edges.
|
||||
- `\sfcallout{name}{width}{band}{title}{body}` hangs off a **finished** `\sfrow`
|
||||
band, one per figure. Inside an open row it is a package error; a non-band
|
||||
anchor or a second card is a warning.
|
||||
- `\sfcaptiontop{name}{text}` is the occasional label above a node.
|
||||
- `\sfnolane` turns the shared caption baseline off.
|
||||
|
||||
## Bottom
|
||||
|
||||
```tex
|
||||
\sfbbox{all}
|
||||
\sfmeaningbox{mb}{96mm}{all}{idea}{objects}{mechanism}
|
||||
\sfsignature{subject}{mb} % optional; subject only
|
||||
```
|
||||
|
||||
Arg 2 is the total box width. An empty `{}` row is dropped. `\usepackage[en]`
|
||||
switches the rails to Concept / Objects / Mechanism.
|
||||
|
||||
## Lint exemptions
|
||||
|
||||
`% superfig-lint: allow-raw-tikz, allow-multiple-callouts, allow-single-group`
|
||||
near the top of the source. Deliverables normally have none.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Delivery checklist
|
||||
|
||||
A clean build only proves TeX and package invariants. Inspect the figure before delivery.
|
||||
|
||||
## Semantics
|
||||
|
||||
- [ ] Every node is one object with one role.
|
||||
- [ ] Every edge is data flow, control flow, dependency, or causality.
|
||||
- [ ] Every group names a real composite, binds at least two members, and all members belong to it.
|
||||
- [ ] At most one `\sfcallout`, hanging off a finished band, not off a single node.
|
||||
- [ ] Captions explain objects or relationships, not decoration.
|
||||
|
||||
## Layout
|
||||
|
||||
- [ ] No node covers another node, caption, edge label, or group border.
|
||||
- [ ] Flow rows start on the same left rail.
|
||||
- [ ] Edge labels sit on their own edge and do not cover unrelated objects.
|
||||
- [ ] Fixed coordinates are on a coarse grid, not tuned against one label width.
|
||||
- [ ] Captions in a row share one baseline.
|
||||
- [ ] Meaning box is one column, at most three rows, no overflow.
|
||||
|
||||
## Style
|
||||
|
||||
- [ ] One role keeps one hue across the whole figure.
|
||||
- [ ] At most four active hue families plus gray.
|
||||
- [ ] Three visibly separated lightness levels where objects need hierarchy; no uniform pastel.
|
||||
- [ ] Borders are neutral; group outlines carry their members' role hue.
|
||||
- [ ] No saturated primaries, shadows, banners, or decorative cards.
|
||||
|
||||
## Thumbnail
|
||||
|
||||
- [ ] Open `*-thumb.png` (360 px). The main claim is still legible.
|
||||
- [ ] Hues remain distinct and muted; text has not collapsed into gray mush.
|
||||
|
||||
## Delivery
|
||||
|
||||
- [ ] PNG preview shown.
|
||||
- [ ] One-paragraph mechanism explanation in prose.
|
||||
- [ ] `.tex` source and PDF/SVG linked.
|
||||
- [ ] Any degraded path or inferred relationship stated explicitly.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Fallback without LaTeX
|
||||
|
||||
Use this path only when `scripts/preflight.sh` exits 2. State explicitly that the
|
||||
`superfig.sty` guarantees — role registry, cursor layout, package warnings, and the build
|
||||
pipeline — are unavailable.
|
||||
|
||||
## Preserve manually
|
||||
|
||||
1. Build the semantics ledger before drawing.
|
||||
2. One hue per role; at most four hues plus gray; three lightness levels.
|
||||
3. Every node is one object; every edge has one relationship; groups are real composites.
|
||||
4. Derive placement from previous bounding boxes plus one gutter constant, not per-label
|
||||
tuning.
|
||||
5. Export SVG and PNG, then inspect full-size and at 360 px using `checklist.md`.
|
||||
|
||||
Prefer SVG for editability. Do not imitate package compliance in the delivery: name the
|
||||
fallback renderer and list any inferred relationship or reduced guarantee.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Visual grammar
|
||||
|
||||
Choose the smallest grammar that exposes the paper's mechanism. Adding a second grammar
|
||||
must add information.
|
||||
|
||||
| Fact to expose | Grammar |
|
||||
|---|---|
|
||||
| one semantic object | `\sfnode` |
|
||||
| linear sequence inside one band | `\sfstage` + `\sfrow` … `\sfrowend` |
|
||||
| an edge between two objects | `\sfconn` for flow, `\sfarrow` for fixed topology |
|
||||
| an edge label | `\sfconn{name}{label}` or `\sfarrowlabel` |
|
||||
| a real composite (module, subsystem, shared owner) | `\sfgroup` |
|
||||
| an operator inside a flow row | `\sfop` |
|
||||
| a side note about a finished band | `\sfcallout` |
|
||||
| the bottom explanation | `\sfmeaningbox` |
|
||||
|
||||
## Node semantics
|
||||
|
||||
One block is one semantic object. If you need two verbs in one block, split it.
|
||||
|
||||
- Label is the object's name or role, not a sentence.
|
||||
- Fill color encodes a role, not importance.
|
||||
- Width/height may differ to reflect a visual hierarchy, but do not use size to invent
|
||||
quantitative meaning unless the figure says so.
|
||||
|
||||
## Edge semantics
|
||||
|
||||
Every arrow must be one of:
|
||||
|
||||
- **data flow** — the output of A becomes the input of B.
|
||||
- **control flow** — A decides whether or when B runs.
|
||||
- **dependency** — B needs A to exist or to have run.
|
||||
- **causality** — A causes B.
|
||||
|
||||
A decorative arrow is an error. If an edge does not carry one of those meanings, remove it
|
||||
or replace it with a grouping/caption.
|
||||
|
||||
## Group semantics
|
||||
|
||||
`\sfgroup` names a composite object. The members inside must actually belong to that
|
||||
composite in the paper. Do not draw an outline around nearby nodes merely because it looks
|
||||
balanced. The fit list is explicit: `{(node1)(node2)}`.
|
||||
|
||||
## Captions and meaning box
|
||||
|
||||
- `\sfcaption{name}{symbol}{detail}`: one short symbol line plus one muted detail line.
|
||||
- `\sfmeaningbox`: at most three rows — idea, objects, mechanism. Prefer removing content
|
||||
over shrinking type or adding columns.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Layout rules
|
||||
|
||||
## Cursor flow
|
||||
|
||||
Inside `\sfrow` … `\sfrowend`, objects are placed from left to right by the cursor.
|
||||
The first object starts on the left rail; every later object reserves the standing gutter.
|
||||
|
||||
- Declare the band height once in `\sfrow`. An object that overflows it is a build warning.
|
||||
- Use `\sfconn` for a labelled edge in the flow; the label reserves its own width.
|
||||
- Do not add magic-number `xshift`s between flow objects.
|
||||
|
||||
## Fixed topology
|
||||
|
||||
Use `\sfnode[at={(x,y)}]{...}` only when the diagram is genuinely non-linear: branches,
|
||||
loops, vertical/horizontal stacks, skip edges.
|
||||
|
||||
- Keep coordinates on a coarse grid; prefer multiples of `\sfgutter` or explicit anchors.
|
||||
- Use `\sfarrow` between placed nodes. Route edges on the background layer.
|
||||
- Put edge labels on the edge itself, not floating nearby.
|
||||
|
||||
## Captions
|
||||
|
||||
Use `\sflane{row}` after `\sfrowend` to give all captions in that row one shared baseline.
|
||||
Symbol and detail lines are two reserved lanes; do not place other text between them.
|
||||
|
||||
## Groups
|
||||
|
||||
A group outline adds inner padding, so place it after its members. It must not cover unrelated
|
||||
nodes. Members must be adjacent in the semantic sense; the explicit fit list prevents the
|
||||
package from inventing a group around whatever is near. A group must bind at least two
|
||||
members.
|
||||
|
||||
After a `\sfrow`, `\sfgroup` refits that row so `\sflane{row}` hangs captions below the
|
||||
outline. Prefer an empty overlay caption and `\sfcaption{group}{...}{...}`: an overlay at
|
||||
the north-west corner lands on skip edges. A connector that should leave the composite
|
||||
starts on the group node (`\sfarrow{block.east}{next.west}`), not on a member — otherwise
|
||||
the shaft crosses an outline that is not its endpoint.
|
||||
|
||||
## Formula
|
||||
|
||||
Call `\sftopformula` after the last `\sfrowend`. The line is centered on `\sfbbox`, whose
|
||||
width is only known once the bands exist.
|
||||
|
||||
## Callouts
|
||||
|
||||
`\sfcallout` hangs off a finished `\sfrow` band, one per figure. It is not an object in
|
||||
the flow and must not be anchored to a single node.
|
||||
|
||||
## Meaning box
|
||||
|
||||
The meaning box hangs below the full figure bbox (`\sfbbox`). It is one column, at most
|
||||
three rows. If it is too long, remove content; do not widen it past the figure or shrink type.
|
||||
@@ -0,0 +1,52 @@
|
||||
# House style
|
||||
|
||||
The package ships these defaults; this file explains what to preserve and what must still
|
||||
be decided.
|
||||
|
||||
## Canvas
|
||||
|
||||
White background, natural `standalone` crop. No forced 16:9. No title by default. The top
|
||||
holds at most two compact claim/formula lines.
|
||||
|
||||
Each stage is one horizontal row with a shared baseline. Use the fewest stages that preserve
|
||||
the primary claim. No unrelated branches, no dashboard panels.
|
||||
|
||||
## Type hierarchy
|
||||
|
||||
| element | size | macro / style |
|
||||
|---|---|---|
|
||||
| top formula/claim | `\large` | `\sftopformula` |
|
||||
| stage label | `\small\bfseries`, muted | `\sfstage` |
|
||||
| node label | `\small` | `\sfnode` |
|
||||
| caption symbol | `\small` | `\sfcaption` |
|
||||
| caption detail | `\scriptsize`, muted | `\sfcaption` |
|
||||
| edge label | `\scriptsize`, muted | `\sfconn` / `\sfarrowlabel` |
|
||||
| meaning box | `\small` | `\sfmeaningbox` |
|
||||
| optional signature | `\scriptsize`, low contrast | `\sfsignature` |
|
||||
|
||||
Never shrink below this hierarchy to make something fit. Move it to another row instead.
|
||||
|
||||
## Color
|
||||
|
||||
Palette: `sfTeal #4F8FA5`, `sfOrange #EE995B`, `sfCoral #C95B5B`, `sfViolet #8A74B5`,
|
||||
`sfGray #85898F`. Muted, mid-chroma, paper-like. Do not add saturated primaries.
|
||||
|
||||
- One semantic color per role, held everywhere: `\sfsetrole{input}{sfTeal}`.
|
||||
- Drawing macros take a **role**, never a color.
|
||||
- At most four active hue families per figure, plus gray.
|
||||
- Contrast comes from lightness separation, not saturation. Use `level=1/2/3` for
|
||||
`role!30 / role!55 / role!80`; do not use uniform pastel fills.
|
||||
- If sign matters, use hue for sign and intensity for magnitude.
|
||||
|
||||
## Edges and nodes
|
||||
|
||||
- Nodes are rounded rectangles with thin neutral borders; no saturated colored outlines.
|
||||
- Group outlines use `role!65` because the outline itself names an object.
|
||||
- Edges are thin neutral arrows on the background layer; text stays on the foreground layer.
|
||||
- Keep all text in LaTeX or CJK text, never rasterized labels.
|
||||
|
||||
## Never
|
||||
|
||||
Metric insets not present in the primary claim. Decorative pills, banners, shadows,
|
||||
repeated separators, extra explanatory cards beyond one `\sfcallout`. Raw TikZ `\draw` /
|
||||
`\fill` / `\path` unless the lint directive allows it.
|
||||
Reference in New Issue
Block a user