%% ribbonproofs.sty %% Copyright 2013 John Wickerson % % This work may be distributed and/or modified under the % conditions of the LaTeX Project Public License, either version 1.3 % of this license or (at your option) any later version. % The latest version of this license is in % http://www.latex-project.org/lppl.txt % and version 1.3 or later is part of all distributions of LaTeX % version 2005/12/01 or later. % % This work has the LPPL maintenance status `maintained'. % % The Current Maintainer of this work is John Wickerson. % % This work consists of the files ribbonproofs.sty, % ribbonproofsmanual.tex, and ribbonproofsmanual.pdf. % Title: Ribbon Proofs % Description: A package for drawing ribbon proofs % Author: John Wickerson % Creation date: 20-Apr-2013 % Last modified: 8-Jul-2013 % =================================================================== % TODO LIST % % * more error detection, e.g. start/fit overlap, % finish/fit overlap, repetition within start, % fit or finish ribbons. % % % * Consider the case where we have a var label and a % rotated main label. Should we write % label=p&, rotated label=q % or % label=p&\rotate{q} % ? % =================================================================== % LOAD REQUIRED PACKAGES % % For colours \RequirePackage[svgnames,usenames]{xcolor} % For drawing stuff \RequirePackage{tikz} % For using the `let` construct \usetikzlibrary{calc} % For drawing shadows \usetikzlibrary{fadings} % For drawing zigzag lines \usetikzlibrary{decorations.pathmorphing} % For splitting a string at a given character \RequirePackage{xstring} % For expansion control \RequirePackage{etextools} % =================================================================== % GENERAL-PURPOSE COMMANDS % Extension of the ExpandNextTwo command provided by etextools. % ExpandNextTwo\foo{arg1}{arg2} fully expands arg1 and arg2 % and then passes them to the \foo command. \newcommand*\ExpandNextThree[4]{% \ExpandNext{% \ExpandNext{% \ExpandNext{#1}{#2}% }{#3}% }{#4}% } % Another extension of the ExpandNextTwo command provided by % etextools. ExpandNextThree\foo{arg1}{arg2}{arg3} fully expands % arg1, arg2 and arg3 and then passes them to the \foo command. \newcommand*\ExpandNextFour[5]{% \ExpandNext{% \ExpandNext{% \ExpandNext{% \ExpandNext{#1}{#2}% }{#3}% }{#4}% }{#5}% } % Append to the beginning of a comma-separated list. % If \list={} then \cons{\list}{a} defines \list={a}. % If \list={p,e} then \cons{\list{a} defines \list={a,p,e}. % The optional argument can be used to change the comma into % something else. The element being appended is not % expanded, and neither is the list. \newcommand*\cons[3][,]{% \ifx#2\@empty \xdef#2{\expandonce{#3}} \else \xdef#2{\expandonce{#3}#1\expandonce{#2}} \fi } % Append to the end of a comma-separated list. (The name % is "cons" backwards.) If \list={} then \snoc{\list}{a} defines % \list={a}. If \list={p,e} then \snoc{\list{a} defines \list={p,e,a}. % The optional argument can be used to change the comma into % something else. The element being appended is not % expanded, and neither is the list. \newcommand*\snoc[3][,]{% \ifx#2\@empty \xdef#2{\expandonce{#3}} \else \xdef#2{\expandonce{#2}#1\expandonce{#3}} \fi } % Test whether a comma-separated list contains a given element. If % element el is in list L, then % \@ifListContains{el}{L}{}{} will execute % , and if it is not, it will execute . \newcommand*\@ifListContains[4]{% \gdef\myflag{0} \edef\mylist{\expandonce{#2}} \foreach\j in \mylist { \IfStrEq{#1}{\j}{ \gdef\myflag{1} #3 \breakforeach }{} } \ifnum\myflag=0\relax #4 \fi } % Test whether a comma-separated list of key-value pairs contains % a given key. If key k is in the "domain" of list L, then % \@ifPairListContainsKey{k}{L}{}{} will % execute , and if it is not, it will execute . \newcommand*\@ifPairListContainsKey[4]{% \gdef\myflag{0} \edef\mylist{\expandonce{#2}} \foreach\j/\values in \mylist { \IfStrEq{#1}{\j}{ \gdef\myflag{1} #3 \breakforeach }{} } \ifnum\myflag=0\relax #4 \fi } % \undefine{foo} makes the command \foo undefined. The effect is % that \ifcsname foo\endcsname will henceforth return false. \newcommand*\undefine[1]{% \expandafter\global% \expandafter\let\csname #1\endcsname=\@undefined% } % A foreach command that iterates over two lists (of the same % length) simultaneously, plucking an element from both lists % on each iteration. For instance, \foreachtwo\foo{1,2}{a,b} % executes \foo{1}{a} and then \foo{2}{b}. \def\@@foreachtwo#1#2,#3;#4,#5;{% #1{#2}{#4}% \@foreachtwo{#1}{#3}{#5}% } \newcommand*\@foreachtwo[3]{% \ifboolexpr{test{\IfSubStr{#2}{,}} and test{\IfSubStr{#3}{,}}}{% \@@foreachtwo{#1}#2;#3;% }{% \ifboolexpr{test{\notblank{#2}} and test{\notblank{#3}}}{% #1{#2}{#3}% }{}% }% } \newcommand*\foreachtwo[3]{% \def\lenI{0} \def\lenII{0} \foreach\x in {#2} { \pgfmathtruncatemacro\lenI{\lenI+1} \global\let\lenI=\lenI } \foreach\x in {#3} { \pgfmathtruncatemacro\lenII{\lenII+1} \global\let\lenII=\lenII } \ifnum\lenI=\lenII \else \errmessage{The lists {#2} (length \lenI) and {#3} (length \lenII) are not the same length} \fi \@foreachtwo{#1}{#2}{#3} } % =================================================================== % CONSTANTS % background colour of "justification" steps \newcommand\jusColor{black!50} % background colour of "command" steps \newcommand\comColor{black} % background colour of ribbons \newcommand\ribColor{black!10} % background colour of vars-as-res ribbons \newcommand\varribColor{black!20} % foreground colour of ribbons \newcommand\ribTextColor{black} % foreground colour of boxes \newcommand\boxTextColor{black} % foreground colour of "guide" text \newcommand\guideTextColor{red!40} % default step height \newcommand\defaultStepHeight{5} % default row height (includes the step) \newcommand\defaultRowHeight{11} % distance from top of ribbon to its label \newcommand\ribTextVOffset{4} % distance from top of box to its label \newcommand\boxTextVOffset{3} % horizontal adjustment of box label \newcommand\boxTextHOffset{1} % distance from bottom of row to guide text on ribbons \newcommand\guideTextVOffset{2} % radius of rounded corners on block steps \newcommand\roundingRadius{2} % radius of rounded corners on boxes \newcommand\boxRoundingRadius{1} % thickness of vertical lines delimiting block steps \newcommand\blockLineWidth{0.5} % thickness of lines delimiting existential boxes \newcommand\boxLineWidth{0.8pt} % height of the shadows that are drawn when a step % passes over a catalyst ribbon \newcommand\shadowHeight{2} % size of the jagged edges on "else" steps \newcommand\zigzagHeight{1} \newcommand\zigzagLength{3} % color of [the darkest point of] a shadow \newcommand\shadowColor{black!40} % adjust the shape of the ribbon twists (value between 0 and 1) \newcommand\twistiness{0.7} % =================================================================== % ERROR MESSAGES \newcommand\printActiveRibbons{% The currently active ribbons are {\@activeRibbons}% } \newcommand\printActiveBoxes{% The currently active existential boxes are {\@activeBoxes}% } \newcommand\errorI[1]{\PackageError{RibbonProofs}{% Ribbon #1\space is specified as a start ribbon, but is already active. \printActiveRibbons}} \newcommand\errorII[1]{\PackageError{RibbonProofs}{% Existential box #1\space is specified as a start box, but is already active. \printActiveBoxes}} \newcommand\errorIII[1]{\PackageError{RibbonProofs}{% Ribbon #1\space is specified as a fit ribbon, but is not currently active. \printActiveRibbons}} \newcommand\errorIV[1]{\PackageError{RibbonProofs}{% Ribbon #1\space is specified as a finish ribbon, but is not currently active. \printActiveRibbons}} \newcommand\errorV[1]{\PackageError{RibbonProofs}{% Existential box #1\space is specified as a fit box, but is not currently active. \printActiveBoxes}} \newcommand\errorVI[1]{\PackageError{RibbonProofs}{% Existential box #1\space is specified as an finish box, but is not currently active. \printActiveBoxes}} \newcommand\errorVII[1]{\PackageError{RibbonProofs}{% Ribbon #1\space is not active, so cannot be moved. \printActiveRibbons}} \newcommand\errorVIII[1]{\PackageError{RibbonProofs}{% Existential box #1\space is not active, so cannot be moved. \printActiveBoxes}} \newcommand\errorIX[1]{\PackageError{RibbonProofs}{% Existential box #1\space is not active, so cannot be extended. \printActiveBoxes}} % =================================================================== % STORAGE % Data is stored in pgf keys, in macros, and in counters. % The "execute style" and "execute code" keys are due to % Andrew Stacey: http://tex.stackexchange.com/q/85637/86. \pgfqkeys{/ribbons/ribbon}{ .unknown/.code={\errmessage{Unknown key: \pgfkeyscurrentkeyRAW}}, left/.estore in= {\ribbons@left}, right/.estore in= {\ribbons@right}, var width/.estore in= {\ribbons@varwidth}, label/.store in= {\ribbons@label}, rotated label/.store in= {\ribbons@rotatedlabel}, execute style/.style = {#1}, execute macro/.style = {execute style/.expand once=#1}, } \pgfqkeys{/ribbons/box}{ .unknown/.code={\errmessage{Unknown key: \pgfkeyscurrentkeyRAW}}, left/.estore in= {\ribbons@left}, right/.estore in= {\ribbons@right}, label/.store in= {\ribbons@label}, color/.store in= {\ribbons@boxcolor}, vertical offset/.store in= {\ribbons@boxvoffset}, horizontal offset/.store in= {\ribbons@boxhoffset}, execute style/.style = {#1}, execute macro/.style = {execute style/.expand once=#1}, } \pgfqkeys{/ribbons/step}{ .unknown/.code={\errmessage{Unknown key: \pgfkeyscurrentkeyRAW}}, pale/.code= {\def\ribbons@color{\jusColor}}, height/.store in= {\ribbons@stepheight}, extra left/.store in= {\ribbons@extraleft}, extra right/.store in= {\ribbons@extraright}, finish ribbons/.store in= {\ribbons@finishribbons}, start ribbons/.store in= {\ribbons@startribbons}, start boxes/.store in= {\ribbons@startboxes}, finish boxes/.store in= {\ribbons@finishboxes}, text/.store in= {\ribbons@text}, } \pgfqkeys{/ribbons/startblock}{ .unknown/.code={\errmessage{Unknown key: \pgfkeyscurrentkeyRAW}}, height/.store in= {\ribbons@stepheight}, extra left/.store in= {\ribbons@extraleft}, extra right/.store in= {\ribbons@extraright}, finish ribbons/.store in= {\ribbons@finishribbons}, start ribbons/.store in= {\ribbons@startribbons}, finish boxes/.store in= {\ribbons@finishboxes}, start boxes/.store in= {\ribbons@startboxes}, fit ribbons/.store in= {\ribbons@fitribbons}, fit boxes/.store in= {\ribbons@fitboxes}, text/.store in= {\ribbons@text}, } \pgfqkeys{/ribbons/continueblock}{ .unknown/.code={\errmessage{Unknown key: \pgfkeyscurrentkeyRAW}}, height/.store in= {\ribbons@stepheight}, start ribbons/.store in={\ribbons@startribbons}, start boxes/.store in= {\ribbons@startboxes}, text/.store in= {\ribbons@text}, jagged/.code= {\def\ribbons@jagged{1}}, repeat labels/.code= {\def\ribbons@repeatlabels{1}}, } \pgfqkeys{/ribbons/finishblock}{ .unknown/.code={\errmessage{Unknown key: \pgfkeyscurrentkeyRAW}}, height/.store in= {\ribbons@stepheight}, finish ribbons/.store in={\ribbons@finishribbons}, start ribbons/.store in= {\ribbons@startribbons}, finish boxes/.store in= {\ribbons@finishboxes}, start boxes/.store in= {\ribbons@startboxes}, extra left/.store in= {\ribbons@extraleft}, extra right/.store in= {\ribbons@extraright}, text/.store in= {\ribbons@text}, } \pgfqkeys{/ribbons/ribbonproof}{ .unknown/.code={\errmessage{Unknown key: \pgfkeyscurrentkeyRAW}}, scale/.store in= {\ribbons@scale}, extra height/.store in= {\ribbons@extraheight}, start boxes/.store in= {\ribbons@startboxes}, start ribbons/.store in= {\ribbons@startribbons}, draw grid/.code= {% \tikzset{every picture/.append style={% execute at end picture={\@drawNotches},% show grid=all}} \@showidstrue} } % For tracking the distance from the top of the diagram. \newcounter{VCursor} % Height of the current row. \newcounter{RowHeight} % =================================================================== % SHOW GRID % % This code is based on Martin Scharrer's showgrid TikZ % library, which can be downloaded from his website: % http://latex.scharrer-online.de/general/wiki/showgrid % I have changed the colour of the grid, provided minor % and major grid lines, and added a numeric scale to % each of the four edges. \pgfdeclarelayer{background} \pgfdeclarelayer{foreground} \pgfsetlayers{background,main,foreground} \newif\if@showgrid@grid \newif\if@showgrid@left \newif\if@showgrid@right \newif\if@showgrid@below \newif\if@showgrid@above \newif\if@showids \tikzset{% every show grid/.style={}, show grid/.style={% execute at end picture={\@showgrid{grid=true,#1}}},% show grid/.default={true}, show grid/.cd, labels/.style={font={\sffamily\small},help lines,text=red!40}, xlabels/.style={}, ylabels/.style={}, keep bb/.code={% \useasboundingbox (current bounding box.south west) rectangle (current bounding box.north east);}, true/.style={left,below}, false/.style={left=false,right=false,above=false, below=false,grid=false}, none/.style={left=false,right=false,above=false,below=false}, all/.style={left=true,right=true,above=true,below=true}, grid/.is if=@showgrid@grid, left/.is if=@showgrid@left, right/.is if=@showgrid@right, below/.is if=@showgrid@below, above/.is if=@showgrid@above, false, } \def\@showgrid#1{% \begin{scope}[every show grid,show grid/.cd,#1] \if@showgrid@grid \begin{pgfonlayer}{background} \draw [help lines, draw=red!10, step=1mm] let \p1 = (current bounding box.south west), \p2 = (current bounding box.north east) in (\x1-2mm,\y1-2mm) grid (\x2+2mm,\y2+2mm); \draw [help lines, draw=red!20, step=10mm] (current bounding box.south west) grid (current bounding box.north east); \pgfpointxy{1}{1}% \edef\xs{\the\pgf@x}% \edef\ys{\the\pgf@y}% \pgfpointanchor{current bounding box}{south west} \edef\xa{\the\pgf@x}% \edef\ya{\the\pgf@y}% \pgfpointanchor{current bounding box}{north east} \edef\xb{\the\pgf@x}% \edef\yb{\the\pgf@y}% \pgfmathtruncatemacro\xbeg{ceil(\xa/\xs)} \pgfmathtruncatemacro\xend{floor(\xb/\xs)} \if@showgrid@below \foreach \X in {\xbeg,...,\xend} { \pgfmathparse{int(mod(\X,10))} \ifnum\pgfmathresult=0 \node [below,show grid/labels,show grid/xlabels] at (\X,\ya) {\X}; \else\fi } \fi \if@showgrid@above \foreach \X in {\xbeg,...,\xend} { \pgfmathparse{int(mod(\X,10))} \ifnum\pgfmathresult=0 \node [above,show grid/labels,show grid/xlabels] at (\X,\yb) {\X}; \else\fi } \fi \pgfmathtruncatemacro\ybeg{ceil(\ya/\ys)} \pgfmathtruncatemacro\yend{floor(\yb/\ys)} \if@showgrid@left \foreach \Y in {\ybeg,...,\yend} { \pgfmathparse{int(mod(\Y,10))} \ifnum\pgfmathresult=0 \node [left,show grid/labels,show grid/ylabels] at (\xa,\Y) {\Y}; \else\fi } \fi \if@showgrid@right \foreach \Y in {\ybeg,...,\yend} { \pgfmathparse{int(mod(\Y,10))} \ifnum\pgfmathresult=0 \node [right,show grid/labels,show grid/ylabels] at (\xb,\Y) {\Y}; \else\fi } \fi \end{pgfonlayer} \fi \end{scope} } % End TikZ showgrid code % =================================================================== % MINOR COMMANDS % The expression % \@defineRibbon{foo}{34}{57}{10}{x=5} % expands to the following definitions % \xdef\RIBfooLEFT{34} % \xdef\RIBfooRIGHT{57} % \xdef\RIBfooVARWIDTH{10} % \gdef\RIBfoo@label{x=5} % and saves "foo" in the list of defined ribbons. \newcommand*\@defineRibbon[5]{% \snoc{\@allDefinedRibbons}{#1} \expandafter\xdef\csname RIB#1LEFT\endcsname{#2} \expandafter\xdef\csname RIB#1RIGHT\endcsname{#3} \expandafter\xdef\csname RIB#1VARWIDTH\endcsname{#4} \expandafter\xdef\csname RIB#1@label\endcsname{\expandonce{#5}} } % The expression % \@defineBox{foo}{34}{57}{green} % expands to the following definitions % \xdef\BOXfooLEFT{34} % \xdef\BOXfooRIGHT{57} % \xdef\BOXfoo@color{green} % and saves "foo" in the list of defined boxes. \newcommand*\@defineBox[5]{% \snoc{\@allDefinedBoxes}{#1} \expandafter\xdef\csname BOX#1LEFT\endcsname{#2} \expandafter\xdef\csname BOX#1RIGHT\endcsname{#3} \expandafter\xdef\csname BOX#1@color\endcsname{#4} \expandafter\xdef\csname BOX#1@label\endcsname{#5} } % In a state where % \RIBfooLEFT = 88 % \RIBfooRIGHT = 99 % \RIBfooVARWIDTH = 10 % the expression % \@rememberPrevRibbon{foo} % expands to the following definitions % \xdef\RIBfoo@prevleft{88} % \xdef\RIBfoo@prevright{99} % \xdef\RIBfoo@prevvarwidth{10} \newcommand*\@rememberPrevRibbon[1]{% \expandafter\xdef\csname RIB#1@prevleft\endcsname{% \csname RIB#1LEFT\endcsname} \expandafter\xdef\csname RIB#1@prevright\endcsname{% \csname RIB#1RIGHT\endcsname} \expandafter\xdef\csname RIB#1@prevvarwidth\endcsname{% \csname RIB#1VARWIDTH\endcsname} } % In a state where % \BOXfooLEFT = 88 % \BOXfooRIGHT = 99 % the expression % \@rememberPrevBox{foo} % expands to the following definitions % \xdef\BOXfoo@prevleft{88} % \xdef\BOXfoo@prevright{99} \newcommand*\@rememberPrevBox[1]{% \expandafter\xdef\csname BOX#1@prevleft\endcsname{% \csname BOX#1LEFT\endcsname} \expandafter\xdef\csname BOX#1@prevright\endcsname{% \csname BOX#1RIGHT\endcsname} } % Various projection functions \newcommand*\@getRibbonLeft[1]{% \csname RIB#1LEFT\endcsname} \newcommand*\@getRibbonRight[1]{% \csname RIB#1RIGHT\endcsname} \newcommand*\@getRibbonVarWidth[1]{% \csname RIB#1VARWIDTH\endcsname} \newcommand*\@getRibbonLabel[1]{% \expandafter\expandonce\csname RIB#1@label\endcsname} \newcommand*\@getRibbonPrevLeft[1]{% \csname RIB#1@prevleft\endcsname} \newcommand*\@getRibbonPrevRight[1]{% \csname RIB#1@prevright\endcsname} \newcommand*\@getRibbonPrevVarWidth[1]{% \csname RIB#1@prevvarwidth\endcsname} \newcommand*\@getBoxLeft[1]{% \csname BOX#1LEFT\endcsname} \newcommand*\@getBoxRight[1]{% \csname BOX#1RIGHT\endcsname} \newcommand*\@getBoxColor[1]{% \csname BOX#1@color\endcsname} \newcommand*\@getBoxLabel[1]{% \csname BOX#1@label\endcsname} \newcommand*\@getBoxPrevLeft[1]{% \csname BOX#1@prevleft\endcsname} \newcommand*\@getBoxPrevRight[1]{% \csname BOX#1@prevright\endcsname} % Add the given identifier to the list of active ribbons. \newcommand*\@addToActiveRibbons[1]{% \snoc\@activeRibbons{#1} } % Add the given identifier to the list of active boxes. \newcommand*\@addToActiveBoxes[1]{% \snoc\@activeBoxes{#1} } % Add the given numbers to the lists of left- and % right-positions of blocks. \newcommand*\@addToBlocks[2]{% \cons\@blocks@left{#1} \cons\@blocks@right{#2} } % Return the left-/right-position of the current % block step \newcommand*\@getBlockLeft{% \expandafter\headOfList\@blocks@left } \newcommand*\@getBlockRight{% \expandafter\headOfList\@blocks@right } % Remove the given identifier from the list of active ribbons. \newcommand*\@removeFromActiveRibbons[1]{% \@expandtwoargs\@removeelement{#1}\@activeRibbons\@activeRibbons \global\let\@activeRibbons=\@activeRibbons } % Remove the given identifier from the list of active boxes. \newcommand*\@removeFromActiveBoxes[1]{% \@expandtwoargs\@removeelement{#1}\@activeBoxes\@activeBoxes \global\let\@activeBoxes=\@activeBoxes } % Split a label into a variable resource and a remainder. For % instance: % \@splitLabel{foo&bar}{\l}{\r} defines \l=foo and \r=bar. % \@splitLabel{foo}{\l}{\r} defines \l={} and \r=foo. % \@splitLabel{&foo}{\l}{\r} defines \l={} and \r=foo. % \@splitLabel{foo&}{\l}{\r} defines \l=foo and \r={}. \newcommand\@splitLabel[3]{ { % This brace-group is to protect the ampersand from % a possibly-enclosing tabular or array environment. \noexpandarg \IfSubStr{#1}{&}{ \StrCut{#1}{&}{#2}{#3} }{ \gdef #2{} \gdef #3{#1} } } } % Draw a label on a ribbon \newcommand*\@drawRibbonLabel{% { % % \expandarg arranges that each argument to xstring macros % is expanded once, rather than fully or not at all. \expandarg \IfSubStr{\ribbons@label}{&}{ \StrCut{\ribbons@label}{&}{\ribbons@varlabel}{\ribbons@label} }{ \gdef \ribbons@varlabel{} } \pgfmathsetmacro\@ribbonCentre{% 0.5*(\ribbons@left+\ribbons@varwidth+\ribbons@right)} \pgfmathsetmacro\@ribbonVarCentre{% \ribbons@left+(0.5*\ribbons@varwidth)} \begin{pgfonlayer}{foreground} \node[text=\ribTextColor, anchor=base] at (\@ribbonCentre,\theVCursor+\ribbons@stepheight+\ribTextVOffset) {$\begin{array}[t]{@{}l@{}}\ribbons@label\end{array}$}; \node[rotate=-90,text=\ribTextColor, anchor=west] at (\@ribbonCentre,\theVCursor+\ribbons@stepheight) {$\begin{array}[t]{@{}l@{}}\ribbons@rotatedlabel\end{array}$}; \node[text=\ribTextColor, anchor=base] at (\@ribbonVarCentre,\theVCursor+\ribbons@stepheight+\ribTextVOffset) {$\begin{array}[t]{@{}l@{}}\ribbons@varlabel\end{array}$}; \end{pgfonlayer} } } % Draw a label on a box \newcommand*\@drawBoxLabel{% \begin{pgfonlayer}{foreground} \node[text=\ribbons@boxcolor, anchor=east,% inner sep=0mm, fill=white] at (\ribbons@left+\boxTextHOffset+\ribbons@boxhoffset,% \theVCursor+\ribbons@stepheight+% \boxTextVOffset+\ribbons@boxvoffset)% {\strut $\ribbons@label$}; \end{pgfonlayer} } % For each ribbon in \ribbons@startribbons, add its identifier to % the list of active ribbons, store its horizontal position, and % draw a label on it. \newcommand*\@processStartRibbons{% \foreach \i/\values in \ribbons@startribbons { % % If ribbon \i is already active, raise an error. \@ifListContains{\i}{\@activeRibbons}{\errorI\i}{} % % NB. The space after \i is important, otherwise % the command \iLEFT will be expanded. \ifcsname RIB\i LEFT\endcsname \gdef\defaultLeft{\@getRibbonLeft\i} \gdef\defaultRight{\@getRibbonRight\i} \gdef\defaultVarWidth{\@getRibbonVarWidth\i} \else \gdef\defaultLeft{0} \gdef\defaultRight{0} \gdef\defaultVarWidth{0} \fi \pgfqkeys{/ribbons/ribbon}{% left=\defaultLeft,right=\defaultRight,% label={}, rotated label={},% var width=\defaultVarWidth,% execute macro=\values} \ExpandNextFour\@defineRibbon\i\ribbons@left% \ribbons@right\ribbons@varwidth\ribbons@label \@rememberPrevRibbon\i \ExpandNext\@addToActiveRibbons\i \@drawRibbonLabel } } % For each box in \ribbons@startboxes, add its identifier to % the list of active boxes, store its horizontal position, and % draw a label on it. \newcommand*\@processStartBoxes{% \foreach \i/\values in \ribbons@startboxes { % % If existential box \i is already active, raise an error. \@ifListContains{\i}{\@activeBoxes}{\errorII\i}{} % % NB. The space after \i is important, since otherwise % the command \iLEFT would be expanded. \ifcsname BOX\i LEFT\endcsname \gdef\defaultLeft{\@getBoxLeft\i} \gdef\defaultRight{\@getBoxRight\i} \gdef\defaultColor{\@getBoxColor\i} \else \gdef\defaultLeft{0} \gdef\defaultRight{0} \gdef\defaultColor{black} \fi \pgfqkeys{/ribbons/box}{% left=\defaultLeft,right=\defaultRight,% color=\defaultColor,label={},% vertical offset=0, horizontal offset=0,% execute macro=\values} \ExpandNextThree\@defineBox% \i\ribbons@left\ribbons@right\ribbons@boxcolor\ribbons@label \@rememberPrevBox\i \ExpandNext\@addToActiveBoxes\i \@drawBoxLabel } } % Draw a rectangle whose northwest and northeast corners are % rounded. Usage: % \topRoundedRectangle{}{}{}{} \newcommand\topRoundedRectangle[4]{% \fill[\ribbons@color] (#1+\roundingRadius,#2) to[bend right=45] (#1,#2+\roundingRadius) -- (#1,#4) -- (#3,#4) -- (#3,#2+\roundingRadius) to[bend right=45] (#3-\roundingRadius,#2) -- cycle; } % Draw a rectangle whose southwest and southeast corners are % rounded. Usage: % \botRoundedRectangle{}{}{}{} \newcommand\botRoundedRectangle[4]{% \fill[\ribbons@color] (#1+\roundingRadius,#4) to[bend left=45] (#1,#4-\roundingRadius) -- (#1,#2) -- (#3,#2) -- (#3,#4-\roundingRadius) to[bend left=45] (#3-\roundingRadius,#4) -- cycle; } %\@roundedElbow{}{}{}{} % % y .------------' % newx oldx \newcommand\@roundedElbow[4]{% \def\radius{#1} \def\y{#2} \def\oldx{#3} \def\newx{#4} \pgfmathsetmacro\oldxsuc{\oldx+1} \pgfmathsetmacro\newxsuc{\newx+1} \ifnum\newx=\oldx \draw(\oldx,\y-\radius) -- (\newx,\y+\radius); \fi \ifnum\oldx>\newx \ifnum\oldx=\newxsuc \draw(\oldx,\y-\radius) to[in=90, out=270] (\newx,\y+\radius); \else \draw(\oldx,\y-\radius) to[bend left=45] (\oldx-\radius,\y) -- (\newx+\radius,\y) to[bend right=45] (\newx,\y+\radius); \fi \fi \ifnum\newx>\oldx \ifnum\newx=\oldxsuc \draw(\oldx,\y-\radius) to[in=90, out=270] (\newx,\y+\radius); \else \draw(\oldx, \y-\radius) to[bend right=45] (\oldx+\radius,\y) -- (\newx-\radius,\y) to[bend left=45] (\newx,\y+\radius); \fi \fi } % Check that each fit ribbon is currently active. If % not, raise an error. \newcommand*\@checkFitRibbons{% \foreach\i in \ribbons@fitribbons { \@ifListContains{\i}{\@activeRibbons}{}{\errorIII\i} } } % Check that each incoming ribbon is currently active. If % not, raise an error. \newcommand*\@checkFinishRibbons{% \foreach\i in \ribbons@finishribbons { \@ifListContains{\i}{\@activeRibbons}{}{\errorIV\i} } } % Check that each fit box is currently active. If % not, raise an error. \newcommand*\@checkFitBoxes{% \foreach\i in \ribbons@fitboxes { \@ifListContains{\i}{\@activeBoxes}{}{\errorV\i} } } % Check that each incoming box is currently active. If % not, raise an error. \newcommand*\@checkFinishBoxes{% \foreach\i in \ribbons@finishboxes { \@ifListContains{\i}{\@activeBoxes}{}{\errorVI\i} } } % Draw some notches down the left-hand edge of the picture % (only when "draw grid" is enabled). \newcommand\@drawNotches{% \coordinate (originalbb) at (current bounding box.north west); \foreach \yvalue in \notchPositions {% \draw[\guideTextColor, ultra thick] let \p1 = (originalbb) in (\x1-1mm,\yvalue) -- ++(-3,0); } } % Draw shadows over "catalyst" ribbons, that is, those % ribbons that pass underneath a step without being a % precondition of that step. \newcommand*\@drawShadowsOverCatalysts{% \pgfmathsetmacro\@shadowTop{\theVCursor+\ribbons@stepheight} \foreach\i in \ribbons@catalystribbons { \pgfmathsetmacro\@shadowLeft{\@getRibbonLeft\i} \pgfmathsetmacro\@shadowRight{\@getRibbonRight\i} \fill[\shadowColor, path fading=south] (\@shadowLeft,\@shadowTop) rectangle (\@shadowRight,\@shadowTop+\shadowHeight); } } % Draw a segment of a box \newcommand*\@drawExistentialBox[2]{% \pgfmathsetmacro\xtopleft{\@getBoxPrevLeft{#1}} \pgfmathsetmacro\xtopright{\@getBoxPrevRight{#1}} \pgfmathsetmacro\xbotleft{\@getBoxLeft{#1}} \pgfmathsetmacro\xbotright{\@getBoxRight{#1}} \def\lineColor{\@getBoxColor{#1}} \def\ytop{#2} \def\ybot{\theVCursor+\theRowHeight} { % a group to hide the &&-operator from a % possibly-enclosing tabular environment. \pgfmathtruncatemacro\isRectangular{% (\xtopleft==\xbotleft)&&(\xtopright==\xbotright)} \begin{scope}[draw=\lineColor,line width=\boxLineWidth] \ifnum\isRectangular=1\relax \draw[xshift=0.5*\boxLineWidth] (\xtopleft,\ytop) -- (\xbotleft,\ybot); \draw[xshift=-0.5*\boxLineWidth] (\xtopright,\ytop) -- (\xbotright,\ybot); \else \def\controlLength{\twistiness*\theRowHeight} \draw[xshift=-0.5*\boxLineWidth] (\xtopright,\ytop) .. controls (\xtopright,\ytop+\controlLength) and (\xbotright,\ybot-\controlLength) .. (\xbotright,\ybot); \draw[xshift=0.5*\boxLineWidth] (\xbotleft,\ybot) .. controls (\xbotleft,\ybot-\controlLength) and (\xtopleft,\ytop+\controlLength) .. (\xtopleft,\ytop); \fi \end{scope} } } % Draw a segment of a ribbon \newcommand*\@drawRibbon[1]{% \pgfmathsetmacro\xtopleft{\@getRibbonPrevLeft{#1}} \pgfmathsetmacro\xtopmid{% \@getRibbonPrevLeft{#1}+\@getRibbonPrevVarWidth{#1}} \pgfmathsetmacro\xtopright{\@getRibbonPrevRight{#1}} \pgfmathsetmacro\xbotleft{\@getRibbonLeft{#1}} \pgfmathsetmacro\xbotmid{% \@getRibbonLeft{#1}+\@getRibbonVarWidth{#1}} \pgfmathsetmacro\xbotright{\@getRibbonRight{#1}} \def\ytop{\theVCursor} \def\ybot{\theVCursor+\theRowHeight} { % a group to hide the &&-operator from a % possibly-enclosing tabular environment. \pgfmathtruncatemacro\isRectangular{% (\xtopleft==\xbotleft)&&% (\xtopmid==\xbotmid)&&% (\xtopright==\xbotright)} \ifnum\isRectangular=1\relax \fill[\varribColor] (\xtopleft,\ytop) rectangle (\xbotmid,\ybot); \fill[\ribColor] (\xtopmid,\ytop) rectangle (\xbotright,\ybot); \else \def\controlLength{\twistiness*\theRowHeight} \def\rightDownwardCurve{(\xtopright,\ytop) .. controls (\xtopright,\ytop+\controlLength) and (\xbotright,\ybot-\controlLength) .. (\xbotright,\ybot)} \def\midDownwardCurve{(\xtopmid,\ytop) .. controls (\xtopmid,\ytop+\controlLength) and (\xbotmid,\ybot-\controlLength) .. (\xbotmid,\ybot)} \def\midUpwardCurve{(\xbotmid,\ybot) .. controls (\xbotmid,\ybot-\controlLength) and (\xtopmid,\ytop+\controlLength) .. (\xtopmid,\ytop)} \def\leftUpwardCurve{(\xbotleft,\ybot) .. controls (\xbotleft,\ybot-\controlLength) and (\xtopleft,\ytop+\controlLength) .. (\xtopleft,\ytop)} \draw[white, line width=0.5mm] \rightDownwardCurve; \draw[white, line width=0.5mm] \leftUpwardCurve; \fill[\ribColor] \rightDownwardCurve -- \midUpwardCurve -- cycle; \fill[\varribColor] \midDownwardCurve -- \leftUpwardCurve -- cycle; \fi } \if@showids \begin{pgfonlayer}{foreground} \def\guideY{\theVCursor+\theRowHeight-\guideTextVOffset} \node[anchor=west,text=\guideTextColor] at (\@getRibbonLeft\i, \guideY) {\strut \i}; \end{pgfonlayer} \fi } % =================================================================== % MAIN COMMANDS % Accessible to the user % Finish the current row. A synonym for "\\". \newcommand*\finishrow[1][0]{% \setcounter{RowHeight}{\defaultRowHeight} \addtocounter{RowHeight}{#1} % % For each active ribbon, draw it and its guide text \foreach\i in \@activeRibbons {% \@drawRibbon\i } % % For each active existential box, draw it \foreach\i in \@activeBoxes {% \@drawExistentialBox\i\theVCursor } % % For each block step, draw the side lines that delimit it. \foreach\x in \@blocks@left {% \fill[\comColor] (\x,\theVCursor) rectangle (\x+\blockLineWidth,\theVCursor+\theRowHeight); } \foreach\x in \@blocks@right {% \fill[\comColor] (\x,\theVCursor) rectangle (\x-\blockLineWidth,\theVCursor+\theRowHeight); } % % If there is a block step in "purgatory", i.e. one that % has been added during the current row, then draw in its % side lines, and add it to the list of block steps. \ifx\@blocksPurgatory@left\@empty \else \pgfmathsetmacro\xleft{\@blocksPurgatory@left} \pgfmathsetmacro\xright{\@blocksPurgatory@right} \pgfmathsetmacro\ytop{\theVCursor+\ribbons@stepheight} \pgfmathsetmacro\ybot{\theVCursor+\theRowHeight} \fill[\comColor] (\xleft,\ytop) rectangle (\xleft+\blockLineWidth,\ybot); \fill[\comColor] (\xright,\ytop) rectangle (\xright-\blockLineWidth,\ybot); \@addToBlocks\xleft\xright \xdef\@blocksPurgatory@left{} \xdef\@blocksPurgatory@right{} \fi % % If there are existential boxes in "purgatory", i.e. some % that have been extended during the current row, then draw % them a little lower than normal (to leave vertical space % for the extension operation), take them out of purgatory, % and put them back into the set of active boxes. \foreach \i in \@boxPurgatory { \ExpandNext\@addToActiveBoxes\i \@drawExistentialBox{\i}{\theVCursor+2*\boxRoundingRadius} } \xdef\@boxPurgatory{} % % Update i@prevleft and i@prevright for each active ribbon i \foreach\i in \@activeRibbons {\@rememberPrevRibbon\i} \foreach\i in \@activeBoxes {\@rememberPrevBox\i} % % If the grid is on, draw a little notch to show where % the row ends \snoc\notchPositions{\theVCursor} % \if@showids % \draw[\guideTextColor, ultra thick] % (0,\theVCursor) -- (3,\theVCursor); % \fi % % Advance the vertical cursor by the height of this row \addtocounter{VCursor}{\theRowHeight}% % % Reset the step height to the default value \xdef\ribbons@stepheight{\defaultStepHeight} } % A command step or a justification step \newcommand*\step[2][]{% % % Set default values for the step colour and % whether it is the start/end of a block step \def\ribbons@color{\comColor} % % Process keys \pgfqkeys{/ribbons/step}{% extra left=0,extra right=0,text={},% finish ribbons={},start ribbons={},% finish boxes={},start boxes={},% height=\defaultStepHeight,#1,text={#2}} % \@checkFinishRibbons \@checkFinishBoxes % % Build lists of all the left positions and right positions % of the incoming ribbons (we must do this early, because % their positions might get overwritten shortly). \gdef\@leftPositions{} \gdef\@rightPositions{} \foreach\i in \ribbons@finishribbons { \xdef\@leftPositions{\@getRibbonLeft\i,\@leftPositions} \xdef\@rightPositions{\@getRibbonRight\i,\@rightPositions} } \foreach\i in \ribbons@finishboxes { \xdef\@leftPositions{\@getBoxLeft\i,\@leftPositions} \xdef\@rightPositions{\@getBoxRight\i,\@rightPositions} } % % remove the incoming ribbons from the list of active % ribbons. \foreach\i in \ribbons@finishribbons { \ExpandNext\@removeFromActiveRibbons\i } \foreach\i in \ribbons@finishboxes { \ExpandNext\@removeFromActiveBoxes\i } % % Store the data for the outgoing ribbons, draw their labels, % and add them to the list of active ribbons \@processStartRibbons \@processStartBoxes % % Continue the lists of all the left positions and right % positions, by adding the data for the outgoing ribbons \foreach \i/\values in \ribbons@startribbons { \xdef\@leftPositions{\@getRibbonLeft\i,\@leftPositions} \xdef\@rightPositions{\@getRibbonRight\i,\@rightPositions} } \foreach \i/\values in \ribbons@startboxes { \xdef\@leftPositions{\@getBoxLeft\i,\@leftPositions} \xdef\@rightPositions{\@getBoxRight\i,\@rightPositions} } % % Calculate the minimum left position and the maximum % right position \pgfmathsetmacro\@minPosition{min(\@leftPositions)} \pgfmathsetmacro\@maxPosition{max(\@rightPositions)} % % Calculate the size and position of the step \pgfmathsetmacro\@stepLeft{\@minPosition-\ribbons@extraleft} \pgfmathsetmacro\@stepRight{\@maxPosition+\ribbons@extraright} % \pgfmathsetmacro\@stepCentre{\@stepLeft*0.5+\@stepRight*0.5} \pgfmathsetmacro\@stepTop{\theVCursor} \pgfmathsetmacro\@stepMid{\theVCursor+0.5*\ribbons@stepheight} \pgfmathsetmacro\@stepBot{\theVCursor+\ribbons@stepheight} % % Draw the step \begin{pgfonlayer}{foreground} \fill[\ribbons@color] (\@stepLeft,\@stepTop) rectangle (\@stepRight,\@stepBot); % % Draw the text on the step \node[text=white,anchor=west] at (\@stepLeft, \@stepMid) {\begin{tabular}{@{}l@{}}\ribbons@text\end{tabular}}; % % Work out which ribbons are the catalysts and draw % shadows over them. \gdef\ribbons@catalystribbons{} \foreach\i in \@activeRibbons { { % a group to hide the &&-operator from a % possibly-enclosing tabular environment. \pgfmathtruncatemacro\isInside{% (\@stepLeft<=(\@getRibbonLeft\i)) && ((\@getRibbonRight\i)<=\@stepRight)} \ifnum\isInside=1 \@ifPairListContainsKey\i\ribbons@startribbons{}{% \cons\ribbons@catalystribbons\i } \fi} } \@drawShadowsOverCatalysts \end{pgfonlayer} } % A justification step \newcommand*\jus[2][]{% \step[pale,#1]{#2}} % A command step \newcommand*\com[2][]{% \step[#1]{\texttt{#2}}} % The start of a block \newcommand*\startblock[2][] {% % % Set default value for the step colour \def\ribbons@color{\comColor} % % Process keys \pgfqkeys{/ribbons/startblock}{% extra left=0,extra right=0,text={},% finish ribbons={},start ribbons={},fit ribbons={},% finish boxes={},start boxes={}, fit boxes={},% height=\defaultStepHeight,#1,text={#2}} % \@checkFitRibbons \@checkFinishRibbons \@checkFitBoxes \@checkFinishBoxes % % Build lists of all the left positions and right positions % of the incoming/fit ribbons (we must do this early, because % the positions of the incoming ribbons might get overwritten % shortly). \gdef\@leftPositions{} \gdef\@rightPositions{} \foreach\i in \ribbons@finishribbons { \xdef\@leftPositions{\@getRibbonLeft\i,\@leftPositions} \xdef\@rightPositions{\@getRibbonRight\i,\@rightPositions} } \foreach\i in \ribbons@fitribbons { \xdef\@leftPositions{\@getRibbonLeft\i,\@leftPositions} \xdef\@rightPositions{\@getRibbonRight\i,\@rightPositions} } \foreach\i in \ribbons@finishboxes { \xdef\@leftPositions{\@getBoxLeft\i,\@leftPositions} \xdef\@rightPositions{\@getBoxRight\i,\@rightPositions} } \foreach\i in \ribbons@fitboxes { \xdef\@leftPositions{\@getBoxLeft\i,\@leftPositions} \xdef\@rightPositions{\@getBoxRight\i,\@rightPositions} } % % Remove the incoming ribbons from the list of active % ribbons. \foreach\i in \ribbons@finishribbons { \ExpandNext\@removeFromActiveRibbons\i } \foreach\i in \ribbons@finishboxes { \ExpandNext\@removeFromActiveBoxes\i } % % Store the data for the outgoing ribbons, draw their labels, % and add them to the list of active ribbons \@processStartRibbons \@processStartBoxes % % Continue the lists of all the left positions and right % positions, by adding the data for the outgoing ribbons \foreach \i/\values in \ribbons@startribbons { \xdef\@leftPositions{\@getRibbonLeft\i,\@leftPositions} \xdef\@rightPositions{\@getRibbonRight\i,\@rightPositions} } \foreach \i/\values in \ribbons@startboxes { \xdef\@leftPositions{\@getBoxLeft\i,\@leftPositions} \xdef\@rightPositions{\@getBoxRight\i,\@rightPositions} } % % Calculate the minimum left position and the maximum % right position \pgfmathsetmacro\@minPosition{min(\@leftPositions)} \pgfmathsetmacro\@maxPosition{max(\@rightPositions)} % % Calculate the size and position of the step \pgfmathsetmacro\@stepLeft{\@minPosition-\ribbons@extraleft} \pgfmathsetmacro\@stepRight{\@maxPosition+\ribbons@extraright} % \pgfmathsetmacro\@stepCentre{\@stepLeft*0.5+\@stepRight*0.5} \pgfmathsetmacro\@stepTop{\theVCursor} \pgfmathsetmacro\@stepMid{\theVCursor+0.5*\ribbons@stepheight} \pgfmathsetmacro\@stepBot{\theVCursor+\ribbons@stepheight} % % Draw a top-rounded rectangle, and add it to % purgatory (the list of almost-ready block steps). \begin{pgfonlayer}{foreground} \topRoundedRectangle\@stepLeft\@stepTop\@stepRight\@stepBot \xdef\@blocksPurgatory@left{\@stepLeft} \xdef\@blocksPurgatory@right{\@stepRight} % % Draw the text on the step \node[text=white,anchor=west] at (\@stepLeft, \@stepMid) {\begin{tabular}{@{}l@{}}\texttt{\ribbons@text}\end{tabular}}; % % Work out which ribbons and boxes are the catalysts \gdef\ribbons@catalystribbons{} \foreach\i in \@activeRibbons { { % a group to hide the &&-operator from a % possibly-enclosing tabular environment. \pgfmathtruncatemacro\isInside{% (\@stepLeft<=(\@getRibbonLeft\i)) && ((\@getRibbonRight\i)<=\@stepRight)} \ifnum\isInside=1 \@ifPairListContainsKey\i\ribbons@startribbons{}{% \cons\ribbons@catalystribbons\i } \fi} } \gdef\ribbons@catalystboxes{} \foreach\i in \@activeBoxes { { % a group to hide the &&-operator from a % possibly-enclosing tabular environment. \pgfmathtruncatemacro\isInside{% (\@stepLeft<=(\@getBoxLeft\i)) && ((\@getBoxRight\i)<=\@stepRight)} \ifnum\isInside=1 \@ifPairListContainsKey\i\ribbons@startboxes{}{% \cons\ribbons@catalystboxes\i } \fi} } % % Save the position of each catalyst ribbon/box. % These positions will be restored each % time we enter an "else" branch. \def\@savedRibbonPositions{} \foreach\i in \ribbons@catalystribbons { \edef\stuffToSave{% \i/\@getRibbonLeft\i/\@getRibbonRight\i/% {{\@getRibbonLabel\i}}} \snoc\@savedRibbonPositions{\stuffToSave} } \cons[;]\@blocks@state{\@savedRibbonPositions} \def\@savedBoxPositions{} \foreach\i in \ribbons@catalystboxes { \edef\stuffToSave{% \i/\@getBoxLeft\i/\@getBoxRight\i/\@getBoxColor\i/% {{\@getBoxLabel\i}}} \snoc\@savedBoxPositions{\stuffToSave} } \cons[;]\@blocks@boxstate{\@savedBoxPositions} % \@drawShadowsOverCatalysts \end{pgfonlayer} } % The middle of a block \newcommand*\continueblock[2][]{% % % Set default value for the step colour \def\ribbons@color{\comColor} % % Set "jagged" flag to 0 (off) \def\ribbons@jagged{0} % % Set "repeat labels" flag to 0 (off) \def\ribbons@repeatlabels{0} % % Process keys \pgfqkeys{/ribbons/continueblock}{% start ribbons={},start boxes={},% height=\defaultStepHeight,#1,text={#2}} % % Add to the set of start ribbons those ribbons that were saved % at the start of this block step, and draw shadows % over them. {\expandarg \StrCut{\@blocks@state}{;}{\@restoredRibbonCatalysts}{\foo} \begin{pgfonlayer}{foreground} \foreach \i/\xleft/\xright/\lab in \@restoredRibbonCatalysts { \ifnum\ribbons@repeatlabels=1 \edef\@tmp{% \i/{left=\xleft,right=\xright,label={\expandonce\lab}}} \else \edef\@tmp{\i/{left=\xleft,right=\xright,label={}}} \fi \cons\ribbons@startribbons\@tmp \fill[\shadowColor, path fading=south] (\xleft,\theVCursor+\ribbons@stepheight) rectangle (\xright,\theVCursor+\ribbons@stepheight+\shadowHeight); } \end{pgfonlayer} } {\expandarg \StrCut{\@blocks@boxstate}{;}{\@restoredBoxCatalysts}{\foo} \foreach \i/\xleft/\xright/\color/\lab in \@restoredBoxCatalysts { \ifnum\ribbons@repeatlabels=1 \edef\@tmp{\i/{left=\xleft,right=\xright,color=\color,% label={\expandonce\lab}}} \else \edef\@tmp{\i/{left=\xleft,right=\xright,color=\color,% label={}}} \fi \cons\ribbons@startboxes\@tmp } } % % Left and right positions are extracted from the % stack of block left/right positions \StrCut{\@blocks@left}{,}{\@stepLeft}{\foo} \global\let\@stepLeft=\@stepLeft \StrCut{\@blocks@right}{,}{\@stepRight}{\foo} \global\let\@stepRight=\@stepRight % % Obliterate the ribbons/boxes inside the footprint of the % block step. \foreach\i in \@activeRibbons { { % a group to hide the &&-operator from a % possibly-enclosing tabular environment. \pgfmathtruncatemacro\isInside{% (\@stepLeft<=(\@getRibbonLeft\i)) && ((\@getRibbonRight\i)<=\@stepRight)} \ifnum\isInside=1 \ExpandNext\@removeFromActiveRibbons\i \fi} } \foreach\i in \@activeBoxes { { % a group to hide the &&-operator from a % possibly-enclosing tabular environment. \pgfmathtruncatemacro\isInside{% (\@stepLeft<=(\@getBoxLeft\i)) && ((\@getBoxRight\i)<=\@stepRight)} \ifnum\isInside=1 \ExpandNext\@removeFromActiveBoxes\i \fi} } % % Store the data for the outgoing ribbons, draw their labels, % and add them to the list of active ribbons \@processStartRibbons \@processStartBoxes % \pgfmathsetmacro\@stepCentre{\@stepLeft*0.5+\@stepRight*0.5} \pgfmathsetmacro\@stepTop{\theVCursor} \pgfmathsetmacro\@stepMid{\theVCursor+0.5*\ribbons@stepheight} \pgfmathsetmacro\@stepBot{\theVCursor+\ribbons@stepheight} % % Draw a rectangle for this step. If "jagged" is set, then % draw a saw-tooth top to indicate that there is a % discontinuity at this step. \begin{pgfonlayer}{foreground} \ifnum\ribbons@jagged=1 \fill[\ribbons@color] (\@stepLeft,\@stepTop) decorate [decoration=saw, segment length=\zigzagLength mm] { -- (\@stepRight,\@stepTop) } -- (\@stepRight,\@stepBot) -- (\@stepLeft,\@stepBot) -- cycle; \else \fill[\ribbons@color] (\@stepLeft,\@stepTop) rectangle (\@stepRight,\@stepBot); \fi % % Draw the text on the step \node[text=white,anchor=west] at (\@stepLeft, \@stepMid) {\begin{tabular}{@{}l@{}}\texttt{\ribbons@text}\end{tabular}}; % \end{pgfonlayer} } % The end of a block \newcommand*\finishblock[2][]{% % % Set default values for the step colour and % whether it is the start/end of a block step \def\ribbons@color{\comColor} % % Process keys \pgfqkeys{/ribbons/finishblock}{% finish ribbons={},start ribbons={},% finish boxes={},start boxes={},% height=\defaultStepHeight,#1,text={#2}} % \@checkFinishRibbons \@checkFinishBoxes % % Left and right positions are extracted from the % stack of block left/right positions \StrCut{\@blocks@left}{,}{\@stepLeft}{\foo} \StrCut{\@blocks@right}{,}{\@stepRight}{\foo} % % Make the ribbons that are inside the footprint of the % block step, but are not start ribbons, into % catalyst ribbons. \gdef\ribbons@catalystribbons{} \foreach\i in \@activeRibbons { { % a group to hide the &&-operator from a % possibly-enclosing tabular environment. \pgfmathtruncatemacro\isInside{% (\@stepLeft<=(\@getRibbonLeft\i)) && ((\@getRibbonRight\i)<=\@stepRight)} \ifnum\isInside=1 \@ifPairListContainsKey\i\ribbons@startribbons{}{% \cons\ribbons@catalystribbons\i } \fi} } % % Remove the incoming ribbons from the list of active % ribbons. \foreach\i in \ribbons@finishribbons { \ExpandNext\@removeFromActiveRibbons\i } \foreach\i in \ribbons@finishboxes { \ExpandNext\@removeFromActiveBoxes\i } % % Store the data for the outgoing ribbons, draw their labels, % and add them to the list of active ribbons \@processStartRibbons \@processStartBoxes % \pgfmathsetmacro\@stepCentre{\@stepLeft*0.5+\@stepRight*0.5} \pgfmathsetmacro\@stepTop{\theVCursor} \pgfmathsetmacro\@stepMid{\theVCursor+0.5*\ribbons@stepheight} \pgfmathsetmacro\@stepBot{\theVCursor+\ribbons@stepheight} % % Draw a bottom-rounded rectangle, and remove it from the % the stack of block steps. \begin{pgfonlayer}{foreground} \botRoundedRectangle\@stepLeft\@stepTop\@stepRight\@stepBot \StrBehind{\@blocks@left}{,}[\@blocks@left] \global\let\@blocks@left=\@blocks@left \StrBehind{\@blocks@right}{,}[\@blocks@right] \global\let\@blocks@right=\@blocks@right {\expandarg \StrBehind{\@blocks@state}{;}[\@blocks@state] \global\let\@blocks@state=\@blocks@state } % % Draw the text on the step \node[text=white,anchor=west] at (\@stepLeft, \@stepMid) {\begin{tabular}{@{}l@{}}\texttt{\ribbons@text}\end{tabular}}; % \@drawShadowsOverCatalysts \end{pgfonlayer} } % Move ribbons \newcommand*\moveribbons[1]{% \foreach \i/\values in {#1} { % % If ribbon \i is not already active, raise an error. \@ifListContains{\i}{\@activeRibbons}{}{\errorVII\i} % \pgfqkeys{/ribbons/ribbon}{% left={\@getRibbonLeft\i}, right={\@getRibbonRight\i},% var width={\@getRibbonVarWidth\i},% execute macro=\values} \ExpandNextFour\@defineRibbon\i\ribbons@left% \ribbons@right\ribbons@varwidth{\@getRibbonLabel\i} % WORRY HERE ABOUT ERROR % % Remove and then re-add ribbon \i. This gives the user control % over the z-order of the ribbons when they are being moved. \ExpandNext\@removeFromActiveRibbons\i \ExpandNext\@addToActiveRibbons\i } } % Move boxes \newcommand*\moveboxes[1]{% \foreach \i/\values in {#1} { % % If box \i is not already active, raise an error. \@ifListContains{\i}{\@activeBoxes}{}{\errorVIII\i} % \pgfqkeys{/ribbons/box}{% left={\@getBoxLeft\i},right={\@getBoxRight\i},% color={\@getBoxColor\i},execute macro=\values} \ExpandNextThree\@defineBox% \i\ribbons@left\ribbons@right\ribbons@boxcolor{\@getBoxLabel\i} } } % Permute ribbons \newcommand*\swapribbons[2]{% \foreach \i in {#1} { \expandafter\xdef\csname RIB\i @oldleft\endcsname{% \csname RIB\i LEFT\endcsname} \expandafter\xdef\csname RIB\i @oldright\endcsname{% \csname RIB\i RIGHT\endcsname} \expandafter\xdef\csname RIB\i @oldvarwidth\endcsname{% \csname RIB\i VARWIDTH\endcsname} } \def\loopbody##1##2{% \moveribbons{##2/{% left=\csname RIB##1@oldleft\endcsname,% right=\csname RIB##1@oldright\endcsname,% var width=\csname RIB##1@oldvarwidth\endcsname}% }% } \foreachtwo\loopbody{#1}{#2} } % Move boxes (an alternative to \moveboxes) \newcommand*\extendboxes[1]{% \foreach \i/\values in {#1} { % % If box \i is not already active, raise an error. \@ifListContains{\i}{\@activeBoxes}{}{\errorIX\i} % \pgfmathtruncatemacro\oldleft{\@getBoxLeft\i} \pgfmathtruncatemacro\oldright{\@getBoxRight\i} \def\boxcolor{\@getBoxColor\i} \pgfqkeys{/ribbons/box}{% left=\oldleft,right=\oldright,% color=\boxcolor,execute macro=\values} \ExpandNextThree\@defineBox% \i\ribbons@left\ribbons@right% \ribbons@boxcolor{\@getBoxLabel\i} \pgfmathtruncatemacro\newleft{\@getBoxLeft\i} \pgfmathtruncatemacro\newright{\@getBoxRight\i} \begin{pgfonlayer}{foreground} \begin{scope}[color=\boxcolor, line width=\boxLineWidth] \begin{scope}[xshift=0.5*\boxLineWidth] \@roundedElbow{\boxRoundingRadius}% {\theVCursor+\boxRoundingRadius}{\oldleft}{\newleft} \end{scope} \begin{scope}[xshift=-0.5*\boxLineWidth] \@roundedElbow{\boxRoundingRadius}% {\theVCursor+\boxRoundingRadius}{\oldright}{\newright} \end{scope} \end{scope} \end{pgfonlayer} % % Update i@prevleft and i@prevright \@rememberPrevBox\i % % Put box i in purgatory, and take it out of the list % of active boxes \ExpandNext\@removeFromActiveBoxes\i \cons\@boxPurgatory{\i} } } \newcommand\ribbonpagebreak{% \end{tikzpicture} \pagebreak % Nudge vertical cursor up a bit. This is a hack to % counteract the fact that the first row does not have % any steps in it. Without this hack, the labels in % the first row would be printed too far down. \addtocounter{VCursor}{-\defaultStepHeight}% \xdef\ribbons@stepheight{\defaultStepHeight}% \begin{tikzpicture}[x=1mm,y=-1mm] % \foreach\i in \@activeRibbons { \edef\ribbons@left{\@getRibbonLeft\i} \edef\ribbons@right{\@getRibbonRight\i} \edef\ribbons@varwidth{\@getRibbonVarWidth\i} \edef\ribbons@label{\@getRibbonLabel\i} \edef\ribbons@rotatedlabel{} \@drawRibbonLabel } \foreach\i in \@activeBoxes { \edef\ribbons@left{\@getBoxLeft\i} \edef\ribbons@right{\@getBoxRight\i} \edef\ribbons@boxcolor{\@getBoxColor\i} \edef\ribbons@label{\@getBoxLabel\i} \edef\ribbons@boxhoffset{0} \edef\ribbons@boxvoffset{0} \@drawBoxLabel } % % Finish this row, making it a shorter row than % usual. \addtocounter{VCursor}{\defaultStepHeight}% \finishrow[\numexpr\ribbons@extraheight-\defaultStepHeight] } % The main environment \newenvironment{ribbonproof}[1][]{% % % Create state % % A comma-separated list of active ribbons. Each element is a ribbon % identifier. \gdef\@activeRibbons{}% \gdef\@activeBoxes{}% % % Remember all the ribbons and boxes that we define over % the course of this ribbon proof, so that they can be % undefined at the end. \gdef\@allDefinedRibbons{}% \gdef\@allDefinedBoxes{}% % % Comma-separated lists of the left-positions and right-positions % of block steps \gdef\@blocks@left{}% \gdef\@blocks@right{}% \gdef\@blocks@state{}% \gdef\@blocks@boxstate{}% % % Block steps in purgatory are those that will % be in the proper list from the next row onwards. \gdef\@blocksPurgatory@left{}% \gdef\@blocksPurgatory@right{}% % % Existential boxes in purgatory are those that will % be in the proper list from the next row onwards. \gdef\@boxPurgatory{}% % % List of notches \gdef\notchPositions{}% % % Process keys \pgfqkeys{/ribbons/ribbonproof}{% scale=1,start ribbons={},% extra height=0,start boxes={},#1}% % % Nudge vertical cursor up a bit. This is a hack to % counteract the fact that the first row does not have % any steps in it. Without this hack, the labels in % the first row would be printed too far down. \setcounter{VCursor}{-\defaultStepHeight}% \xdef\ribbons@stepheight{\defaultStepHeight}% % % Make the \\ command a synonym for \finishrow. The % reason for this is mainly to exploit the syntax % highlighting in AucTeX, which emphasises \\ commands. \renewcommand\\{\finishrow}% % % Draw the picture "upside-down", with mm as the units \begin{tikzpicture}[x=1mm,y=-1mm] % % Add the initial ribbons to the list of active % ribbons, and draw their labels \@processStartRibbons \@processStartBoxes % % Prepare the vertical cursor for the first % proper row. \setcounter{VCursor}{0} % % Finish this row, making it a shorter row than % usual. \finishrow[\numexpr\ribbons@extraheight-\defaultStepHeight] }{% \end{tikzpicture}% % % Undefine all the ribbons and boxes that were globally % defined over the course of this picture. \foreach\i in \@allDefinedRibbons{% \undefine{RIB\i LEFT}% \undefine{RIB\i RIGHT}% \undefine{RIB\i VARWIDTH}% }% \foreach\i in \@allDefinedBoxes{% \undefine{BOX\i LEFT}% \undefine{BOX\i RIGHT}% \undefine{BOX\i @color}% }% }