From 604ab2b8e6483eed86c3262618436714f6a721cb Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Mon, 8 Jul 2013 22:01:17 +0000 Subject: ribbonproofs (8jul13) git-svn-id: svn://tug.org/texlive/trunk@31137 c570f23f-e606-0410-a88d-b1316a301751 --- .../tex/latex/ribbonproofs/ribbonproofs.sty | 1696 ++++++++++++++++++++ 1 file changed, 1696 insertions(+) create mode 100644 Master/texmf-dist/tex/latex/ribbonproofs/ribbonproofs.sty (limited to 'Master/texmf-dist/tex/latex/ribbonproofs') diff --git a/Master/texmf-dist/tex/latex/ribbonproofs/ribbonproofs.sty b/Master/texmf-dist/tex/latex/ribbonproofs/ribbonproofs.sty new file mode 100644 index 00000000000..e304a97e8c2 --- /dev/null +++ b/Master/texmf-dist/tex/latex/ribbonproofs/ribbonproofs.sty @@ -0,0 +1,1696 @@ +%% 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}% + }% +} -- cgit v1.2.3