%% 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] {\sd@lblillegal}; \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] {\sd@lbllegal}; \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} \def\sd@lblillegal{illegal}\def\sd@lbllegal{legal} \else \def\sd@lblidea{思路}\def\sd@lblrw{改写}\def\sd@lblcaveat{注意} \def\sd@lblillegal{非法}\def\sd@lbllegal{合法} \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