A third sibling toolkit: rewrite steps, justification rails, cancel/substitute highlights, and lint-on-warning builds.
298 lines
10 KiB
TeX
298 lines
10 KiB
TeX
%% superderive.sty -- stepwise algebraic derivation figures
|
|
%% Third sibling of superfig / supertensor. Same house style and
|
|
%% role/cursor/warning discipline. Primitives are rewrite steps,
|
|
%% not nodes or tensor faces.
|
|
%%
|
|
%% Options: cjk XeLaTeX + fandol
|
|
%% en English meaning-box rails
|
|
\NeedsTeXFormat{LaTeX2e}
|
|
\ProvidesPackage{superderive}[2026/08/17 v1.0 stepwise derivation figures]
|
|
|
|
\newif\ifder@cjk\der@cjkfalse
|
|
\newif\ifder@en\der@enfalse
|
|
\DeclareOption{cjk}{\der@cjktrue}
|
|
\DeclareOption{en}{\der@entrue}
|
|
\DeclareOption{zh}{\der@enfalse}
|
|
\ProcessOptions\relax
|
|
|
|
\RequirePackage{amsmath}
|
|
\RequirePackage{amssymb}
|
|
\RequirePackage{xcolor}
|
|
\RequirePackage{etoolbox}
|
|
\RequirePackage{array}
|
|
\RequirePackage{tikz}
|
|
\usetikzlibrary{calc,positioning,arrows.meta,fit}
|
|
|
|
\ifder@cjk
|
|
\RequirePackage[UTF8,fontset=fandol]{ctex}
|
|
\fi
|
|
|
|
\pgfdeclarelayer{sdbg}
|
|
\pgfdeclarelayer{sdfg}
|
|
\pgfsetlayers{sdbg,main,sdfg}
|
|
|
|
\definecolor{sdTeal}{HTML}{4F8FA5}
|
|
\definecolor{sdOrange}{HTML}{EE995B}
|
|
\definecolor{sdCoral}{HTML}{C95B5B}
|
|
\definecolor{sdViolet}{HTML}{8A74B5}
|
|
\definecolor{sdGray}{HTML}{85898F}
|
|
\definecolor{sdInk}{HTML}{1A1A1A}
|
|
|
|
\newcommand{\sdsetrole}[2]{%
|
|
\edef\sd@newrole{#2}%
|
|
\ifcsname sd@role@#1\endcsname
|
|
\edef\sd@oldrole{\csname sd@role@#1\endcsname}%
|
|
\ifx\sd@oldrole\sd@newrole\else
|
|
\PackageWarning{superderive}{Role `#1' was already mapped to
|
|
`\sd@oldrole' and cannot be remapped to `\sd@newrole'}%
|
|
\fi
|
|
\else
|
|
\expandafter\gdef\csname sd@role@#1\endcsname{#2}%
|
|
\fi}
|
|
\newcommand{\sdrole}[1]{%
|
|
\ifcsname sd@role@#1\endcsname\csname sd@role@#1\endcsname\else sdGray\fi}
|
|
\newcommand{\sdcheckrole}[1]{%
|
|
\ifcsname sd@role@#1\endcsname\else
|
|
\PackageWarning{superderive}{Undeclared role `#1' -- drawn in neutral gray.
|
|
Declare it with \string\sdsetrole}%
|
|
\fi}
|
|
\sdsetrole{neutral}{sdGray}
|
|
\sdsetrole{keep}{sdTeal}
|
|
\sdsetrole{rewrite}{sdOrange}
|
|
\sdsetrole{cancel}{sdCoral}
|
|
\sdsetrole{intro}{sdViolet}
|
|
\sdsetrole{warn}{sdCoral}
|
|
|
|
\newcommand{\sdlevelpct}[1]{\ifcase#1 0\or30\or55\or80\else55\fi}
|
|
|
|
\tikzset{
|
|
sd stage/.style = {font=\small\bfseries, text=black!55, inner sep=2pt},
|
|
sd note/.style = {font=\scriptsize, text=black!55, inner sep=2pt, align=left},
|
|
sd arrow/.style = {-{Stealth[length=2.2mm,width=1.6mm]}, draw=black!45, line width=0.5pt},
|
|
sd term/.style = {draw=black!60, line width=0.5pt, rounded corners=1.5pt,
|
|
inner sep=6pt, font=\small, text=sdInk, align=center},
|
|
sd reason/.style= {font=\scriptsize, text=black!55, align=left,
|
|
draw=black!18, fill=black!3, rounded corners=1.5pt,
|
|
inner xsep=6pt, inner ysep=5pt},
|
|
}
|
|
|
|
\newlength{\sdgutter}\setlength{\sdgutter}{6mm}
|
|
\newlength{\sdrowgap}\setlength{\sdrowgap}{5mm}
|
|
\newlength{\sdblockgap}\setlength{\sdblockgap}{9mm}
|
|
\newlength{\sd@railx}\newlength{\sd@cx}\newlength{\sd@cy}
|
|
\newlength{\sd@ycur}\newlength{\sd@bandh}\newlength{\sd@tmpx}\newlength{\sd@tmpy}
|
|
\newif\ifder@inrow
|
|
\newif\ifder@first
|
|
\newcount\sd@ncols
|
|
|
|
\newcommand{\sdlayoutreset}{%
|
|
\global\sd@railx=0pt \global\sd@ycur=0pt
|
|
\global\sd@cx=0pt \global\sd@cy=0pt \global\sd@bandh=0pt
|
|
\global\der@inrowfalse
|
|
\global\sd@ncols=0
|
|
\gdef\sd@rowlist{}\gdef\sd@alllist{}\gdef\sd@rowname{}%
|
|
\gdef\sd@lastnode{}}
|
|
\sdlayoutreset
|
|
\newcommand{\sdleftrail}[1]{\global\sd@railx=\dimexpr#1\relax}
|
|
\newcommand{\sdvgap}[1]{\global\advance\sd@ycur by -\dimexpr#1\relax}
|
|
\newcommand{\sdgap}[1]{\global\advance\sd@cx by \dimexpr#1\relax}
|
|
|
|
\newcommand{\sd@lower}[1]{%
|
|
\pgfextracty{\sd@tmpy}{\pgfpointanchor{#1}{south}}%
|
|
\ifdim\sd@tmpy<\sd@ycur \global\sd@ycur=\sd@tmpy \fi}
|
|
\newcommand{\sd@regall}[1]{\xdef\sd@alllist{\sd@alllist(#1)}}
|
|
\newcommand{\sd@regrow}[1]{%
|
|
\xdef\sd@rowlist{\sd@rowlist(#1)}\sd@regall{#1}%
|
|
\gdef\sd@lastnode{#1}}
|
|
\newcommand{\sd@needrow}[1]{%
|
|
\ifder@inrow\else
|
|
\PackageError{superderive}{\string#1\space needs an open \string\sdrow}%
|
|
{Close placement only works between \string\sdrow\space and \string\sdrowend.}%
|
|
\fi}
|
|
\newcommand{\sd@leadgap}[1]{%
|
|
\ifder@first
|
|
\global\der@firstfalse
|
|
\else
|
|
\global\advance\sd@cx by \dimexpr#1\relax
|
|
\fi}
|
|
|
|
\newcommand{\sdstage}[2]{%
|
|
\dimen0=\sd@ycur \advance\dimen0 by -\sdblockgap
|
|
\node[sd stage, anchor=north west] (#1) at (\the\sd@railx,\the\dimen0) {#2};
|
|
\sd@regall{#1}\sd@lower{#1}}
|
|
|
|
% \sdrow{name} or \sdrow[height]{name}
|
|
\newcommand{\sdrow}[2][16mm]{%
|
|
\gdef\sd@rowlist{}\gdef\sd@rowname{#2}\gdef\sd@lastnode{}%
|
|
\global\der@inrowtrue\global\der@firsttrue
|
|
\pgfmathsetlengthmacro{\sd@bh}{#1}%
|
|
\global\sd@bandh=\sd@bh
|
|
\global\sd@cx=\sd@railx
|
|
\dimen0=\sd@ycur \advance\dimen0 by -\sdrowgap \advance\dimen0 by -0.5\sd@bandh
|
|
\global\sd@cy=\dimen0}
|
|
|
|
\newcommand{\sdrowend}{%
|
|
\ifdefempty{\sd@rowlist}%
|
|
{\PackageWarning{superderive}{Empty \string\sdrow\space `\sd@rowname'}}%
|
|
{\edef\sd@do{\noexpand\node[inner sep=0pt, outer sep=0pt,
|
|
fit={\sd@rowlist}] (\sd@rowname) {};}\sd@do
|
|
\sd@regall{\sd@rowname}\sd@lower{\sd@rowname}}%
|
|
\global\der@inrowfalse}
|
|
|
|
\newcommand{\sdbbox}[1]{%
|
|
\edef\sd@do{\noexpand\node[inner sep=0pt, outer sep=0pt,
|
|
fit={\sd@alllist}] (#1) {};}\sd@do}
|
|
\newcommand{\sdtrack}[1]{\sd@regall{#1}\sd@lower{#1}}
|
|
\newcommand{\sdtopformula}[2]{%
|
|
\sdbbox{sd@bbt}%
|
|
\node[above=6mm of sd@bbt, anchor=south, font=\large] (#1) {#2};
|
|
\sd@regall{#1}}
|
|
|
|
\pgfkeys{
|
|
/sd/term/.cd,
|
|
role/.store in=\sd@role,
|
|
level/.store in=\sd@level,
|
|
gap/.store in=\sd@gap,
|
|
role=rewrite, level=2, gap=\sdgutter,
|
|
}
|
|
|
|
% \sdstep[keys]{name}{lhs}{rhs}
|
|
\newcommand{\sdstep}[4][]{%
|
|
\sd@needrow{\sdstep}%
|
|
\begingroup
|
|
\pgfkeys{/sd/term/.cd,#1}%
|
|
\sdcheckrole{\sd@role}%
|
|
\edef\sd@col{\sdrole{\sd@role}}%
|
|
\sd@leadgap{\sd@gap}%
|
|
\node[anchor=west, inner sep=0pt] (#2) at (\the\sd@cx,\the\sd@cy) {%
|
|
\begin{tikzpicture}[baseline=(sd@mid)]
|
|
\node[sd term, fill=\sd@col!\sdlevelpct{\sd@level}] (sd@L) {$\displaystyle #3$};
|
|
\node[sd term, fill=\sd@col!\sdlevelpct{\sd@level},
|
|
right=\sdgutter of sd@L] (sd@R) {$\displaystyle #4$};
|
|
\coordinate (sd@mid) at ($(sd@L.east)!0.5!(sd@R.west)$);
|
|
\begin{pgfonlayer}{sdbg}
|
|
\draw[sd arrow] (sd@L.east) -- (sd@R.west);
|
|
\end{pgfonlayer}
|
|
\end{tikzpicture}};%
|
|
\pgfextractx{\sd@tmpx}{\pgfpointanchor{#2}{east}}%
|
|
\global\sd@cx=\sd@tmpx
|
|
\sd@regrow{#2}%
|
|
\endgroup}
|
|
|
|
% \sdreason{step-name}{text} -- hangs off the named step, or the last step.
|
|
\newcommand{\sdreason}[2]{%
|
|
\sd@needrow{\sdreason}%
|
|
\sd@leadgap{\sdgutter}%
|
|
\node[sd reason, anchor=west, text width=38mm] (#1-why)
|
|
at (\the\sd@cx,\the\sd@cy) {#2};%
|
|
\pgfextractx{\sd@tmpx}{\pgfpointanchor{#1-why}{east}}%
|
|
\global\sd@cx=\sd@tmpx
|
|
\sd@regrow{#1-why}}
|
|
|
|
% \sdcancel[keys]{name}{math}
|
|
\newcommand{\sdcancel}[3][]{%
|
|
\sd@needrow{\sdcancel}%
|
|
\begingroup
|
|
\pgfkeys{/sd/term/.cd,role=cancel,#1}%
|
|
\sdcheckrole{\sd@role}%
|
|
\edef\sd@col{\sdrole{\sd@role}}%
|
|
\sd@leadgap{\sd@gap}%
|
|
\node[sd term, fill=\sd@col!\sdlevelpct{\sd@level}, anchor=west] (#2)
|
|
at (\the\sd@cx,\the\sd@cy) {$\displaystyle #3$};%
|
|
\draw[\sd@col, line width=0.8pt]
|
|
($(#2.west)+(1.2mm,0)$) -- ($(#2.east)+(-1.2mm,0)$);
|
|
\pgfextractx{\sd@tmpx}{\pgfpointanchor{#2}{east}}%
|
|
\global\sd@cx=\sd@tmpx
|
|
\sd@regrow{#2}%
|
|
\endgroup}
|
|
|
|
% \sdsubst[keys]{name}{from}{to}
|
|
\newcommand{\sdsubst}[4][]{%
|
|
\sd@needrow{\sdsubst}%
|
|
\begingroup
|
|
\pgfkeys{/sd/term/.cd,role=rewrite,#1}%
|
|
\sdcheckrole{\sd@role}%
|
|
\edef\sd@col{\sdrole{\sd@role}}%
|
|
\sd@leadgap{\sd@gap}%
|
|
\node[anchor=west, inner sep=0pt] (#2) at (\the\sd@cx,\the\sd@cy) {%
|
|
\begin{tikzpicture}[baseline=(sd@mid)]
|
|
\node[sd term, fill=sdCoral!30] (sd@F) {$\displaystyle #3$};
|
|
\draw[sdCoral, line width=0.7pt]
|
|
($(sd@F.west)+(1mm,0)$) -- ($(sd@F.east)+(-1mm,0)$);
|
|
\node[sd term, fill=\sd@col!\sdlevelpct{\sd@level},
|
|
right=\sdgutter of sd@F] (sd@T) {$\displaystyle #4$};
|
|
\coordinate (sd@mid) at ($(sd@F.east)!0.5!(sd@T.west)$);
|
|
\draw[sd arrow] (sd@F.east) -- (sd@T.west);
|
|
\end{tikzpicture}};%
|
|
\pgfextractx{\sd@tmpx}{\pgfpointanchor{#2}{east}}%
|
|
\global\sd@cx=\sd@tmpx
|
|
\sd@regrow{#2}%
|
|
\endgroup}
|
|
|
|
% \sdbox[keys]{name}{math}
|
|
\newcommand{\sdbox}[3][]{%
|
|
\sd@needrow{\sdbox}%
|
|
\begingroup
|
|
\pgfkeys{/sd/term/.cd,role=keep,#1}%
|
|
\sdcheckrole{\sd@role}%
|
|
\edef\sd@col{\sdrole{\sd@role}}%
|
|
\sd@leadgap{\sd@gap}%
|
|
\node[sd term, fill=\sd@col!\sdlevelpct{\sd@level}, anchor=west] (#2)
|
|
at (\the\sd@cx,\the\sd@cy) {$\displaystyle #3$};%
|
|
\pgfextractx{\sd@tmpx}{\pgfpointanchor{#2}{east}}%
|
|
\global\sd@cx=\sd@tmpx
|
|
\sd@regrow{#2}%
|
|
\endgroup}
|
|
|
|
% \sdcol{name}{illegal}{legal} -- one contrast pair per figure.
|
|
\newcommand{\sdcol}[3]{%
|
|
\sd@needrow{\sdcol}%
|
|
\global\advance\sd@ncols by 1
|
|
\ifnum\sd@ncols>1
|
|
\PackageWarning{superderive}{Contrast column `#1' is the second pair
|
|
in the figure. The budget is one illegal/legal pair}%
|
|
\fi
|
|
\sd@leadgap{\sdgutter}%
|
|
\node[anchor=west, inner sep=0pt] (#1) at (\the\sd@cx,\the\sd@cy) {%
|
|
\begin{tikzpicture}[baseline=(sd@mid)]
|
|
\node[sd term, fill=sdCoral!30] (sd@B) {$\displaystyle #2$};
|
|
\node[font=\scriptsize, text=sdCoral, above=1pt of sd@B] {非法};
|
|
\node[sd term, fill=sdTeal!30, right=8mm of sd@B] (sd@G) {$\displaystyle #3$};
|
|
\node[font=\scriptsize, text=sdTeal, above=1pt of sd@G] {合法};
|
|
\coordinate (sd@mid) at ($(sd@B.east)!0.5!(sd@G.west)$);
|
|
\end{tikzpicture}};%
|
|
\pgfextractx{\sd@tmpx}{\pgfpointanchor{#1}{east}}%
|
|
\global\sd@cx=\sd@tmpx
|
|
\sd@regrow{#1}}
|
|
|
|
\ifder@en
|
|
\def\sd@lblidea{Idea}\def\sd@lblrw{Rewrite}\def\sd@lblcaveat{Caveat}
|
|
\else
|
|
\def\sd@lblidea{思路}\def\sd@lblrw{改写}\def\sd@lblcaveat{注意}
|
|
\fi
|
|
\newcommand{\sdsetlabels}[3]{\def\sd@lblidea{#1}\def\sd@lblrw{#2}\def\sd@lblcaveat{#3}}
|
|
\newlength{\sd@raillen}
|
|
\ifder@en
|
|
\setlength{\sd@raillen}{5.4em}
|
|
\else
|
|
\setlength{\sd@raillen}{3.2em}
|
|
\fi
|
|
|
|
\newcommand{\sdmeaningbox}[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{\sd@raillen}@{\hspace{7pt}}p{\dimexpr#2-\sd@raillen-7pt\relax}@{}}
|
|
\ifblank{#4}{}{\textbf{\sd@lblidea} & \raggedright\arraybackslash #4 \\}
|
|
\ifblank{#5}{}{\textbf{\sd@lblrw} & \raggedright\arraybackslash #5 \\}
|
|
\ifblank{#6}{}{\textbf{\sd@lblcaveat} & \raggedright\arraybackslash #6 \\}
|
|
\end{tabular}};}
|
|
|
|
\newcommand{\sdsignature}[2]{%
|
|
\node[below=2.2mm of #2, font=\scriptsize, text=black!45] (#2-sig) {#1};}
|
|
|
|
\endinput
|