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,8 @@
|
|||||||
|
build/
|
||||||
|
out/
|
||||||
|
*.aux
|
||||||
|
*.log
|
||||||
|
*.out
|
||||||
|
*.fls
|
||||||
|
*.fdb_latexmk
|
||||||
|
*.synctex.gz
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# superfig
|
||||||
|
|
||||||
|
A paper-figure toolkit for non-tensor diagrams: a LaTeX/TikZ macro package, a
|
||||||
|
build pipeline that fails on silent corruption, worked examples, and an agent
|
||||||
|
skill that ties them together.
|
||||||
|
|
||||||
|
It is the generic-node sibling of `supertensor`. Same house style, same
|
||||||
|
role/cursor/warning discipline. The primitives are nodes, edges and groups,
|
||||||
|
not tensor faces. Use `supertensor` when axis lengths and shape identities
|
||||||
|
are the claim.
|
||||||
|
|
||||||
|
This repository is a **child** of SuperPaper, the family parent that
|
||||||
|
routes paper notes to the right figure toolkit. Clone SuperPaper with
|
||||||
|
`--recurse-submodules` for the whole family; use this directory alone
|
||||||
|
when you only need node/edge figures.
|
||||||
|
|
||||||
|
```
|
||||||
|
SKILL.md the skill entry point (lean; loads references on demand)
|
||||||
|
references/ grammar, layout, style, api, checklist, antipatterns
|
||||||
|
assets/superfig.sty the macro package
|
||||||
|
scripts/preflight.sh is the TikZ + CJK path available?
|
||||||
|
scripts/lint.py reject source-level invariant escapes
|
||||||
|
scripts/build.sh lint, compile, audit the log, export pdf/svg/png/thumb
|
||||||
|
scripts/test.sh positive examples plus negative package/lint fixtures
|
||||||
|
examples/ four golden examples + an anti-pattern gallery
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/preflight.sh # 0 = full path, 1 = degraded, 2 = no LaTeX
|
||||||
|
./scripts/build.sh examples/pipeline.tex # -> examples/build/pipeline.{pdf,svg,png}
|
||||||
|
./scripts/test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
A minimal figure:
|
||||||
|
|
||||||
|
```tex
|
||||||
|
\documentclass[border=10pt]{standalone}
|
||||||
|
\usepackage[cjk]{superfig}
|
||||||
|
|
||||||
|
\sfsetrole{input}{sfTeal}
|
||||||
|
\sfsetrole{model}{sfOrange}
|
||||||
|
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\sfstage{S}{one band, placed by cursor}
|
||||||
|
\sfrow{R1}{14mm}
|
||||||
|
\sfnode[role=input]{x}{输入 $x$}{16mm}{12mm}
|
||||||
|
\sfconn{e1}{预处理}
|
||||||
|
\sfnode[role=model]{f}{模型 $f_\theta$}{18mm}{12mm}
|
||||||
|
\sfrowend
|
||||||
|
\sflane{R1}
|
||||||
|
\sfcaption{x}{$x$}{原始输入}
|
||||||
|
\sfcaption{f}{$f_\theta$}{可学习参数}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
|
```
|
||||||
|
|
||||||
|
See `references/api.md` for the full macro list.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
| file | shows |
|
||||||
|
|---|---|
|
||||||
|
| `pipeline.tex` | cursor flow; loss as a side object, not a station on the forward path |
|
||||||
|
| `branch-architecture.tex` | residual skip; a group captioned on the shared lane |
|
||||||
|
| `state-flow.tex` | time/state step; one `\sfcallout` on a finished band |
|
||||||
|
| `dependency-graph.tex` | dual-encoder join, fusion placed from existing anchors |
|
||||||
|
| `antipatterns.tex` | four figures that compile cleanly and still teach something false |
|
||||||
|
|
||||||
|
## Using it as an agent skill
|
||||||
|
|
||||||
|
`SKILL.md` is the entry point; the `references/` files are loaded on demand.
|
||||||
|
Point your agent runtime at this directory. The skill assumes `scripts/` and
|
||||||
|
`assets/` sit beside it.
|
||||||
|
|
||||||
|
## Why the build script fails on warnings
|
||||||
|
|
||||||
|
`Missing character` drops a CJK glyph silently. `Overfull \hbox` lets a label
|
||||||
|
escape its lane. `Package superfig Warning` is an undeclared role, a
|
||||||
|
single-member group, a band overflow, a second callout, or a callout hanging
|
||||||
|
off a node. `build.sh` greps for all of them and exits non-zero.
|
||||||
|
|
||||||
|
A clean build also proves the source avoided raw TikZ drawing, a formula
|
||||||
|
placed before the last row, and more than four active hues. It still cannot
|
||||||
|
prove that the relationships are right; that is what `references/checklist.md`
|
||||||
|
is for.
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
name: superfig
|
||||||
|
description: Create or refine clean paper-style figures for non-tensor concepts — architecture and block diagrams, pipelines and data flow, state/time flows, dependency graphs, and conceptual mechanism diagrams. Use whenever a paper idea should be explained with clear nodes, edges, grouping, captions, and a short meaning box in a muted slide-ready style. Not for tensor/matrix/shape semantics (use supertensor) or plotting numeric data.
|
||||||
|
---
|
||||||
|
|
||||||
|
# superfig
|
||||||
|
|
||||||
|
Turn one paper claim or mechanism into one dense, slide-ready figure with three zones:
|
||||||
|
|
||||||
|
1. **Top — idea.** The compact claim or formula. Optional; call it after the drawing.
|
||||||
|
2. **Middle — structure.** Semantic nodes, directed edges, grouped composites, and captions.
|
||||||
|
3. **Bottom — meaning.** What the idea is, what the objects are, what the mechanism does.
|
||||||
|
|
||||||
|
`assets/superfig.sty` enforces the house style and the common cursor/role discipline.
|
||||||
|
Draw with `\sfnode` / `\sfconn` / `\sfarrow` / `\sfgroup`; do not hand-roll TikZ rectangles
|
||||||
|
and arrows unless the linter explicitly allows it.
|
||||||
|
|
||||||
|
## When to use
|
||||||
|
|
||||||
|
- Architecture, block diagram, pipeline, data flow, dependency graph, state or time flow.
|
||||||
|
- A conceptual mechanism that is not a tensor shape: nodes, arrows, containment, lanes.
|
||||||
|
- A figure that should look like `supertensor` in palette and text hierarchy.
|
||||||
|
|
||||||
|
Do **not** use for tensor faces, axes, contractions, sharding or broadcasting; route those to
|
||||||
|
`supertensor`. Do not use for loss curves, benchmark bars, scatter plots, or dashboards.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Preflight.** `./scripts/preflight.sh`. Exit 0 = TikZ+CJK path. Exit 1 = degraded
|
||||||
|
(say so). Exit 2 = no LaTeX; read `references/fallback.md` and name the lost guarantees.
|
||||||
|
2. **Reduce** the paper to one primary claim. Drop parallel objectives, optional modules,
|
||||||
|
diagnostics and secondary paths unless the user asks for them.
|
||||||
|
3. **Build a semantics ledger** before drawing:
|
||||||
|
- *nodes* — each block is one semantic object: kind, domain/range, owner, lifecycle.
|
||||||
|
- *edges* — each arrow is data flow, control flow, dependency, or causality; never decoration.
|
||||||
|
- *groups* — an outline only when it names a real composite object.
|
||||||
|
See `references/grammar.md`.
|
||||||
|
4. **Choose the smallest grammar** that exposes the mechanism:
|
||||||
|
- one object = `\sfnode[role=..., level=...]{name}{label}{width}{height}`
|
||||||
|
- horizontal flow = `\sfstage` + `\sfrow` … `\sfrowend`
|
||||||
|
- horizontal connector with label = `\sfconn{name}{label}`
|
||||||
|
- fixed/branching edge = `\sfarrow[options]{from}{to}` or `\sfarrowlabel`
|
||||||
|
- composite = `\sfgroup[role=...]{name}{(member1)(member2)}{}` then `\sfcaption`
|
||||||
|
- a whole-band aside = `\sfcallout`
|
||||||
|
- bottom explanation = `\sfmeaningbox`
|
||||||
|
Full macro list: `references/api.md`. Worked figures: `examples/`.
|
||||||
|
5. **Build and audit.** `./scripts/build.sh fig.tex` runs lint, TeX checks, and exports.
|
||||||
|
Then run `references/checklist.md` against the PNG full-size and thumbnail. Redraw on
|
||||||
|
any mandatory violation.
|
||||||
|
|
||||||
|
## Non-negotiables
|
||||||
|
|
||||||
|
- **Semantics** — one node, one object. An edge says one relationship. A group is a real
|
||||||
|
composite, not a decorative border.
|
||||||
|
- **Layout** — use the cursor in flow rows; use `at={<coord>}` only when the topology is
|
||||||
|
genuinely non-linear. Never hand-tune a neighbor against a magic offset.
|
||||||
|
- **Style** — muted palette, one hue per role, at most four active hues plus gray,
|
||||||
|
three separated lightness levels, no saturated primaries, no dashboard clutter.
|
||||||
|
- **Build** — `Missing character`, overfull/underfull boxes, and `Package superfig Warning`
|
||||||
|
are build failures, not cosmetic warnings.
|
||||||
|
|
||||||
|
`references/antipatterns.md` shows the common ways a clean compile still tells the reader
|
||||||
|
something false.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Default to editable TikZ. `scripts/build.sh` emits PDF, SVG, white-background PNG,
|
||||||
|
transparent PNG and a 360 px thumbnail. Deliver the PNG preview, a one-paragraph mechanism
|
||||||
|
explanation, and the `.tex` source plus vector artifact.
|
||||||
|
|
||||||
|
- **Chinese figures:** `\usepackage[cjk]{superfig}` (XeLaTeX + portable Fandol). Keep
|
||||||
|
standard English terms where natural (`softmax`, `logits`, `gather`).
|
||||||
|
- **English figures:** `\usepackage[en]{superfig}` — same geometry, English meaning-box rails.
|
||||||
|
- Keep math in LaTeX, not raw Unicode.
|
||||||
|
- Add `\sfsignature{<subject>}{<box>}` only when the user or house template asks for it.
|
||||||
|
|
||||||
|
## Iterating
|
||||||
|
|
||||||
|
When the user asks for a change, do not restart the figure. Edit the role/ledger or the one
|
||||||
|
macro call that owns the offending object, rebuild, and re-audit. If a fix requires shrinking
|
||||||
|
type, closing the gutter, or covering another object, move the stage to another row instead.
|
||||||
|
Ask before dropping an object or edge the user named.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Superfig"
|
||||||
|
short_description: "Create clean paper-style architecture and flow figures"
|
||||||
|
default_prompt: "Use $superfig to turn this paper idea into an editable figure with nodes, edges and a meaning box."
|
||||||
@@ -0,0 +1,385 @@
|
|||||||
|
%% superfig.sty -- general paper-figure toolkit
|
||||||
|
%% A smaller sibling of supertensor.sty. It keeps the house style, role/color
|
||||||
|
%% discipline, cursor layout, build-time warning discipline and audit mindset,
|
||||||
|
%% but its primitives are generic nodes and edges, not tensor faces.
|
||||||
|
%%
|
||||||
|
%% Options: cjk load ctex with the portable fandol fontset (XeLaTeX)
|
||||||
|
%% en English rail labels in the meaning box (default: zh)
|
||||||
|
\NeedsTeXFormat{LaTeX2e}
|
||||||
|
\ProvidesPackage{superfig}[2026/08/16 v1.0 general paper figure toolkit]
|
||||||
|
|
||||||
|
\newif\iffig@cjk\fig@cjkfalse
|
||||||
|
\newif\iffig@en\fig@enfalse
|
||||||
|
\DeclareOption{cjk}{\fig@cjktrue}
|
||||||
|
\DeclareOption{en}{\fig@entrue}
|
||||||
|
\DeclareOption{zh}{\fig@enfalse}
|
||||||
|
\ProcessOptions\relax
|
||||||
|
|
||||||
|
\RequirePackage{amsmath}
|
||||||
|
\RequirePackage{amssymb}
|
||||||
|
\RequirePackage{xcolor}
|
||||||
|
\RequirePackage{etoolbox}
|
||||||
|
\RequirePackage{array}
|
||||||
|
\RequirePackage{tikz}
|
||||||
|
\usetikzlibrary{calc,positioning,arrows.meta,backgrounds,fit,decorations.pathreplacing}
|
||||||
|
|
||||||
|
\iffig@cjk
|
||||||
|
\RequirePackage[UTF8,fontset=fandol]{ctex}
|
||||||
|
\fi
|
||||||
|
|
||||||
|
% ---------------------------------------------------------------- layers ----
|
||||||
|
\pgfdeclarelayer{sfbg}
|
||||||
|
\pgfdeclarelayer{sffg}
|
||||||
|
\pgfsetlayers{sfbg,main,sffg}
|
||||||
|
|
||||||
|
% ---------------------------------------------------------------- palette ---
|
||||||
|
\definecolor{sfTeal}{HTML}{4F8FA5}
|
||||||
|
\definecolor{sfOrange}{HTML}{EE995B}
|
||||||
|
\definecolor{sfCoral}{HTML}{C95B5B}
|
||||||
|
\definecolor{sfViolet}{HTML}{8A74B5}
|
||||||
|
\definecolor{sfGray}{HTML}{85898F}
|
||||||
|
\definecolor{sfInk}{HTML}{1A1A1A}
|
||||||
|
|
||||||
|
% Role registry: drawing macros take a ROLE, never a color.
|
||||||
|
\newcommand{\sfsetrole}[2]{%
|
||||||
|
\edef\sf@newrole{#2}%
|
||||||
|
\ifcsname sf@role@#1\endcsname
|
||||||
|
\edef\sf@oldrole{\csname sf@role@#1\endcsname}%
|
||||||
|
\ifx\sf@oldrole\sf@newrole\else
|
||||||
|
\PackageWarning{superfig}{Role `#1' was already mapped to
|
||||||
|
`\sf@oldrole' and cannot be remapped to `\sf@newrole'}%
|
||||||
|
\fi
|
||||||
|
\else
|
||||||
|
\expandafter\gdef\csname sf@role@#1\endcsname{#2}%
|
||||||
|
\fi}
|
||||||
|
\newcommand{\sfrole}[1]{%
|
||||||
|
\ifcsname sf@role@#1\endcsname\csname sf@role@#1\endcsname\else sfGray\fi}
|
||||||
|
\newcommand{\sfcheckrole}[1]{%
|
||||||
|
\ifcsname sf@role@#1\endcsname\else
|
||||||
|
\PackageWarning{superfig}{Undeclared role `#1' -- drawn in neutral gray.
|
||||||
|
Declare it with \string\sfsetrole\space so the hue budget stays visible}%
|
||||||
|
\fi}
|
||||||
|
\sfsetrole{neutral}{sfGray}
|
||||||
|
|
||||||
|
\newcommand{\sflevelpct}[1]{\ifcase#1 0\or30\or55\or80\else55\fi}
|
||||||
|
|
||||||
|
% --------------------------------------------------------- type hierarchy ---
|
||||||
|
\tikzset{
|
||||||
|
sf sym/.style = {font=\small, text=sfInk, inner sep=1pt},
|
||||||
|
sf shape/.style = {font=\scriptsize, text=black!55, inner sep=1pt},
|
||||||
|
sf stage/.style = {font=\small\bfseries, text=black!55, inner sep=2pt},
|
||||||
|
sf op/.style = {font=\Large, text=sfInk, inner sep=2pt},
|
||||||
|
sf note/.style = {font=\scriptsize, text=black!55, inner sep=2pt},
|
||||||
|
sf arrow/.style = {-{Stealth[length=2.2mm,width=1.6mm]}, draw=black!45, line width=0.5pt},
|
||||||
|
sf box/.style = {draw=black!60, line width=0.5pt, rounded corners=1.5pt,
|
||||||
|
inner sep=5pt, font=\small, align=center, text=sfInk},
|
||||||
|
}
|
||||||
|
|
||||||
|
% --------------------------------------------------------- flow layout ------
|
||||||
|
\newlength{\sfgutter}\setlength{\sfgutter}{6mm}
|
||||||
|
\newlength{\sfrowgap}\setlength{\sfrowgap}{3.5mm}
|
||||||
|
\newlength{\sfblockgap}\setlength{\sfblockgap}{9mm}
|
||||||
|
\newlength{\sflinklen}\setlength{\sflinklen}{10mm}
|
||||||
|
\newlength{\sf@railx}\newlength{\sf@cx}\newlength{\sf@cy}
|
||||||
|
\newlength{\sf@ycur}\newlength{\sf@bandh}\newlength{\sf@tmpx}\newlength{\sf@tmpy}
|
||||||
|
\newif\iffig@inrow
|
||||||
|
\newif\iffig@first
|
||||||
|
\newif\iffig@bracket
|
||||||
|
\newcount\sf@nmem
|
||||||
|
\newcount\sf@ncallouts
|
||||||
|
|
||||||
|
\newcommand{\sflayoutreset}{%
|
||||||
|
\global\sf@railx=0pt \global\sf@ycur=0pt
|
||||||
|
\global\sf@cx=0pt \global\sf@cy=0pt \global\sf@bandh=0pt
|
||||||
|
\global\fig@inrowfalse
|
||||||
|
\global\sf@ncallouts=0
|
||||||
|
\gdef\sf@rowlist{}\gdef\sf@alllist{}\gdef\sf@rowname{}%
|
||||||
|
\gdef\sf@lastnode{}\gdef\sf@linklabel{}\gdef\sf@linkprev{}}
|
||||||
|
\sflayoutreset
|
||||||
|
\newcommand{\sfleftrail}[1]{\global\sf@railx=\dimexpr#1\relax}
|
||||||
|
\newcommand{\sfvgap}[1]{\global\advance\sf@ycur by -\dimexpr#1\relax}
|
||||||
|
|
||||||
|
\newcommand{\sf@lower}[1]{%
|
||||||
|
\pgfextracty{\sf@tmpy}{\pgfpointanchor{#1}{south}}%
|
||||||
|
\ifdim\sf@tmpy<\sf@ycur \global\sf@ycur=\sf@tmpy \fi}
|
||||||
|
\newcommand{\sf@regall}[1]{\xdef\sf@alllist{\sf@alllist(#1)}}
|
||||||
|
\newcommand{\sf@regrow}[1]{%
|
||||||
|
\xdef\sf@rowlist{\sf@rowlist(#1)}\sf@regall{#1}%
|
||||||
|
\sf@drawpendinglink{#1}%
|
||||||
|
\gdef\sf@lastnode{#1}}
|
||||||
|
\newcommand{\sf@needrow}[1]{%
|
||||||
|
\iffig@inrow\else
|
||||||
|
\PackageError{superfig}{\string#1\space needs an open \string\sfrow}%
|
||||||
|
{Flow placement only works between \string\sfrow\space and
|
||||||
|
\string\sfrowend. Pass at={<coord>} instead.}%
|
||||||
|
\fi}
|
||||||
|
\newcommand{\sf@leadgap}[1]{%
|
||||||
|
\iffig@first
|
||||||
|
\global\fig@firstfalse
|
||||||
|
\else
|
||||||
|
\global\advance\sf@cx by \dimexpr#1\relax
|
||||||
|
\fi}
|
||||||
|
\newcommand{\sf@flow}[2]{%
|
||||||
|
\dimen0=\dimexpr#1\relax
|
||||||
|
\dimen2=\sf@cx \advance\dimen2 by 0.5\dimen0
|
||||||
|
\edef\sf@pos{(\the\dimen2,\the\sf@cy)}%
|
||||||
|
\global\advance\sf@cx by \dimen0}
|
||||||
|
\newcommand{\sf@checkh}[2]{%
|
||||||
|
\dimen0=\dimexpr#2\relax \advance\dimen0 by -\sf@bandh
|
||||||
|
\ifdim\dimen0>4mm
|
||||||
|
\PackageWarning{superfig}{Object `#1' overflows its band by
|
||||||
|
\the\dimen0. Raise the height declared in \string\sfrow\space or give it
|
||||||
|
its own band}%
|
||||||
|
\fi}
|
||||||
|
|
||||||
|
\newcommand{\sfstage}[2]{%
|
||||||
|
\dimen0=\sf@ycur \advance\dimen0 by -\sfblockgap
|
||||||
|
\node[sf stage, anchor=north west] (#1) at (\the\sf@railx,\the\dimen0) {#2};
|
||||||
|
\sf@regall{#1}\sf@lower{#1}}
|
||||||
|
|
||||||
|
\newcommand{\sfrow}[2]{%
|
||||||
|
\gdef\sf@rowlist{}\gdef\sf@rowname{#1}\gdef\sf@lastnode{}%
|
||||||
|
\gdef\sf@linklabel{}\gdef\sf@linkprev{}%
|
||||||
|
\expandafter\gdef\csname sf@isrow@#1\endcsname{1}%
|
||||||
|
\global\fig@inrowtrue\global\fig@firsttrue
|
||||||
|
\pgfmathsetlengthmacro{\sf@bh}{#2}%
|
||||||
|
\global\sf@bandh=\sf@bh
|
||||||
|
\global\sf@cx=\sf@railx
|
||||||
|
\dimen0=\sf@ycur \advance\dimen0 by -\sfrowgap \advance\dimen0 by -0.5\sf@bandh
|
||||||
|
\global\sf@cy=\dimen0}
|
||||||
|
|
||||||
|
\newcommand{\sfrowend}{%
|
||||||
|
\ifdefempty{\sf@rowlist}%
|
||||||
|
{\PackageWarning{superfig}{Empty \string\sfrow\space `\sf@rowname'}}%
|
||||||
|
{\edef\sf@do{\noexpand\node[inner sep=0pt, outer sep=0pt,
|
||||||
|
fit={\sf@rowlist}] (\sf@rowname) {};}\sf@do
|
||||||
|
\sf@regall{\sf@rowname}\sf@lower{\sf@rowname}}%
|
||||||
|
\global\fig@inrowfalse}
|
||||||
|
|
||||||
|
% --------------------------------------------------------------- nodes ------
|
||||||
|
\pgfkeys{
|
||||||
|
/sf/node/.cd,
|
||||||
|
role/.store in=\sf@role,
|
||||||
|
level/.store in=\sf@level,
|
||||||
|
gap/.store in=\sf@gap,
|
||||||
|
bracket/.is if=fig@bracket,
|
||||||
|
at/.store in=\sf@at,
|
||||||
|
role=neutral, level=2, gap=\sfgutter, bracket=false, at={},
|
||||||
|
}
|
||||||
|
|
||||||
|
% \sfnode[keys]{name}{label}{width}{height}
|
||||||
|
% A semantic object: one node, one fill, one role. In an open \sfrow an empty
|
||||||
|
% at places it at the cursor; at={<coord>} bypasses the cursor for branchy
|
||||||
|
% architecture figures. Width/height are real lengths or unit expressions.
|
||||||
|
\newcommand{\sfnode}[5][]{%
|
||||||
|
\begingroup
|
||||||
|
\pgfkeys{/sf/node/.cd,#1}%
|
||||||
|
\pgfmathsetlengthmacro{\sf@w}{#4}%
|
||||||
|
\pgfmathsetlengthmacro{\sf@h}{#5}%
|
||||||
|
\sfcheckrole{\sf@role}%
|
||||||
|
\edef\sf@col{\sfrole{\sf@role}}%
|
||||||
|
\edef\sf@fill{\sf@col!\sflevelpct{\sf@level}}%
|
||||||
|
\ifdefempty{\sf@at}{%
|
||||||
|
\sf@needrow{\sfnode}%
|
||||||
|
\sf@checkh{#2}{#5}%
|
||||||
|
\sf@leadgap{\sf@gap}%
|
||||||
|
\iffig@bracket\pgfmathsetlengthmacro{\sf@tw}{\sf@w+6.8mm}\else\let\sf@tw\sf@w\fi
|
||||||
|
\sf@flow{\sf@tw}{\sf@h}%
|
||||||
|
}{%
|
||||||
|
\edef\sf@pos{\sf@at}%
|
||||||
|
}%
|
||||||
|
\expandafter\node[sf box, fill=\sf@fill, minimum width=\sf@w,
|
||||||
|
minimum height=\sf@h, at={\sf@pos}] (#2) {#3};%
|
||||||
|
\iffig@bracket
|
||||||
|
\draw[black!55, line width=0.5pt]
|
||||||
|
($(#2.north west)+(-1.1mm,0.6mm)$) -- ++(-1.1mm,0)
|
||||||
|
-- ($(#2.south west)+(-2.2mm,-0.6mm)$) -- ++(1.1mm,0);
|
||||||
|
\draw[black!55, line width=0.5pt]
|
||||||
|
($(#2.north east)+(1.1mm,0.6mm)$) -- ++(1.1mm,0)
|
||||||
|
-- ($(#2.south east)+(2.2mm,-0.6mm)$) -- ++(-1.1mm,0);
|
||||||
|
\fi
|
||||||
|
\ifdefempty{\sf@at}{%
|
||||||
|
\sf@regrow{#2}%
|
||||||
|
}{%
|
||||||
|
\sf@regall{#2}\sf@lower{#2}%
|
||||||
|
}%
|
||||||
|
\endgroup}
|
||||||
|
|
||||||
|
% Operator or small annotation in a flow row.
|
||||||
|
\newcommand{\sfop}[2]{%
|
||||||
|
\iffig@inrow\else
|
||||||
|
\PackageError{superfig}{\string\sfop\space needs an open \string\sfrow}%
|
||||||
|
{Operators are flow objects. Use \string\sfarrow\space for a fixed edge.}%
|
||||||
|
\fi
|
||||||
|
\sf@leadgap{\sfgutter}%
|
||||||
|
\node[sf op, anchor=west] (#1) at (\the\sf@cx,\the\sf@cy) {#2};%
|
||||||
|
\pgfextractx{\sf@tmpx}{\pgfpointanchor{#1}{east}}%
|
||||||
|
\global\sf@cx=\sf@tmpx
|
||||||
|
\sf@regrow{#1}}
|
||||||
|
|
||||||
|
\newcommand{\sfgap}[1]{\global\advance\sf@cx by \dimexpr#1\relax}
|
||||||
|
|
||||||
|
% ------------------------------------------------------------ connectors ----
|
||||||
|
\newcommand{\sf@drawpendinglink}[1]{%
|
||||||
|
\ifdefempty{\sf@linkprev}{}{%
|
||||||
|
\begin{pgfonlayer}{sfbg}
|
||||||
|
\ifdefempty{\sf@linklabel}%
|
||||||
|
{\draw[sf arrow] (\sf@linkprev.east) -- (#1.west);}%
|
||||||
|
{\draw[sf arrow] (\sf@linkprev.east) -- (\sf@linklabel.west);
|
||||||
|
\draw[sf arrow] (\sf@linklabel.east) -- (#1.west);}%
|
||||||
|
\end{pgfonlayer}
|
||||||
|
\gdef\sf@linkprev{}\gdef\sf@linklabel{}}}
|
||||||
|
|
||||||
|
% \sfconn{name}{label} -- horizontal edge through the cursor. The label reserves
|
||||||
|
% its own width, so it cannot be wider than the edge it labels.
|
||||||
|
\newcommand{\sfconn}[2]{%
|
||||||
|
\sf@needrow{\sfconn}%
|
||||||
|
\xdef\sf@linkprev{\sf@lastnode}%
|
||||||
|
\ifblank{#2}%
|
||||||
|
{\gdef\sf@linklabel{}\sfgap{\sflinklen}}%
|
||||||
|
{\sf@leadgap{\sfgutter}%
|
||||||
|
\node[sf note, anchor=west, inner sep=1pt] (#1) at (\the\sf@cx,\the\sf@cy) {#2};
|
||||||
|
\pgfextractx{\sf@tmpx}{\pgfpointanchor{#1}{east}}%
|
||||||
|
\global\sf@cx=\sf@tmpx
|
||||||
|
\gdef\sf@linklabel{#1}\sf@regall{#1}}}
|
||||||
|
|
||||||
|
% Fixed directed edge between two already-placed objects.
|
||||||
|
\newcommand{\sfarrow}[3][]{%
|
||||||
|
\begin{pgfonlayer}{sfbg}
|
||||||
|
\draw[sf arrow] (#2) to[#1] (#3);
|
||||||
|
\end{pgfonlayer}}
|
||||||
|
\newcommand{\sfarrowlabel}[4][]{%
|
||||||
|
\begin{pgfonlayer}{sfbg}
|
||||||
|
\draw[sf arrow] (#2) to[#1] node[sf note, auto, fill=white, inner sep=1pt] {#4} (#3);
|
||||||
|
\end{pgfonlayer}}
|
||||||
|
|
||||||
|
% ----------------------------------------------------------- bounding box ---
|
||||||
|
\newcommand{\sfbbox}[1]{%
|
||||||
|
\edef\sf@do{\noexpand\node[inner sep=0pt, outer sep=0pt,
|
||||||
|
fit={\sf@alllist}] (#1) {};}\sf@do}
|
||||||
|
|
||||||
|
\newcommand{\sftrack}[1]{\sf@regall{#1}\sf@lower{#1}}
|
||||||
|
|
||||||
|
\newcommand{\sftopformula}[2]{%
|
||||||
|
\sfbbox{sf@bbt}%
|
||||||
|
\node[above=6mm of sf@bbt, anchor=south] (#1) {#2};
|
||||||
|
\sf@regall{#1}}
|
||||||
|
|
||||||
|
% -------------------------------------------------------------- captions ----
|
||||||
|
\def\sf@lane{}
|
||||||
|
\newcommand{\sflane}[1]{\def\sf@lane{#1}}
|
||||||
|
\newcommand{\sfnolane}{\def\sf@lane{}}
|
||||||
|
\newcommand{\sfcaption}[3]{%
|
||||||
|
\ifdefempty{\sf@lane}%
|
||||||
|
{\node[sf sym, below=1.6mm of #1] (#1-sym) {#2};}%
|
||||||
|
{\node[sf sym, anchor=north]
|
||||||
|
at ($(#1.center |- \sf@lane.south)+(0,-1.6mm)$) (#1-sym) {#2};}%
|
||||||
|
\node[sf shape, below=0.6mm of #1-sym] (#1-shape) {#3};%
|
||||||
|
\sf@regall{#1-shape}\sf@lower{#1-shape}}
|
||||||
|
\newcommand{\sfcaptiontop}[2]{%
|
||||||
|
\node[sf sym, above=1.6mm of #1] (#1-top) {#2};%
|
||||||
|
\sf@regall{#1-top}}
|
||||||
|
|
||||||
|
% --------------------------------------------------------------- grouping ---
|
||||||
|
\pgfkeys{
|
||||||
|
/sf/group/.cd,
|
||||||
|
role/.store in=\sf@role,
|
||||||
|
role=neutral,
|
||||||
|
}
|
||||||
|
|
||||||
|
% Count an explicit fit list like {(a)(b)}. A trailing empty () is the sentinel.
|
||||||
|
\def\sf@countmembers#1{\sf@nmem=0 \sf@eatmem#1()\relax}
|
||||||
|
\def\sf@eatmem(#1)#2\relax{%
|
||||||
|
\if\relax\detokenize{#1}\relax
|
||||||
|
\else
|
||||||
|
\advance\sf@nmem by 1
|
||||||
|
\sf@eatmem#2\relax
|
||||||
|
\fi}
|
||||||
|
|
||||||
|
% \sfgroup[role=...]{name}{(member1)(member2)}{caption}
|
||||||
|
% The fit list is explicit so the group can wrap any placed nodes. The outline
|
||||||
|
% names one composite object, never decoration. An overlay caption is optional;
|
||||||
|
% prefer \sfcaption{name}{...}{...} so the label sits on the caption lane.
|
||||||
|
\newcommand{\sfgroup}[4][]{%
|
||||||
|
\begingroup
|
||||||
|
\pgfkeys{/sf/group/.cd,#1}%
|
||||||
|
\sfcheckrole{\sf@role}%
|
||||||
|
\sf@countmembers{#3}%
|
||||||
|
\ifnum\sf@nmem<2
|
||||||
|
\PackageWarning{superfig}{Group `#2' wraps a single object.
|
||||||
|
Bind at least two members; an outline around one node is decoration}%
|
||||||
|
\fi
|
||||||
|
\edef\sf@col{\sfrole{\sf@role}}%
|
||||||
|
\node[draw=\sf@col!65, line width=0.6pt, rounded corners=3pt,
|
||||||
|
inner sep=6pt, fit={#3}] (#2) {};%
|
||||||
|
\ifblank{#4}{}{%
|
||||||
|
\node[anchor=north west, font=\scriptsize\bfseries,
|
||||||
|
text=\sf@col!85!black, inner sep=1pt]
|
||||||
|
at ([yshift=2pt]#2.north west) {#4};}%
|
||||||
|
\sf@regall{#2}\sf@lower{#2}%
|
||||||
|
% Expand the last row's fit so \sflane{row} hangs captions below the outline.
|
||||||
|
\ifdefempty{\sf@rowname}{}{%
|
||||||
|
\edef\sf@do{\noexpand\node[inner sep=0pt, outer sep=0pt,
|
||||||
|
fit={(\sf@rowname)(#2)}] (\sf@rowname) {};}\sf@do
|
||||||
|
\sf@lower{\sf@rowname}}%
|
||||||
|
\endgroup}
|
||||||
|
|
||||||
|
% --------------------------------------------------------------- callout ---
|
||||||
|
\newcommand{\sfcallout}[5]{%
|
||||||
|
\iffig@inrow
|
||||||
|
\PackageError{superfig}{\string\sfcallout\space inside an open
|
||||||
|
\string\sfrow}%
|
||||||
|
{A callout is an aside, not an object. Close the band first.}%
|
||||||
|
\fi
|
||||||
|
\ifcsname sf@isrow@#3\endcsname\else
|
||||||
|
\PackageWarning{superfig}{Callout `#1' is anchored to `#3', which is not a
|
||||||
|
\string\sfrow\space band. A card hanging off a single object reads as a step}%
|
||||||
|
\fi
|
||||||
|
\global\advance\sf@ncallouts by 1
|
||||||
|
\ifnum\sf@ncallouts>1
|
||||||
|
\PackageWarning{superfig}{Callout `#1' is the second card in the figure.
|
||||||
|
The budget is one callout per figure}%
|
||||||
|
\fi
|
||||||
|
\node[anchor=north west, draw=black!18, fill=black!3, rounded corners=1.5pt,
|
||||||
|
inner xsep=7pt, inner ysep=6pt, text width=#2] (#1)
|
||||||
|
at ([xshift=\sfgutter]#3.north east) {%
|
||||||
|
\scriptsize\raggedright
|
||||||
|
\ifblank{#4}{}{\textbf{#4}\par\vspace{1.5pt}}%
|
||||||
|
#5\par};
|
||||||
|
\sftrack{#1}}
|
||||||
|
|
||||||
|
% ------------------------------------------------------------ meaning box ---
|
||||||
|
\iffig@en
|
||||||
|
\def\sf@lblaxes{Concept}\def\sf@lblobj{Objects}\def\sf@lblmech{Mechanism}
|
||||||
|
\else
|
||||||
|
\def\sf@lblaxes{概念}\def\sf@lblobj{对象}\def\sf@lblmech{机制}
|
||||||
|
\fi
|
||||||
|
\newcommand{\sfsetlabels}[3]{\def\sf@lblaxes{#1}\def\sf@lblobj{#2}\def\sf@lblmech{#3}}
|
||||||
|
\newlength{\sf@raillen}
|
||||||
|
\iffig@en
|
||||||
|
\setlength{\sf@raillen}{5.4em}
|
||||||
|
\else
|
||||||
|
\setlength{\sf@raillen}{3.2em}
|
||||||
|
\fi
|
||||||
|
\newcommand{\sfsetrail}[1]{\sf@raillen=\dimexpr#1\relax}
|
||||||
|
|
||||||
|
% \sfmeaningbox{name}{total width}{anchor}{idea}{objects}{mechanism}
|
||||||
|
% One low-contrast reading box. Pass {} to drop a row.
|
||||||
|
\newcommand{\sfmeaningbox}[6]{%
|
||||||
|
\node[below=4mm of #3, anchor=north,
|
||||||
|
draw=black!18, fill=black!3, rounded corners=1.5pt,
|
||||||
|
inner xsep=9pt, inner ysep=8pt, text width=#2] (#1) {%
|
||||||
|
\small
|
||||||
|
\setlength{\tabcolsep}{0pt}%
|
||||||
|
\renewcommand{\arraystretch}{1.25}%
|
||||||
|
\begin{tabular}{@{}p{\sf@raillen}@{\hspace{7pt}}p{\dimexpr#2-\sf@raillen-7pt\relax}@{}}
|
||||||
|
\ifblank{#4}{}{\textbf{\sf@lblaxes} & \raggedright\arraybackslash #4 \\}
|
||||||
|
\ifblank{#5}{}{\textbf{\sf@lblobj} & \raggedright\arraybackslash #5 \\}
|
||||||
|
\ifblank{#6}{}{\textbf{\sf@lblmech} & \raggedright\arraybackslash #6 \\}
|
||||||
|
\end{tabular}};}
|
||||||
|
|
||||||
|
% ------------------------------------------------------------- signature ----
|
||||||
|
\newcommand{\sfsignature}[2]{%
|
||||||
|
\node[below=2.2mm of #2, font=\scriptsize, text=black!45] (#2-sig) {#1};}
|
||||||
|
|
||||||
|
\endinput
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
% Anti-pattern gallery -- four figures that compile cleanly and still teach
|
||||||
|
% the reader something false. Each pair is wrong / right.
|
||||||
|
% ../scripts/build.sh antipatterns.tex
|
||||||
|
\documentclass[border=10pt]{standalone}
|
||||||
|
\usepackage[cjk]{superfig}
|
||||||
|
|
||||||
|
\sfsetrole{a}{sfTeal}
|
||||||
|
\sfsetrole{b}{sfOrange}
|
||||||
|
\sfsetrole{c}{sfCoral}
|
||||||
|
\sfsetrole{d}{sfViolet}
|
||||||
|
|
||||||
|
\newcommand{\bad}[1]{{\color{sfCoral}$\times$}\;#1}
|
||||||
|
\newcommand{\good}[1]{{\color{sfTeal}$\checkmark$}\;#1}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\begin{tikzpicture}
|
||||||
|
|
||||||
|
% (1) A decorative arrow vs a data-flow edge.
|
||||||
|
\sfnode[role=a, at={(0,0)}]{A1}{编码器}{16mm}{10mm}
|
||||||
|
\sfnode[role=b, at={(32mm,0)}]{A2}{指标卡}{16mm}{10mm}
|
||||||
|
\sfarrow{A1}{A2}
|
||||||
|
\sfnode[role=a, at={(64mm,0)}]{A3}{编码器}{16mm}{10mm}
|
||||||
|
\sfnode[role=b, at={(96mm,0)}]{A4}{解码器}{16mm}{10mm}
|
||||||
|
\sfarrowlabel{A3.east}{A4.west}{隐状态}
|
||||||
|
|
||||||
|
\node[sf stage, anchor=south west] at ($(A1.north west)+(0,6mm)$)
|
||||||
|
{(1) 装饰箭头};
|
||||||
|
\node[sf shape, anchor=north] (A1c) at ($(A1.south)!0.5!(A2.south)+(0,-3mm)$)
|
||||||
|
{\bad{箭头不承载关系}};
|
||||||
|
\node[sf shape, anchor=north] (A3c) at ($(A3.south)!0.5!(A4.south)+(0,-3mm)$)
|
||||||
|
{\good{隐状态从编码流向解码}};
|
||||||
|
|
||||||
|
% (2) A group that is only a visual border vs a real composite.
|
||||||
|
\sfnode[role=a, at={(0,-32mm)}]{B1}{输入}{14mm}{10mm}
|
||||||
|
\sfnode[role=d, at={(24mm,-32mm)}]{B2}{输出}{14mm}{10mm}
|
||||||
|
\sfgroup[role=a]{Bg}{(B1)(B2)}{}
|
||||||
|
\sfnode[role=b, at={(56mm,-32mm)}]{B3}{注意力}{16mm}{10mm}
|
||||||
|
\sfnode[role=b, at={(82mm,-32mm)}]{B4}{前馈}{14mm}{10mm}
|
||||||
|
\sfgroup[role=b]{Bb}{(B3)(B4)}{}
|
||||||
|
\sfarrow{B3.east}{B4.west}
|
||||||
|
|
||||||
|
\node[sf stage, anchor=south west] at ($(B1.north west)+(0,8mm)$)
|
||||||
|
{(2) 组框只是装饰边};
|
||||||
|
\node[sf shape, anchor=north] (B1c) at ($(B1.south)!0.5!(B2.south)+(0,-7mm)$)
|
||||||
|
{\bad{首尾框在一起,不是一个模块}};
|
||||||
|
\node[sf shape, anchor=north] (B3c) at ($(B3.south)!0.5!(B4.south)+(0,-7mm)$)
|
||||||
|
{\good{主路模块是真实复合对象}};
|
||||||
|
|
||||||
|
% (3) One node doing two jobs vs a split.
|
||||||
|
\sfnode[role=b, at={(0,-68mm)}]{C1}{注意力 + LN + 残差}{38mm}{12mm}
|
||||||
|
\sfnode[role=b, at={(56mm,-68mm)}]{C2}{注意力}{16mm}{10mm}
|
||||||
|
\sfnode[role=c, at={(84mm,-68mm)}]{C3}{残差加}{16mm}{10mm}
|
||||||
|
\sfarrow{C2.east}{C3.west}
|
||||||
|
|
||||||
|
\node[sf stage, anchor=south west] at ($(C1.north west)+(0,6mm)$)
|
||||||
|
{(3) 一个节点做两件事};
|
||||||
|
\node[sf shape, anchor=north] (C1c) at ($(C1.south)+(0,-3mm)$)
|
||||||
|
{\bad{机制被一口吞掉}};
|
||||||
|
\node[sf shape, anchor=north] (C2c) at ($(C2.south)!0.5!(C3.south)+(0,-3mm)$)
|
||||||
|
{\good{一步一个对象}};
|
||||||
|
|
||||||
|
% (4) A new hue for the same object vs one role, two levels.
|
||||||
|
\sfnode[role=a, level=2, at={(0,-100mm)}]{D1}{$x$}{14mm}{10mm}
|
||||||
|
\sfnode[role=c, level=2, at={(24mm,-100mm)}]{D2}{$x$ 归一化}{20mm}{10mm}
|
||||||
|
\sfnode[role=a, level=1, at={(64mm,-100mm)}]{D3}{$x$}{14mm}{10mm}
|
||||||
|
\sfnode[role=a, level=3, at={(90mm,-100mm)}]{D4}{$x$ 归一化}{20mm}{10mm}
|
||||||
|
|
||||||
|
\node[sf stage, anchor=south west] at ($(D1.north west)+(0,6mm)$)
|
||||||
|
{(4) 同一对象换了色相};
|
||||||
|
\node[sf shape, anchor=north] (D1c) at ($(D1.south)!0.5!(D2.south)+(0,-3mm)$)
|
||||||
|
{\bad{换色相像换了对象}};
|
||||||
|
\node[sf shape, anchor=north] (D3c) at ($(D3.south)!0.5!(D4.south)+(0,-3mm)$)
|
||||||
|
{\good{同一角色,深浅分层}};
|
||||||
|
|
||||||
|
\node[inner sep=0pt, fit=(A1)(A4)(D1)(D4)(A1c)(D3c)(Bg)(Bb)] (all) {};
|
||||||
|
\sfmeaningbox{mb}{118mm}{all}
|
||||||
|
{}
|
||||||
|
{四张错图都能干净编译。编译器不检查图讲的事情对不对}
|
||||||
|
{交付前按 \texttt{references/checklist.md} 做一次人眼审图}
|
||||||
|
|
||||||
|
\end{tikzpicture}
|
||||||
|
\end{document}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
% superfig golden example 2 -- a small branchy architecture, not a tensor
|
||||||
|
% shape diagram. Main path uses the cursor; the residual is a skip edge;
|
||||||
|
% the group is captioned on the shared lane, not as an overlay.
|
||||||
|
% ../scripts/build.sh branch-architecture.tex
|
||||||
|
\documentclass[border=10pt]{standalone}
|
||||||
|
\usepackage[cjk]{superfig}
|
||||||
|
|
||||||
|
\sfsetrole{enc}{sfTeal}
|
||||||
|
\sfsetrole{attn}{sfOrange}
|
||||||
|
\sfsetrole{ffn}{sfViolet}
|
||||||
|
\sfsetrole{out}{sfCoral}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\begin{tikzpicture}
|
||||||
|
|
||||||
|
\sfstage{S}{双分支结构:主路 + 残差}
|
||||||
|
\sfvgap{7mm}
|
||||||
|
\sfrow{R1}{12mm}
|
||||||
|
\sfnode[role=enc]{in}{输入}{16mm}{10mm}
|
||||||
|
\sfconn{e1}{}
|
||||||
|
\sfnode[role=attn]{attn}{注意力}{18mm}{10mm}
|
||||||
|
\sfconn{e2}{特征}
|
||||||
|
\sfnode[role=ffn]{ffn}{前馈}{18mm}{10mm}
|
||||||
|
\sfgap{\sflinklen}
|
||||||
|
\sfnode[role=enc]{add}{融合}{14mm}{10mm}
|
||||||
|
\sfconn{e4}{}
|
||||||
|
\sfnode[role=out]{out}{输出}{14mm}{10mm}
|
||||||
|
\sfrowend
|
||||||
|
|
||||||
|
% No overlay caption: it would sit on the residual. Caption the group below.
|
||||||
|
\sfgroup[role=attn]{block}{(attn)(ffn)}{}
|
||||||
|
\sfarrow{block.east}{add.west}
|
||||||
|
\sfarrowlabel[bend left=18]{in.north}{add.north}{残差}
|
||||||
|
|
||||||
|
\sflane{R1}
|
||||||
|
\sfcaption{in}{输入}{源数据}
|
||||||
|
\sfcaption{block}{主路模块}{注意力 + 前馈}
|
||||||
|
\sfcaption{add}{融合}{残差相加}
|
||||||
|
\sfcaption{out}{输出}{目标表示}
|
||||||
|
|
||||||
|
\sfbbox{all}
|
||||||
|
\sfmeaningbox{mb}{108mm}{all}
|
||||||
|
{主路逐层处理,残差边跳过主路}
|
||||||
|
{输入、主路模块、融合节点、输出}
|
||||||
|
{残差把输入直接送入融合,减轻深层优化负担}
|
||||||
|
|
||||||
|
\end{tikzpicture}
|
||||||
|
\end{document}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
% superfig golden example 4 -- a dual-encoder dependency, not a linear pipeline.
|
||||||
|
% Two encoder rows share a rail; fusion is placed from their east anchors.
|
||||||
|
% ../scripts/build.sh dependency-graph.tex
|
||||||
|
\documentclass[border=10pt]{standalone}
|
||||||
|
\usepackage[cjk]{superfig}
|
||||||
|
|
||||||
|
\sfsetrole{text}{sfTeal}
|
||||||
|
\sfsetrole{img}{sfOrange}
|
||||||
|
\sfsetrole{fuse}{sfViolet}
|
||||||
|
\sfsetrole{out}{sfCoral}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\begin{tikzpicture}
|
||||||
|
|
||||||
|
\sfstage{S1}{双塔编码}
|
||||||
|
\sfrow{R1}{12mm}
|
||||||
|
\sfnode[role=text]{txt}{文本}{14mm}{10mm}
|
||||||
|
\sfconn{e1}{}
|
||||||
|
\sfnode[role=text]{te}{文本编码}{18mm}{10mm}
|
||||||
|
\sfrowend
|
||||||
|
\sflane{R1}
|
||||||
|
\sfcaption{txt}{text}{token 序列}
|
||||||
|
\sfcaption{te}{$E_t$}{文本向量}
|
||||||
|
|
||||||
|
\sfrow{R2}{12mm}
|
||||||
|
\sfnode[role=img]{img}{图像}{14mm}{10mm}
|
||||||
|
\sfconn{e2}{}
|
||||||
|
\sfnode[role=img]{ve}{视觉编码}{18mm}{10mm}
|
||||||
|
\sfrowend
|
||||||
|
\sflane{R2}
|
||||||
|
\sfcaption{img}{image}{视觉输入}
|
||||||
|
\sfcaption{ve}{$E_v$}{视觉向量}
|
||||||
|
|
||||||
|
% Fusion sits to the right of the two encoders, on their shared east line.
|
||||||
|
\sfnode[role=fuse, at={($(te.east)!0.5!(ve.east)+(20mm,0)$)}]{fu}{融合}{16mm}{10mm}
|
||||||
|
\sfnode[role=out, at={($(fu.east)+(16mm,0)$)}]{dec}{解码}{16mm}{10mm}
|
||||||
|
\sfnode[role=out, at={($(dec.east)+(16mm,0)$)}]{out}{输出}{14mm}{10mm}
|
||||||
|
\sfarrow{te.east}{fu.west}
|
||||||
|
\sfarrow{ve.east}{fu.west}
|
||||||
|
\sfarrow{fu.east}{dec.west}
|
||||||
|
\sfarrow{dec.east}{out.west}
|
||||||
|
\sfnolane
|
||||||
|
\sfcaption{fu}{fuse}{晚融合}
|
||||||
|
\sfcaption{dec}{decode}{条件生成}
|
||||||
|
\sfcaption{out}{out}{目标文本}
|
||||||
|
|
||||||
|
\sfbbox{all}
|
||||||
|
\sftopformula{F}{%
|
||||||
|
$y = \mathrm{Dec}\bigl(\mathrm{Fuse}(E_t(x),\, E_v(I))\bigr)$}
|
||||||
|
\sfmeaningbox{mb}{112mm}{all}
|
||||||
|
{两个编码器互不共享权重,只在融合节点会合}
|
||||||
|
{文本塔、视觉塔、融合、解码}
|
||||||
|
{各塔独立编码;融合后才进入解码,所以任一侧的依赖都经过融合节点}
|
||||||
|
|
||||||
|
\end{tikzpicture}
|
||||||
|
\end{document}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
% superfig golden example 1 -- one horizontal paper-figure pipeline.
|
||||||
|
% Main path is input -> model -> prediction. Loss is a side object, not a
|
||||||
|
% station on the forward path.
|
||||||
|
% ../scripts/build.sh pipeline.tex
|
||||||
|
\documentclass[border=10pt]{standalone}
|
||||||
|
\usepackage[cjk]{superfig}
|
||||||
|
|
||||||
|
\sfsetrole{input}{sfTeal}
|
||||||
|
\sfsetrole{model}{sfOrange}
|
||||||
|
\sfsetrole{loss}{sfCoral}
|
||||||
|
\sfsetrole{output}{sfViolet}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\begin{tikzpicture}
|
||||||
|
|
||||||
|
\sfstage{SA}{推理流程:一次前向}
|
||||||
|
\sfrow{R1}{16mm}
|
||||||
|
\sfnode[role=input]{x}{输入 $x$}{16mm}{12mm}
|
||||||
|
\sfconn{e1}{预处理}
|
||||||
|
\sfnode[role=model]{f}{模型 $f_\theta$}{18mm}{12mm}
|
||||||
|
\sfconn{e2}{logits}
|
||||||
|
\sfnode[role=output]{y}{预测 $\hat y$}{16mm}{12mm}
|
||||||
|
\sfrowend
|
||||||
|
|
||||||
|
% Loss compares the prediction with the target; it is not on the main path.
|
||||||
|
% Hang it below ŷ with enough shaft that no caption sits on the arrow.
|
||||||
|
\sfnode[role=loss, at={($(y.south)+(0,-22mm)$)}]{s}{损失 $L$}{14mm}{12mm}
|
||||||
|
\sfarrowlabel{y.south}{s.north}{$L(\hat y,y)$}
|
||||||
|
|
||||||
|
\sflane{R1}
|
||||||
|
\sfcaption{x}{$x$}{原始输入}
|
||||||
|
\sfcaption{f}{$f_\theta$}{可学习参数}
|
||||||
|
\sfnolane
|
||||||
|
\sfcaption{s}{$L$}{与真值比较}
|
||||||
|
|
||||||
|
\sfbbox{all}
|
||||||
|
\sftopformula{F}{%
|
||||||
|
$x \;\xrightarrow{\;f_\theta\;}\; \hat y,\qquad
|
||||||
|
\min_\theta\; L\bigl(f_\theta(x),\,y\bigr)$}
|
||||||
|
\sfmeaningbox{mb}{96mm}{all}
|
||||||
|
{一次从输入到预测的前向;损失在预测之后单独计算}
|
||||||
|
{数据、模型参数、预测、损失}
|
||||||
|
{预处理后送入模型;模型产生 logits 得到预测;损失比较预测与真值,梯度再回到参数}
|
||||||
|
\sfsignature{推理流程示意}{mb}
|
||||||
|
|
||||||
|
\end{tikzpicture}
|
||||||
|
\end{document}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
% superfig golden example 3 -- a state/time flow.
|
||||||
|
% One decode step: previous KV plus a new token produce the next state
|
||||||
|
% and the emitted token. The callout is an aside on the finished state band.
|
||||||
|
% ../scripts/build.sh state-flow.tex
|
||||||
|
\documentclass[border=10pt]{standalone}
|
||||||
|
\usepackage[cjk]{superfig}
|
||||||
|
|
||||||
|
\sfsetrole{state}{sfTeal}
|
||||||
|
\sfsetrole{step}{sfOrange}
|
||||||
|
\sfsetrole{tok}{sfViolet}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\begin{tikzpicture}
|
||||||
|
|
||||||
|
\sfstage{S}{解码一步:旧状态 + 新 token}
|
||||||
|
\sfvgap{26mm}
|
||||||
|
\sfrow{R1}{14mm}
|
||||||
|
\sfnode[role=state]{kv}{KV$_t$}{16mm}{11mm}
|
||||||
|
\sfconn{c1}{读}
|
||||||
|
\sfnode[role=step]{dec}{解码}{16mm}{11mm}
|
||||||
|
\sfconn{c2}{追加}
|
||||||
|
\sfnode[role=state]{kv2}{KV$_{t+1}$}{18mm}{11mm}
|
||||||
|
\sfrowend
|
||||||
|
\sflane{R1}
|
||||||
|
\sfcaption{kv}{KV$_t$}{已缓存键值}
|
||||||
|
\sfcaption{dec}{一步}{读 KV,写新列}
|
||||||
|
\sfcaption{kv2}{KV$_{t+1}$}{状态推进}
|
||||||
|
\sfcallout{N1}{38mm}{R1}{为何保留 KV}{%
|
||||||
|
下一步只读已有列、只追加 $x_t$ 这一列。重算前缀是另一条路径,这张图不画。}
|
||||||
|
|
||||||
|
% Tokens enter from above so the caption lane below the state row stays clear.
|
||||||
|
\sfnode[role=tok, at={($(dec.north west)+(-4mm,14mm)$)}]{xt}{$x_t$}{14mm}{10mm}
|
||||||
|
\sfnode[role=tok, at={($(dec.north east)+(4mm,14mm)$)}]{yt}{$y_t$}{14mm}{10mm}
|
||||||
|
\sfarrow{xt.south}{dec.north}
|
||||||
|
\sfarrow{dec.north}{yt.south}
|
||||||
|
|
||||||
|
\sfbbox{all}
|
||||||
|
\sftopformula{F}{%
|
||||||
|
$(\mathrm{KV}_{t+1},\, y_t) = \mathrm{Decode}(\mathrm{KV}_t,\, x_t)$}
|
||||||
|
\sfmeaningbox{mb}{108mm}{all}
|
||||||
|
{自回归一步:状态沿时间推进}
|
||||||
|
{缓存 KV、解码算子、本步 token}
|
||||||
|
{解码读 $\mathrm{KV}_t$ 与 $x_t$,写出 $y_t$,并把新列追加为 $\mathrm{KV}_{t+1}$}
|
||||||
|
|
||||||
|
\end{tikzpicture}
|
||||||
|
\end{document}
|
||||||
@@ -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.
|
||||||
Executable
+65
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Lint, compile, and export superfig deliverables.
|
||||||
|
# ./scripts/build.sh figure.tex [outdir]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SRC="${1:?usage: build.sh figure.tex [outdir]}"
|
||||||
|
[[ -f "$SRC" ]] || { echo "no such file: $SRC" >&2; exit 2; }
|
||||||
|
SRCDIR="$(cd "$(dirname "$SRC")" && pwd)"
|
||||||
|
BASE="$(basename "$SRC" .tex)"
|
||||||
|
OUT="${2:-$SRCDIR/build}"
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
mkdir -p "$OUT"
|
||||||
|
|
||||||
|
if [[ "${SF_SKIP_LINT:-0}" != "1" ]]; then
|
||||||
|
echo "==> lint $BASE"
|
||||||
|
python3 "$ROOT/scripts/lint.py" "$SRC"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> xelatex $BASE"
|
||||||
|
TEXINPUTS="$ROOT/assets:$SRCDIR:" \
|
||||||
|
xelatex -halt-on-error -interaction=nonstopmode \
|
||||||
|
-output-directory="$OUT" "$SRC" >/dev/null 2>&1 \
|
||||||
|
|| { echo "!! xelatex failed; last errors:" >&2
|
||||||
|
grep -n -A4 -m3 '^!' "$OUT/$BASE.log" >&2 || tail -30 "$OUT/$BASE.log" >&2
|
||||||
|
exit 1; }
|
||||||
|
|
||||||
|
LOG="$OUT/$BASE.log"
|
||||||
|
status=0
|
||||||
|
|
||||||
|
if grep -q "Missing character" "$LOG"; then
|
||||||
|
echo "!! missing glyphs:" >&2
|
||||||
|
grep -m5 "Missing character" "$LOG" >&2
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
if grep -qE "^(Overfull|Underfull) \\\\[hv]box" "$LOG"; then
|
||||||
|
echo "!! overfull/underfull boxes:" >&2
|
||||||
|
grep -m5 -E "^(Overfull|Underfull) \\\\[hv]box" "$LOG" >&2
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
if grep -q "Package superfig Warning" "$LOG"; then
|
||||||
|
echo "!! superfig warnings:" >&2
|
||||||
|
grep -m5 -A2 "Package superfig Warning" "$LOG" >&2
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v pdftocairo >/dev/null 2>&1; then
|
||||||
|
echo "==> exporting svg / png"
|
||||||
|
pdftocairo -svg "$OUT/$BASE.pdf" "$OUT/$BASE.svg"
|
||||||
|
pdftocairo -png -r 300 -singlefile "$OUT/$BASE.pdf" "$OUT/$BASE"
|
||||||
|
pdftocairo -png -r 300 -singlefile -transp "$OUT/$BASE.pdf" "$OUT/$BASE-alpha"
|
||||||
|
pdftocairo -png -scale-to-x 360 -scale-to-y -1 -singlefile \
|
||||||
|
"$OUT/$BASE.pdf" "$OUT/$BASE-thumb"
|
||||||
|
else
|
||||||
|
echo "!! pdftocairo missing: PDF only, no SVG/PNG" >&2
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> artifacts in $OUT"
|
||||||
|
ls -1 "$OUT/$BASE"*.{pdf,svg,png} 2>/dev/null | sed 's/^/ /'
|
||||||
|
if [[ $status -ne 0 ]]; then
|
||||||
|
echo "==> BUILD DIRTY: fix the warnings above before delivering." >&2
|
||||||
|
else
|
||||||
|
echo "==> clean. Now do the visual audit (references/checklist.md)."
|
||||||
|
fi
|
||||||
|
exit $status
|
||||||
Executable
+159
@@ -0,0 +1,159 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Static source checks for superfig figures.
|
||||||
|
|
||||||
|
The package owns layout and colors at render time; this linter catches the
|
||||||
|
source-level escapes TeX cannot see: undeclared roles, too many active hue
|
||||||
|
families, hand-rolled TikZ drawing, a formula placed before the last row,
|
||||||
|
a callout that is not an aside on a band, and a group that wraps one node.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROLE_DECL_RE = re.compile(r"\\sfsetrole\{([^{}]+)\}\{([^{}]+)\}")
|
||||||
|
ROLE_KEY_RE = re.compile(r"role\s*=\s*([A-Za-z0-9_-]+)")
|
||||||
|
RAW_TIKZ_RE = re.compile(r"\\(?:draw|fill|path)\b")
|
||||||
|
ROW_NAME_RE = re.compile(r"\\sfrow\{([^{}]+)\}")
|
||||||
|
CALLOUT_RE = re.compile(r"\\sfcallout\{([^{}]+)\}\{[^{}]+\}\{([^{}]+)\}")
|
||||||
|
GROUP_RE = re.compile(
|
||||||
|
r"\\sfgroup(?:\[[^\]]*\])?\{([^{}]+)\}\{([^{}]*)\}\{([^{}]*)\}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def strip_comments(source: str) -> str:
|
||||||
|
return re.sub(r"(?<!\\)%.*$", "", source, flags=re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
|
def line_of(source: str, offset: int) -> int:
|
||||||
|
return source.count("\n", 0, offset) + 1
|
||||||
|
|
||||||
|
|
||||||
|
def directives(source: str) -> set[str]:
|
||||||
|
found: set[str] = set()
|
||||||
|
for match in re.finditer(r"^\s*%\s*superfig-lint:\s*(.+)$", source, re.MULTILINE):
|
||||||
|
found.update(item.strip() for item in match.group(1).split(","))
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def lint(path: Path) -> list[str]:
|
||||||
|
raw = path.read_text(encoding="utf-8")
|
||||||
|
allowed = directives(raw)
|
||||||
|
source = strip_comments(raw)
|
||||||
|
errors: list[str] = []
|
||||||
|
|
||||||
|
declared: dict[str, str] = {"neutral": "sfGray"}
|
||||||
|
for match in ROLE_DECL_RE.finditer(source):
|
||||||
|
name, color = (part.strip() for part in match.groups())
|
||||||
|
previous = declared.get(name)
|
||||||
|
if previous is not None and previous != color and name != "neutral":
|
||||||
|
errors.append(
|
||||||
|
f"line {line_of(source, match.start())}: role {name!r} changes "
|
||||||
|
f"from {previous!r} to {color!r}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
declared[name] = color
|
||||||
|
|
||||||
|
used_colors = {"sfGray"}
|
||||||
|
for match in ROLE_KEY_RE.finditer(source):
|
||||||
|
role = match.group(1)
|
||||||
|
color = declared.get(role)
|
||||||
|
if color is None:
|
||||||
|
errors.append(
|
||||||
|
f"line {line_of(source, match.start())}: role {role!r} is used "
|
||||||
|
f"before \\sfsetrole"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
used_colors.add(color)
|
||||||
|
|
||||||
|
active = used_colors - {"sfGray"}
|
||||||
|
if len(active) > 4:
|
||||||
|
errors.append(
|
||||||
|
f"figure uses {len(active)} active hue families "
|
||||||
|
f"({', '.join(sorted(active))}); maximum is 4 plus gray"
|
||||||
|
)
|
||||||
|
|
||||||
|
if "allow-raw-tikz" not in allowed:
|
||||||
|
for match in RAW_TIKZ_RE.finditer(source):
|
||||||
|
errors.append(
|
||||||
|
f"line {line_of(source, match.start())}: hand-rolled TikZ drawing "
|
||||||
|
f"bypasses \\sfnode/\\sfarrow; add allow-raw-tikz only for a "
|
||||||
|
f"genuinely non-linear detail"
|
||||||
|
)
|
||||||
|
|
||||||
|
formula_positions = [m.start() for m in re.finditer(r"\\sftopformula\b", source)]
|
||||||
|
row_ends = [m.start() for m in re.finditer(r"\\sfrowend\b", source)]
|
||||||
|
if formula_positions and row_ends and formula_positions[-1] < row_ends[-1]:
|
||||||
|
errors.append(
|
||||||
|
f"line {line_of(source, formula_positions[-1])}: \\sftopformula "
|
||||||
|
f"must follow the last \\sfrowend"
|
||||||
|
)
|
||||||
|
|
||||||
|
band_names = set(ROW_NAME_RE.findall(source))
|
||||||
|
callout_anchors: dict[str, str] = {}
|
||||||
|
first_callout: str | None = None
|
||||||
|
for match in CALLOUT_RE.finditer(source):
|
||||||
|
name, anchor = (part.strip() for part in match.groups())
|
||||||
|
line = line_of(source, match.start())
|
||||||
|
if anchor not in band_names:
|
||||||
|
errors.append(
|
||||||
|
f"line {line}: callout {name!r} is anchored to {anchor!r}, which "
|
||||||
|
f"is not a \\sfrow band; a card hanging off a single object "
|
||||||
|
f"reads as a step"
|
||||||
|
)
|
||||||
|
elif anchor in callout_anchors:
|
||||||
|
errors.append(
|
||||||
|
f"line {line}: callout {name!r} is the second card on band "
|
||||||
|
f"{anchor!r} (after {callout_anchors[anchor]!r}); one aside "
|
||||||
|
f"per band"
|
||||||
|
)
|
||||||
|
elif first_callout is not None and "allow-multiple-callouts" not in allowed:
|
||||||
|
errors.append(
|
||||||
|
f"line {line}: callout {name!r} is the second card in the "
|
||||||
|
f"figure (after {first_callout!r}); the budget is one callout "
|
||||||
|
f"per figure"
|
||||||
|
)
|
||||||
|
callout_anchors.setdefault(anchor, name)
|
||||||
|
if first_callout is None:
|
||||||
|
first_callout = name
|
||||||
|
|
||||||
|
if "allow-single-group" not in allowed:
|
||||||
|
for match in GROUP_RE.finditer(source):
|
||||||
|
name, fit, _caption = match.groups()
|
||||||
|
members = re.findall(r"\(([^()]+)\)", fit)
|
||||||
|
if len(members) < 2:
|
||||||
|
errors.append(
|
||||||
|
f"line {line_of(source, match.start())}: group {name!r} "
|
||||||
|
f"wraps {len(members)} member(s); bind at least two"
|
||||||
|
)
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="lint a superfig .tex source")
|
||||||
|
parser.add_argument("sources", nargs="+", type=Path)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
failed = False
|
||||||
|
for path in args.sources:
|
||||||
|
if not path.is_file():
|
||||||
|
print(f"superfig-lint: no such file: {path}", file=sys.stderr)
|
||||||
|
failed = True
|
||||||
|
continue
|
||||||
|
errors = lint(path)
|
||||||
|
if errors:
|
||||||
|
failed = True
|
||||||
|
print(f"!! {path}", file=sys.stderr)
|
||||||
|
for error in errors:
|
||||||
|
print(f" {error}", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(f" lint {path}")
|
||||||
|
return 1 if failed else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Executable
+47
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Toolchain preflight for superfig.
|
||||||
|
# Exit codes: 0 full path, 1 degraded (no CJK/export), 2 no LaTeX.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
QUIET=0
|
||||||
|
[[ "${1:-}" == "--quiet" ]] && QUIET=1
|
||||||
|
say() { [[ $QUIET -eq 1 ]] || echo -e "$*"; }
|
||||||
|
|
||||||
|
ok=0; warn=0; fail=0; cjk_warn=0; export_warn=0
|
||||||
|
check() {
|
||||||
|
local name="$1"; shift
|
||||||
|
if "$@" >/dev/null 2>&1; then say " ok $name"; ok=$((ok+1)); return 0
|
||||||
|
else say " MISS $name"; return 1; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
say "superfig preflight"
|
||||||
|
say "--- required ---"
|
||||||
|
check "xelatex" command -v xelatex || fail=$((fail+1))
|
||||||
|
check "standalone.cls" kpsewhich standalone.cls || fail=$((fail+1))
|
||||||
|
check "tikz.sty" kpsewhich tikz.sty || fail=$((fail+1))
|
||||||
|
check "array.sty" kpsewhich array.sty || fail=$((fail+1))
|
||||||
|
check "etoolbox.sty" kpsewhich etoolbox.sty || fail=$((fail+1))
|
||||||
|
|
||||||
|
say "--- chinese figures ---"
|
||||||
|
check "ctex.sty" kpsewhich ctex.sty || { warn=$((warn+1)); cjk_warn=1; }
|
||||||
|
check "fandol font" kpsewhich FandolSong-Regular.otf || { warn=$((warn+1)); cjk_warn=1; }
|
||||||
|
|
||||||
|
say "--- raster / vector export ---"
|
||||||
|
check "pdftocairo" command -v pdftocairo || { warn=$((warn+1)); export_warn=1; }
|
||||||
|
|
||||||
|
if [[ $fail -gt 0 ]]; then
|
||||||
|
say ""
|
||||||
|
say "RESULT: no usable LaTeX path."
|
||||||
|
say "Fall back to SVG/HTML and state that superfig package guarantees are unavailable."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if [[ $warn -gt 0 ]]; then
|
||||||
|
say ""
|
||||||
|
say "RESULT: degraded."
|
||||||
|
[[ $cjk_warn -eq 1 ]] && say " - missing ctex/fandol -> English-label figures only."
|
||||||
|
[[ $export_warn -eq 1 ]] && say " - missing pdftocairo -> PDF only, no PNG/SVG."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
say ""
|
||||||
|
say "RESULT: full path available (TikZ + CJK + vector/raster export)."
|
||||||
|
exit 0
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build every valid example, then prove invalid TeX and lint fixtures fail.
|
||||||
|
#
|
||||||
|
# ./scripts/test.sh
|
||||||
|
set -uo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
for f in "$ROOT"/tests/*.tex "$ROOT"/examples/*.tex; do
|
||||||
|
[[ -e "$f" ]] || continue
|
||||||
|
name="$(basename "$f")"
|
||||||
|
if "$ROOT/scripts/build.sh" "$f" >/dev/null 2>&1; then
|
||||||
|
echo " ok $name"
|
||||||
|
else
|
||||||
|
echo " FAIL $name"
|
||||||
|
fail=$((fail+1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for f in "$ROOT"/tests/invalid/*.tex; do
|
||||||
|
[[ -e "$f" ]] || continue
|
||||||
|
name="$(basename "$f")"
|
||||||
|
if SF_SKIP_LINT=1 "$ROOT/scripts/build.sh" "$f" >/dev/null 2>&1; then
|
||||||
|
echo " FAIL $name (invalid fixture built cleanly)"
|
||||||
|
fail=$((fail+1))
|
||||||
|
else
|
||||||
|
echo " ok $name (rejected by package/build)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for f in "$ROOT"/tests/lint-invalid/*.tex; do
|
||||||
|
[[ -e "$f" ]] || continue
|
||||||
|
name="$(basename "$f")"
|
||||||
|
if python3 "$ROOT/scripts/lint.py" "$f" >/dev/null 2>&1; then
|
||||||
|
echo " FAIL $name (invalid fixture passed lint)"
|
||||||
|
fail=$((fail+1))
|
||||||
|
else
|
||||||
|
echo " ok $name (rejected by lint)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $fail -gt 0 ]]; then
|
||||||
|
echo "$fail failing check(s); rerun the reported build or lint command to inspect" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "all positive and negative checks passed"
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
\documentclass{standalone}
|
||||||
|
\usepackage[en]{superfig}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\draw (0,0) -- (1,0);
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
Reference in New Issue
Block a user