summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/latex
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2023-04-24 20:15:03 +0000
committerKarl Berry <karl@freefriends.org>2023-04-24 20:15:03 +0000
commit02c2da13028e03fce73e05dd7ae264d773c95ca2 (patch)
tree73d540563d552de931fb84b9ec941f33726fece3 /Master/texmf-dist/tex/latex
parent00abb7dcc333bbd02b2d84766f9bd59a8a330d0e (diff)
tikz-nfold (24apr23)
git-svn-id: svn://tug.org/texlive/trunk@66928 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/latex')
-rw-r--r--Master/texmf-dist/tex/latex/tikz-nfold/pgflibrarybezieroffset.code.tex416
-rw-r--r--Master/texmf-dist/tex/latex/tikz-nfold/tikzlibrarynfold.code.tex1462
2 files changed, 1140 insertions, 738 deletions
diff --git a/Master/texmf-dist/tex/latex/tikz-nfold/pgflibrarybezieroffset.code.tex b/Master/texmf-dist/tex/latex/tikz-nfold/pgflibrarybezieroffset.code.tex
index e3391ed92c0..7d9a86312f6 100644
--- a/Master/texmf-dist/tex/latex/tikz-nfold/pgflibrarybezieroffset.code.tex
+++ b/Master/texmf-dist/tex/latex/tikz-nfold/pgflibrarybezieroffset.code.tex
@@ -16,12 +16,6 @@
% This work consists of the files pgflibrarybezieroffset.code.tex,
% tikzlibrarynfold.code.tex, tikz-nfold-doc.tex, and tikz-nfold-doc.pdf.
-
-% stores the current \pgf@x and \pgf@y in #1
-\def\pgfstorepoint#1{\edef#1{\noexpand\pgfpoint{\the\pgf@x}{\the\pgf@y}}}
-% global version in case we need it:
-\def\pgfglobalstorepoint#1{\xdef#1{\noexpand\pgfpoint{\the\pgf@x}{\the\pgf@y}}}
-
% Split a Bezier curve (de Casteljau's algorithm)
% #1 = time (between 0 and 1)
% #2-#5: control points
@@ -32,19 +26,16 @@
% this is partially implemented in some pgf file, possibly decorations or basic paths.
% But maybe I will need the general case in the future, maybe with some advanced fully simple detection.
% Leave it in for now
-\newcommand{\pgf@splitbezier}[5]{
+\def\pgf@splitbezier#1#2#3#4#5{%
% based on pgfcorepoints.code.tex, \pgfpointcurveattime
- \pgfmathparse{#1}%
- \let\pgf@time@s=\pgfmathresult%
- \global\pgf@x=\pgfmathresult pt%
- \global\pgf@x=-\pgf@x%
+ \pgfmathsetmacro\pgf@time@s{#1}%
+ \pgf@x=-\pgf@time@s pt%
\advance\pgf@x by 1pt%
\edef\pgf@time@t{\pgf@sys@tonumber{\pgf@x}}%
% P^0_3
- \pgf@process{#5}%
+ \pgfextract@process\pgf@splitbezier@ii@iv{#5}%
\pgf@xc=\pgf@x%
\pgf@yc=\pgf@y%
- \pgfstorepoint{\pgf@splitbezier@ii@iv}
% P^0_2
\pgf@process{#4}%
\pgf@xb=\pgf@x%
@@ -54,90 +45,123 @@
\pgf@xa=\pgf@x%
\pgf@ya=\pgf@y%
% P^0_0
- \pgf@process{#2}%
- \pgfstorepoint{\pgf@splitbezier@i@i}
+ \pgfextract@process\pgf@splitbezier@i@i{#2}%
% First iteration:
% P^1_0
- \global\pgf@x=\pgf@time@t\pgf@x\global\advance\pgf@x by\pgf@time@s\pgf@xa%
- \global\pgf@y=\pgf@time@t\pgf@y\global\advance\pgf@y by\pgf@time@s\pgf@ya%
- \pgfstorepoint{\pgf@splitbezier@i@ii}
+ \pgf@x=\pgf@time@t\pgf@x\advance\pgf@x by\pgf@time@s\pgf@xa%
+ \pgf@y=\pgf@time@t\pgf@y\advance\pgf@y by\pgf@time@s\pgf@ya%
+ \pgfextract@process\pgf@splitbezier@i@ii{}%
% P^1_1
\pgf@xa=\pgf@time@t\pgf@xa\advance\pgf@xa by\pgf@time@s\pgf@xb%
\pgf@ya=\pgf@time@t\pgf@ya\advance\pgf@ya by\pgf@time@s\pgf@yb%
% P^1_2
\pgf@xb=\pgf@time@t\pgf@xb\advance\pgf@xb by\pgf@time@s\pgf@xc%
\pgf@yb=\pgf@time@t\pgf@yb\advance\pgf@yb by\pgf@time@s\pgf@yc%
- \edef\pgf@splitbezier@ii@iii{\noexpand\pgfpoint{\the\pgf@xb}{\the\pgf@yb}}
+ \edef\pgf@splitbezier@ii@iii{\noexpand\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}%
% P^2_0
- \global\pgf@x=\pgf@time@t\pgf@x\global\advance\pgf@x by\pgf@time@s\pgf@xa%
- \global\pgf@y=\pgf@time@t\pgf@y\global\advance\pgf@y by\pgf@time@s\pgf@ya%
- \pgfstorepoint{\pgf@splitbezier@i@iii}
+ \pgf@x=\pgf@time@t\pgf@x\advance\pgf@x by\pgf@time@s\pgf@xa%
+ \pgf@y=\pgf@time@t\pgf@y\advance\pgf@y by\pgf@time@s\pgf@ya%
+ \pgfextract@process\pgf@splitbezier@i@iii{}%
% P^2_1
\pgf@xa=\pgf@time@t\pgf@xa\advance\pgf@xa by\pgf@time@s\pgf@xb%
\pgf@ya=\pgf@time@t\pgf@ya\advance\pgf@ya by\pgf@time@s\pgf@yb%
- \edef\pgf@splitbezier@ii@ii{\noexpand\pgfpoint{\the\pgf@xa}{\the\pgf@ya}}
+ \edef\pgf@splitbezier@ii@ii{\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}%
% P^3_0
- \global\pgf@x=\pgf@time@t\pgf@x\global\advance\pgf@x by\pgf@time@s\pgf@xa%
- \global\pgf@y=\pgf@time@t\pgf@y\global\advance\pgf@y by\pgf@time@s\pgf@ya%
- \pgfstorepoint{\pgf@splitbezier@i@iv}
- \pgfstorepoint{\pgf@splitbezier@ii@i}
+ \pgf@x=\pgf@time@t\pgf@x\advance\pgf@x by\pgf@time@s\pgf@xa%
+ \pgf@y=\pgf@time@t\pgf@y\advance\pgf@y by\pgf@time@s\pgf@ya%
+ \pgfextract@process\pgf@splitbezier@i@iv{}%
+ \let\pgf@splitbezier@ii@i\pgf@splitbezier@i@iv
}
% computes the cross product and puts it into \pgfmathresult
-\newcommand{\pgfcrossproduct}[2]{
- \pgf@process{#1}%
- \pgf@xa=\pgf@x%
- \pgf@ya=\pgf@y%
- \pgf@process{#2}%
- \pgfmathparse{\pgf@xa*\pgf@y-\pgf@ya*\pgf@x}%
+\def\pgfmathcrossproduct#1#2{%
+ \begingroup
+ \pgf@process{#1}%
+ \pgf@xa=\pgf@x%
+ \pgf@ya=\pgf@y%
+ \pgf@process{#2}%
+ \pgf@y=\pgf@sys@tonumber\pgf@xa\pgf@y
+ \advance\pgf@y by -\pgf@sys@tonumber\pgf@ya\pgf@x
+ \pgfmath@returnone\pgf@y
+ \endgroup
}
-\newcommand{\pgfdotproduct}[2]{
- \pgf@process{#1}%
- \pgf@xa=\pgf@x%
- \pgf@ya=\pgf@y%
- \pgf@process{#2}%
- \pgfmathparse{\pgf@xa*\pgf@x+\pgf@ya*\pgf@y}%
+\def\pgfmathdotproduct#1#2{%
+ \begingroup
+ \pgf@process{#1}%
+ \pgf@xa=\pgf@x%
+ \pgf@ya=\pgf@y%
+ \pgf@process{#2}%
+ \pgf@x=\pgf@sys@tonumber\pgf@xa\pgf@x
+ \advance\pgf@x by \pgf@sys@tonumber\pgf@ya\pgf@y
+ \pgfmath@returnone\pgf@x
+ \endgroup
}
-\newcommand{\pgfcrossdot}[2]{
- \pgf@process{#1}%
- \pgf@xa=\pgf@x%
- \pgf@ya=\pgf@y%
- \pgf@process{#2}%
- \pgfmathsetlengthmacro{\pgf@tmp@dot}{\pgf@xa*\pgf@x+\pgf@ya*\pgf@y}%
- \pgfmathsetlengthmacro{\pgf@tmp@cross}{\pgf@xa*\pgf@y-\pgf@ya*\pgf@x}%
+\def\pgfmathcrossdot#1#2{%
+ \begingroup
+ \pgf@process{#1}%
+ \pgf@xa=\pgf@x%
+ \pgf@ya=\pgf@y%
+ \pgf@process{#2}%
+ \pgf@xb=\pgf@sys@tonumber\pgf@xa\pgf@x
+ \pgf@yb=\pgf@sys@tonumber\pgf@xa\pgf@y
+ \advance\pgf@xb by \pgf@sys@tonumber\pgf@ya\pgf@y
+ \advance\pgf@yb by -\pgf@sys@tonumber\pgf@ya\pgf@x
+ \edef\pgf@temp{%
+ \edef\noexpand\pgf@tmp@dot{\pgf@sys@tonumber\pgf@xb}%
+ \edef\noexpand\pgf@tmp@cross{\pgf@sys@tonumber\pgf@yb}%
+ }%
+ \expandafter
+ \endgroup\pgf@temp
}
-
+% Calculates abs(\pgf@x) + abs(\pgf@y) in #1
+\def\pgfpointtaxicabnorm#1{%
+ \ifdim\pgf@x<0pt
+ #1=-\pgf@x
+ \else
+ #1=\pgf@x
+ \fi
+ \ifdim\pgf@y<0pt
+ \advance#1 by -\pgf@y
+ \else
+ \advance#1 by \pgf@y
+ \fi
+}
% Computes the normalised tangents of a given Bezier curve and stores them in \pgf@tmp@tang@i and \pgf@tmp@tang@ii.
+% Also computes the angles and stores them in \pgf@tmp@angle@i and \pgf@tmp@angle@ii.
% All degenerate cases are covered. For a triple degenerate curve (all points equal), the vector (1,0) is returned.
-\newcommand{\pgf@offset@compute@tangents}[4]{
- \pgfpointdiff{#1}{#2} % unintuitively, this is PTii - PTi
- \pgfmathparse{abs(\pgf@x) + abs(\pgf@y)}
- \ifdim\pgfmathresult pt<0.1pt\relax
+\def\pgf@offset@compute@tangents#1#2#3#4{%
+ \pgf@process{\pgfpointdiff{#1}{#2}}% unintuitively, this is PTii - PTi
+ \pgfpointtaxicabnorm\pgf@xa
+ \ifdim\pgf@xa<0.1pt\relax
% edge case: first point and first control point are equal
- \pgfpointdiff{#1}{#3}
- \pgfmathparse{abs(\pgf@x) + abs(\pgf@y)}
- \ifdim\pgfmathresult pt<0.1pt\relax
+ \pgf@process{\pgfpointdiff{#1}{#3}}%
+ \pgfpointtaxicabnorm\pgf@xa
+ \ifdim\pgf@xa<0.1pt\relax
% edge case: first three points are equal
- \pgfpointdiff{#1}{#4}
+ \pgf@process{\pgfpointdiff{#1}{#4}}%
\fi
\fi
- \pgfpointnormalised{}
- \pgfstorepoint\pgf@tmp@tang@i
- \pgfpointdiff{#3}{#4}
- \pgfmathparse{abs(\pgf@x) + abs(\pgf@y)}
- \ifdim\pgfmathresult pt<0.1pt\relax
- \pgfpointdiff{#2}{#4}
- \pgfmathparse{abs(\pgf@x) + abs(\pgf@y)}
- \ifdim\pgfmathresult pt<0.1pt\relax
- \pgfpointdiff{#1}{#4}
+ \pgfextract@process\pgf@tmp@tang@i{%
+ \pgfpointnormalised{}%
+ % \pgfpointnormalised stores the angle of the vector in \pgf@tmp
+ \global\let\pgf@nfold@tmp\pgf@tmp%
+ }%
+ \let\pgf@tmp@angle@i\pgf@nfold@tmp%
+ \pgf@process{\pgfpointdiff{#3}{#4}}%
+ \pgfpointtaxicabnorm\pgf@xa
+ \ifdim\pgf@xa<0.1pt\relax
+ \pgf@process{\pgfpointdiff{#2}{#4}}%
+ \pgfpointtaxicabnorm\pgf@xa
+ \ifdim\pgf@xa<0.1pt\relax
+ \pgf@process{\pgfpointdiff{#1}{#4}}%
\fi
\fi
- \pgfpointnormalised{}
- \pgfstorepoint\pgf@tmp@tang@ii
+ \pgfextract@process\pgf@tmp@tang@ii{\pgfpointnormalised{}\global\let\pgf@nfold@tmp\pgf@tmp}%
+ \let\pgf@tmp@angle@ii\pgf@nfold@tmp%
}
@@ -145,73 +169,89 @@
% Offsetting a simple section %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-\newcommand{\pgf@offset@bezier@segment}[5]{%
- % TODO would it make sense to use \pgf@process here?
+\def\pgf@offset@bezier@segment#1#2#3#4#5{%
% normalise tangents and normals; this avoids overflow issues later, and we need
% the normal vector to be of length 1 anyway
- \pgf@offset@compute@tangents{#1}{#2}{#3}{#4}
+ \pgf@offset@compute@tangents{#1}{#2}{#3}{#4}%
% offset A1
% compute the normal
\pgf@tmp@tang@i
- \pgf@xa=\pgf@x
- \pgf@x=-\pgf@y
- \pgf@y=\pgf@xa
- \pgfstorepoint\pgf@tmp@normal@i
- % Leaving this parameter empty amounts to working directly on the register
- \pgfpointadd{\pgfpointscale{#5}{}}{#1}
- \pgfstorepoint{\pgf@bezier@offset@i}
+ \edef\pgf@tmp@normal@i{\noexpand\pgfqpoint{-\the\pgf@y}{\the\pgf@x}}%
+ \pgfextract@process\pgf@bezier@offset@i
+ {\pgfpointadd{\pgfpointscale{#5}{\pgf@tmp@normal@i}}{#1}}%
% offset A4
\pgf@tmp@tang@ii
- \pgf@xa=\pgf@x
- \pgf@x=-\pgf@y
- \pgf@y=\pgf@xa
- \pgfstorepoint\pgf@tmp@normal@ii
- \pgfpointadd{\pgfpointscale{#5}{}}{#4}
- \pgfstorepoint\pgf@bezier@offset@iv
+ \edef\pgf@tmp@normal@ii{\noexpand\pgfqpoint{-\the\pgf@y}{\the\pgf@x}}%
+ \pgfextract@process\pgf@bezier@offset@iv
+ {\pgfpointadd{\pgfpointscale{#5}{\pgf@tmp@normal@ii}}{#4}}%
% now compute A'_2 and A'_3
- \pgfpointdiff{#1}{#4}
- \pgfmathsetmacro{\pgf@tmp@secantlen}{veclen(\pgf@x,\pgf@y)}
+ \pgf@process{\pgfpointdiff{#1}{#4}}%
+ \pgfmathveclen@{\pgf@sys@tonumber\pgf@x}{\pgf@sys@tonumber\pgf@y}%
+ \let\pgf@tmp@secantlen\pgfmathresult
\ifdim\pgf@tmp@secantlen pt<0.1pt\relax
% Edge case: Either the curve is degenerate to a point or it is not simple.
% Either way we offset A1 and A4, and preserve the vectors A1A2 and A3A4.
- \pgfwarning{pgf-offset: first and last point are too close, expect glitches}
- \pgfpointadd{\pgf@bezier@offset@i}{\pgfpointdiff{#1}{#2}}
- \pgfstorepoint\pgf@bezier@offset@ii
- \pgfpointadd{\pgf@bezier@offset@iv}{\pgfpointdiff{#4}{#3}}
- \pgfstorepoint\pgf@bezier@offset@iii
+ \pgfutil@packagewarning{tikz-nfold}{first and last point are too close, expect glitches}%
+ \pgfextract@process\pgf@bezier@offset@ii
+ {\pgfpointadd{\pgf@bezier@offset@i}{\pgfpointdiff{#1}{#2}}}%
+ \pgfextract@process\pgf@bezier@offset@iii
+ {\pgfpointadd{\pgf@bezier@offset@iv}{\pgfpointdiff{#4}{#3}}}%
\else
- \pgfpointnormalised{}
- \pgfstorepoint\pgf@tmp@secant
- \pgfcrossdot{}{\pgf@tmp@tang@ii}
- \ifdim\pgf@tmp@dot<.5pt\relax%
+ \pgfextract@process\pgf@tmp@secant{\pgfpointnormalised{}}%
+ \pgfmathcrossdot{}{\pgf@tmp@tang@ii}%
+ \ifdim\pgf@tmp@dot pt<.5pt\relax%
% this can only happen in non-simple curves
- \pgfwarning{pgf-offset: cosine of \pgf@tmp@dot\space clamped to 0.5 in non-simple segment}%
- \def\pgf@tmp@dot{.5pt}%
+ \pgfutil@packagewarning{tikz-nfold}{cosine of \pgf@tmp@dot\space clamped to 0.5 in non-simple segment}%
+ \def\pgf@tmp@dot{.5}%
\fi%
- \pgfmathsetmacro{\pgf@tmp@tanbeta}{\pgf@tmp@cross/\pgf@tmp@dot}%
- \pgfcrossdot{\pgf@tmp@secant}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}
+ \pgfmathdivide@{\pgf@tmp@cross}{\pgf@tmp@dot}%
+ \let\pgf@tmp@tanbeta\pgfmathresult
+ \pgfmathcrossdot{\pgf@tmp@secant}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}
% There are cases where we want #5/secantlen to be quite large, so we should not clamp the value here
- \pgfmathparse{1 + #5/\pgf@tmp@secantlen*(\pgf@tmp@cross - \pgf@tmp@dot*\pgf@tmp@tanbeta)}
- \pgfpointadd%
- {\pgf@bezier@offset@i}%
- {\pgfpointscale{\pgfmathresult pt}{\pgfpointdiff{#1}{#2}}}%
- \pgfstorepoint\pgf@bezier@offset@ii
+ % \pgfmathparse{1 + #5/\pgf@tmp@secantlen*(\pgf@tmp@cross - \pgf@tmp@dot*\pgf@tmp@tanbeta)}%
+ \pgfmath@offset@calculate@scale{\pgf@tmp@secantlen}{\pgf@tmp@cross}{\pgf@tmp@dot}{\pgf@tmp@tanbeta}{#5}%
+ \pgfextract@process\pgf@bezier@offset@ii{%
+ \pgfpointadd
+ {\pgf@bezier@offset@i}
+ {\pgfqpointscale{\pgfmathresult}{\pgfpointdiff{#1}{#2}}}%
+ }%
% third control point
- \pgfcrossdot{\pgf@tmp@secant}{\pgf@tmp@tang@i}
- \ifdim\pgf@tmp@dot<.5pt\relax%
- \pgfwarning{pgf-offset: cosine of \pgf@tmp@dot\space clamped to 0.5 in non-simple segment}%
- \def\pgf@tmp@dot{.5pt}%
- \fi%
- \pgfmathsetmacro{\pgf@tmp@tanbeta}{\pgf@tmp@cross/\pgf@tmp@dot}%
- \pgfcrossdot{\pgf@tmp@secant}{\pgfpointnormalised{\pgfpointdiff{#4}{#3}}}%
- \pgfmathparse{1 + #5/\pgf@tmp@secantlen*(\pgf@tmp@cross - \pgf@tmp@dot*\pgf@tmp@tanbeta)}%
- \pgfpointadd%
- {\pgf@bezier@offset@iv}%
- {\pgfpointscale{\pgfmathresult pt}{\pgfpointdiff{#4}{#3}}}%
- \pgfstorepoint\pgf@bezier@offset@iii%
+ \pgfmathcrossdot{\pgf@tmp@secant}{\pgf@tmp@tang@i}%
+ \ifdim\pgf@tmp@dot pt<.5pt\relax
+ \pgfutil@packagewarning{tikz-nfold}{cosine of \pgf@tmp@dot\space clamped to 0.5 in non-simple segment}%
+ \def\pgf@tmp@dot{.5}%
+ \fi
+ \pgfmathdivide@{\pgf@tmp@cross}{\pgf@tmp@dot}%
+ \let\pgf@tmp@tanbeta\pgfmathresult
+ \pgfmathcrossdot{\pgf@tmp@secant}{\pgfpointnormalised{\pgfpointdiff{#4}{#3}}}%
+ % \pgfmathparse{1 + #5/\pgf@tmp@secantlen*(\pgf@tmp@cross - \pgf@tmp@dot*\pgf@tmp@tanbeta)}%
+ \pgfmath@offset@calculate@scale{\pgf@tmp@secantlen}{\pgf@tmp@cross}{\pgf@tmp@dot}{\pgf@tmp@tanbeta}{#5}%
+ \pgfextract@process\pgf@bezier@offset@iii{%
+ \pgfpointadd
+ {\pgf@bezier@offset@iv}
+ {\pgfqpointscale{\pgfmathresult}{\pgfpointdiff{#4}{#3}}}%
+ }%
\fi
}
+% calculates 1+#5/#1*(#2-#3*#4)
+% #1 = secantlen
+% #2 = cross
+% #3 = dot
+% #4 = tanbeta
+% #5 = #5 (offset)
+\def\pgfmath@offset@calculate@scale#1#2#3#4#5{%
+ \begingroup
+ \pgfmathmultiply@{#3}{#4}%
+ \pgfmathsubtract@{#2}{\pgfmathresult}%
+ \let\pgfmath@temp\pgfmathresult
+ \pgfmathreciprocal@{#1}%
+ \pgfmathmultiply@{\pgfmathresult}{\pgfmath@temp}%
+ \pgfmathmultiply{\pgfmathresult}{#5}%
+ \pgfmathadd@{\pgfmathresult}{1}%
+ \pgfmath@smuggleone\pgfmathresult
+ \endgroup
+}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Subdividing and offsetting %
@@ -225,35 +265,42 @@
%
% Subdivides a Bezier curve into "simple" segments (according to the definition below),
% offsets the segments, and draws them. Because offsetting also involves relocating
-% the starting points, these macros come in two variants: with and without a \pgfmoveto{}
+% the starting points, these macros come in two variants: with and without a \pgfpathmoveto{}
% to the new starting point.
%
% Interface:
% #1-#4: control points of the whole Bezier curve
% #5: offset
-\newcommand{\pgfoffsetcurve}[5]{%
- \pgf@subdivideandoffsetcurve{#1}{#2}{#3}{#4}{#5}{\pgf@offset@max@recursion}{0}{\pgf@nfold@callback@move}%
+% \def\pgfoffsetcurve#1#2#3#4#5{%
+% \pgf@subdivideandoffsetcurve{#1}{#2}{#3}{#4}{#5}{\pgf@offset@max@recursion}{0}{\pgf@nfold@callback@move}%
+% }
+% \def\pgfoffsetcurvenomove#1#2#3#4#5{%
+% \pgf@subdivideandoffsetcurve{#1}{#2}{#3}{#4}{#5}{\pgf@offset@max@recursion}{0}{\pgf@nfold@callback@nomove}%
+% }
+
+\def\pgfoffsetcurve#1#2#3#4#5{%
+ \pgfoffsetcurvecallback{#1}{#2}{#3}{#4}{#5}{\pgf@nfold@callback@move}%
}
-\newcommand{\pgfoffsetcurvenomove}[5]{%
- \pgf@subdivideandoffsetcurve{#1}{#2}{#3}{#4}{#5}{\pgf@offset@max@recursion}{0}{\pgf@nfold@callback@nomove}%
+\def\pgfoffsetcurvenomove#1#2#3#4#5{%
+ \pgfoffsetcurvecallback{#1}{#2}{#3}{#4}{#5}{\pgf@nfold@callback@nomove}%
}
% Arguments:
% #1-#4: control points of the segment
% #5: =0 if this is the first segment of the curve, =1 otherwise
% (checking for #5=0 allows us to draw the curve without interruptions)
-\newcommand{\pgf@nfold@callback@move}[5]{%
+\def\pgf@nfold@callback@move#1#2#3#4#5{%
\ifnum#5=0\relax\pgfpathmoveto{#1}\fi%
\pgfpathcurveto{#2}{#3}{#4}%
}
% this version never does a moveto at the start. Useful for drawing a path consisting of
% multiple Bezier curves.
-\newcommand{\pgf@nfold@callback@nomove}[5]{\pgfpathcurveto{#2}{#3}{#4}}
+\def\pgf@nfold@callback@nomove#1#2#3#4#5{\pgfpathcurveto{#2}{#3}{#4}}
% Like the previous macro, but with a custom callback macro for each segment instead of
% executing \drawsegment as defined above. See \drawsegment for the arguments.
-\newcommand{\pgfoffsetcurvecallback}[6]{%
+\def\pgfoffsetcurvecallback#1#2#3#4#5#6{%
\pgf@subdivideandoffsetcurve{#1}{#2}{#3}{#4}{#5}{\pgf@offset@max@recursion}{0}{#6}%
}
@@ -265,66 +312,73 @@
% #7: =0 if this is the start of the curve, =1 otherwise;
% #8: callback for output (see above)
\newif\ifpgf@offset@subdivide
-\newcommand{\pgf@subdivideandoffsetcurve}[8]{%
+\def\pgf@subdivideandoffsetcurve#1#2#3#4#5#6#7#8{%
% we need a group to avoid overwriting variables in recursive calls
\begingroup%
- \pgf@offset@subdividefalse%
- \c@pgf@counta=#6%
- \advance\c@pgf@counta by-1%
- \pgf@process{#1}\pgfstorepoint{\pgf@ctrl@i}%
- \pgf@process{#2}\pgfstorepoint{\pgf@ctrl@ii}%
- \pgf@process{#3}\pgfstorepoint{\pgf@ctrl@iii}%
- \pgf@process{#4}\pgfstorepoint{\pgf@ctrl@iv}%
- % Use the non-degenerate tangents for the simplicity check
- \pgf@offset@compute@tangents{\pgf@ctrl@i}{\pgf@ctrl@ii}{\pgf@ctrl@iii}{\pgf@ctrl@iv}%
- \pgfpointdiff{\pgf@ctrl@i}{\pgf@ctrl@iv}\pgfstorepoint{\pgf@itoiv}%
- \pgfcrossproduct{\pgf@itoiv}{\pgf@tmp@tang@i}%
- \edef\firstcross{\pgfmathresult}%
- \pgfcrossproduct{\pgf@itoiv}{\pgf@tmp@tang@ii}%
- % First simplicity check: Are A2 and A3 on the same side of the A1-A4 line?
- % -> compute the sign of the cross products, use the sign function to avoid overflows
- \pgfmathparse{sign(\firstcross)*sign(\pgfmathresult)}%
- \ifdim\pgfmathresult pt>0pt\relax%
- \pgf@offset@subdividetrue%
- \else%
- % Second simplicity check: How large is the angle between the tangents in A1 and A4?
- \pgfdotproduct{\pgf@tmp@tang@i}{\pgf@tmp@tang@ii}%
- \ifdim\pgfmathresult pt<.5pt\relax%
+ \pgf@offset@subdividefalse%
+ \c@pgf@counta=#6\relax
+ \advance\c@pgf@counta by -1
+ \pgfextract@process\pgf@ctrl@i{#1}%
+ \pgfextract@process\pgf@ctrl@ii{#2}%
+ \pgfextract@process\pgf@ctrl@iii{#3}%
+ \pgfextract@process\pgf@ctrl@iv{#4}%
+ % Use the non-degenerate tangents for the simplicity check
+ \pgf@offset@compute@tangents{\pgf@ctrl@i}{\pgf@ctrl@ii}{\pgf@ctrl@iii}{\pgf@ctrl@iv}%
+ \pgfextract@process\pgf@itoiv{\pgfpointdiff{\pgf@ctrl@i}{\pgf@ctrl@iv}}%
+ \pgfmathcrossproduct{\pgf@itoiv}{\pgf@tmp@tang@i}%
+ \let\firstcross\pgfmathresult
+ \pgfmathcrossproduct{\pgf@itoiv}{\pgf@tmp@tang@ii}%
+ % First simplicity check: Are A2 and A3 on the same side of the A1-A4 line?
+ % -> compute the sign of the cross products, use the sign function to avoid overflows
+ % just give it a pass if one of them is zero, hence 2 and 3 at the end
+ \ifnum
+ \ifdim \firstcross pt<0pt -1\else\ifdim \firstcross pt>0pt 1\else 2\fi\fi
+ =\ifdim\pgfmathresult pt<0pt -1\else\ifdim\pgfmathresult pt>0pt 1\else 3\fi\fi
+ \relax % the \relax is important!
\pgf@offset@subdividetrue%
- \else
- % Third simplicity check: Put a limit on the lengths of the i-ii and iii-iv vectors combined
- \pgf@itoiv
- \pgfmathsetmacro{\pgf@tmp@len@i@iv}{veclen(\pgf@x,\pgf@y)}
- \pgfpointdiff{\pgf@ctrl@i}{\pgf@ctrl@ii}
- \pgf@xb=\pgf@x\pgf@yb=\pgf@y
- \pgfpointdiff{\pgf@ctrl@iii}{\pgf@ctrl@iv}
- \pgfmathparse{\pgf@tmp@len@i@iv < veclen(\pgf@xb,\pgf@yb) + veclen(\pgf@x,\pgf@y)}
- \ifnum\pgfmathresult=1\relax
+ \else%
+ % Second simplicity check: How large is the angle between the tangents in A1 and A4?
+ \pgfmathdotproduct{\pgf@tmp@tang@i}{\pgf@tmp@tang@ii}%
+ \ifdim\pgfmathresult pt<.5pt\relax%
\pgf@offset@subdividetrue%
- \fi
+ \else
+ % Third simplicity check: Put a limit on the lengths of the i-ii and iii-iv vectors combined
+ \pgf@itoiv
+ \pgfmathveclen@{\pgf@sys@tonumber\pgf@x}{\pgf@sys@tonumber\pgf@y}%
+ \pgf@xa=\pgfmathresult pt
+ \pgf@process{\pgfpointdiff{\pgf@ctrl@i}{\pgf@ctrl@ii}}%
+ \pgfmathveclen@{\pgf@sys@tonumber\pgf@x}{\pgf@sys@tonumber\pgf@y}%
+ \pgf@xb=\pgfmathresult pt
+ \pgf@process{\pgfpointdiff{\pgf@ctrl@iii}{\pgf@ctrl@iv}}%
+ \pgfmathveclen@{\pgf@sys@tonumber\pgf@x}{\pgf@sys@tonumber\pgf@y}%
+ \advance\pgf@xb by \pgfmathresult pt
+ % \veclen(itoiv) < veclen(ii-i) + veclen(iv-iii)
+ \ifdim\pgf@xa<\pgf@xb
+ \pgf@offset@subdividetrue
+ \fi
+ \fi%
\fi%
- \fi%
- \ifpgf@offset@subdivide%
- \ifnum\c@pgf@counta<0%
- % We hit the recursion limit but the segment is not simple
- \pgfwarning{pgf-offset: Recursion limit reached, glitches may occur. %
- Consider increasing \string\pgf@offset@max@recursion}%
- % Try to offset the curve anyway. The result will not be precise,
- % but the code is sufficiently robust to not crash
+ \ifpgf@offset@subdivide%
+ \ifnum\c@pgf@counta<0%
+ % We hit the recursion limit but the segment is not simple
+ \pgfutil@packagewarning{tikz-nfold}{Recursion limit reached, glitches may occur. %
+ Consider increasing \string\pgf@offset@max@recursion}%
+ % Try to offset the curve anyway. The result will not be precise,
+ % but the code is sufficiently robust to not crash
+ \pgf@offset@bezier@segment{\pgf@ctrl@i}{\pgf@ctrl@ii}{\pgf@ctrl@iii}{\pgf@ctrl@iv}{#5}%
+ #8{\pgf@bezier@offset@i}{\pgf@bezier@offset@ii}{\pgf@bezier@offset@iii}{\pgf@bezier@offset@iv}{#7}%
+ \else
+ % split the non-simple segment and execute recursive calls
+ \pgf@splitbezier{.5}{\pgf@ctrl@i}{\pgf@ctrl@ii}{\pgf@ctrl@iii}{\pgf@ctrl@iv}%
+ % pass on the "start of the curve flag" only to the first term
+ \pgf@subdivideandoffsetcurve{\pgf@splitbezier@i@i}{\pgf@splitbezier@i@ii}{\pgf@splitbezier@i@iii}{\pgf@splitbezier@i@iv}{#5}{\c@pgf@counta}{#7}{#8}%
+ \pgf@subdivideandoffsetcurve{\pgf@splitbezier@ii@i}{\pgf@splitbezier@ii@ii}{\pgf@splitbezier@ii@iii}{\pgf@splitbezier@ii@iv}{#5}{\c@pgf@counta}{1}{#8}%
+ \fi%
+ \else%
+ % curve is simple
\pgf@offset@bezier@segment{\pgf@ctrl@i}{\pgf@ctrl@ii}{\pgf@ctrl@iii}{\pgf@ctrl@iv}{#5}%
#8{\pgf@bezier@offset@i}{\pgf@bezier@offset@ii}{\pgf@bezier@offset@iii}{\pgf@bezier@offset@iv}{#7}%
- \else
- % split the non-simple segment and execute recursive calls
- \pgf@splitbezier{.5}{\pgf@ctrl@i}{\pgf@ctrl@ii}{\pgf@ctrl@iii}{\pgf@ctrl@iv}%
- % pass on the "start of the curve flag" only to the first term
- \pgf@subdivideandoffsetcurve{\pgf@splitbezier@i@i}{\pgf@splitbezier@i@ii}{\pgf@splitbezier@i@iii}{\pgf@splitbezier@i@iv}{#5}{\c@pgf@counta}{#7}{#8}%
- \pgf@subdivideandoffsetcurve{\pgf@splitbezier@ii@i}{\pgf@splitbezier@ii@ii}{\pgf@splitbezier@ii@iii}{\pgf@splitbezier@ii@iv}{#5}{\c@pgf@counta}{1}{#8}%
\fi%
- \else%
- % curve is simple
- \pgf@offset@bezier@segment{\pgf@ctrl@i}{\pgf@ctrl@ii}{\pgf@ctrl@iii}{\pgf@ctrl@iv}{#5}%
- #8{\pgf@bezier@offset@i}{\pgf@bezier@offset@ii}{\pgf@bezier@offset@iii}{\pgf@bezier@offset@iv}{#7}%
- \fi%
\endgroup%
}
@@ -336,16 +390,16 @@
% For convenience we also provide macros that offset straight lines. These also come in two variants
% similar to the macros for curves.
%
-\newcommand{\pgfoffsetline}[3]{
- \pgfpointscale{#3}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}
+\def\pgfoffsetline#1#2#3{%
+ \pgfpointscale{#3}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}%
\pgf@xc=-\pgf@y
\pgf@yc=\pgf@x
- \pgfpathmoveto{\pgfpointadd{#1}{\pgfqpoint{\pgf@xc}{\pgf@yc}}}
- \pgfpathlineto{\pgfpointadd{#2}{\pgfqpoint{\pgf@xc}{\pgf@yc}}}
+ \pgfpathmoveto{\pgfpointadd{#1}{\pgfqpoint{\pgf@xc}{\pgf@yc}}}%
+ \pgfpathlineto{\pgfpointadd{#2}{\pgfqpoint{\pgf@xc}{\pgf@yc}}}%
}
-\newcommand{\pgfoffsetlinenomove}[3]{
- \pgfpointscale{#3}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}
+\def\pgfoffsetlinenomove#1#2#3{%
+ \pgfpointscale{#3}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}%
\pgf@xc=-\pgf@y
\pgf@yc=\pgf@x
- \pgfpathlineto{\pgfpointadd{#2}{\pgfqpoint{\pgf@xc}{\pgf@yc}}}
+ \pgfpathlineto{\pgfpointadd{#2}{\pgfqpoint{\pgf@xc}{\pgf@yc}}}%
}
diff --git a/Master/texmf-dist/tex/latex/tikz-nfold/tikzlibrarynfold.code.tex b/Master/texmf-dist/tex/latex/tikz-nfold/tikzlibrarynfold.code.tex
index 8013d423f6c..75c3cdc0d37 100644
--- a/Master/texmf-dist/tex/latex/tikz-nfold/tikzlibrarynfold.code.tex
+++ b/Master/texmf-dist/tex/latex/tikz-nfold/tikzlibrarynfold.code.tex
@@ -16,12 +16,10 @@
% This work consists of the files pgflibrarybezieroffset.code.tex,
% tikzlibrarynfold.code.tex, tikz-nfold-doc.tex, and tikz-nfold-doc.pdf.
-\usetikzlibrary{decorations}
\usetikzlibrary{arrows.meta}
-%\usepgflibrary{bezieroffset}
\usepgflibrary{bezieroffset}
-%
+
%
% Intercepting join settings
% --------------------------
@@ -63,63 +61,66 @@
% Various helper commands
% -----------------------
-% check if a decoration segment is visible, i.e. not moveto or last
+% check if a segment is visible, i.e. not moveto or last
\newif\ifpgf@nfold@segm@visible
-\def\ifpgfsegmentvisible#1{%
+\def\checkpgfsegmentvisible#1{%
\pgf@nfold@segm@visiblefalse%
- \ifx#1\pgfdecorationinputsegmentlineto%
- \pgf@nfold@segm@visibletrue\fi%
- \ifx#1\pgfdecorationinputsegmentcurveto%
- \pgf@nfold@segm@visibletrue\fi%
- \ifx#1\pgfdecorationinputsegmentclosepath%
- \pgf@nfold@segm@visibletrue\fi%
- \ifpgf@nfold@segm@visible%
+ \ifx#1\pgf@nfold@inputsegmentlineto%
+ \pgf@nfold@segm@visibletrue%
+ \else\ifx#1\pgf@nfold@inputsegmentcurveto%
+ \pgf@nfold@segm@visibletrue
+ \else\ifx#1\pgf@nfold@inputsegmentclosepath%
+ \pgf@nfold@segm@visibletrue
+ \fi\fi\fi
}
+%
% Joining offset lines
% --------------------
%
% One of the more difficult aspects is joining the segments of an offset path. Without this step,
% the path would be interrupted or self-intersect whenever there is a non-zero angle between
-% two segments. This code reproduces the existing line joins "bevel", "miter" and "round" as well
-% as possible.
+% two segments. This code reproduces the existing line joins "bevel", "miter" and "round".
%
\def\pgf@nfold@miterjoin{
- % - Although it isn't obvious, this code is invariant under
- % \pgf@nfold@deltaphi -> \pgf@nfold@deltaphi + 2pi
- % - The tip of the miter join is computed starting from the unshifted point and then moving
+ % The tip of the miter join is computed starting from the original (unshifted) centre of the join;
+ % we then move orthorgonal to the average of the old and new angle
\pgfpointadd%
- {\pgf@decorate@inputsegment@first}%
+ {\pgf@nfold@inputsegment@first}%
{\pgfpointpolar%
- {\pgf@nfold@cached@endangle+.5*\pgf@nfold@deltaphi+90}%
- {\pgf@shiftdec@amount/cos(.5*\pgf@nfold@deltaphi)}%
+ {\pgf@nfold@previous@endangle+.5*\pgf@nfold@deltaphi@start+90}% do not change
+ {\pgf@nfold@shiftamount/cos(.5*\pgf@nfold@deltaphi@start)}%
}%
\pgfpathlineto{}%
}
\def\pgf@nfold@beveljoin{
- % The subsequent formula needs deltaphi to be between -180 and 180
- \pgfmathsetmacro{\pgf@nfold@deltaphi}{Mod(\pgf@nfold@deltaphi + 180, 360) - 180}
- % For a good-looking result, inside lines should have a constant distance from each other all the way
- % through the bevel join, which is a non-trivial condition. To achieve this result, the outer lines get
- % a bevel-like join and the inner lines get a miter-like join; the threshold depends on the angle.
+ % The bevel join for one component line consists of three parts:
+ % 1) an extension of the ingoing line,
+ % 2) a middle line, angled at the average of the incoming and outgoing line,
+ % 3) an extension of the outgoing line.
+ % Different components of the ingoing and outgoing lines have a constant distance from each other.
+ % For a good-looking output, the mittle parts of the component lines thus should also have a constant distance,
+ % which is a non-trivial condition. To generate such an output the outer lines get a bevel-like join
+ % and the inner lines get a miter-like join; the threshold depends on deltaphi.
% We first compute by how much the outermost line must be continued from the beginning of the join.
% The protrusion amount must be lowered by a little bit for a rather complicated reason: The offset would be
% dead on if the outermost offset line were centered on the _edge_ of the wide line, but we want to draw
% the outside line _fully inside_ the wide line. The factor of tan(deltaphi/4) can be derived, but is not obvious.
- \pgfmathsetmacro{\bevelouterprotrusion}{\pgf@nfold@shortenforjoin - .5*\pgflinewidth*abs(tan(.25*\pgf@nfold@deltaphi))}
+ \pgfmathsetlengthmacro{\bevelouterprotrusion}%
+ {\pgf@nfold@shortenstartjoin - .5*\pgflinewidth*abs(tan(.25*\pgf@nfold@deltaphi@start))}
% The following applies to middle lines only: We compute by how much they need to be shortened so the distance
% between the lines in the join is correct.
- \pgfmathsetmacro{\bevelshorten}{2*\insidepercentage*abs(tan(.25*\pgf@nfold@deltaphi))}
- % This theshold decides if the inside line is a bevel or a miter
- \pgfmathparse{\bevelshorten < abs(sin(.5*\pgf@nfold@deltaphi))}
+ \pgfmathsetmacro{\bevelshorten}{2*\insidepercentage*abs(tan(.25*\pgf@nfold@deltaphi@start))}
+ % This threshold decides if the inside line has a bevel or a miter join
+ \pgfmathparse{\bevelshorten < abs(sin(.5*\pgf@nfold@deltaphi@start))}
\ifnum\pgfmathresult=1\relax
- \pgfmathsetmacro{\bevelextension}{\bevelouterprotrusion-\bevelshorten*\pgf@decoration@nfold@hwidth}
- \pgfpointadd{\pgf@nfold@offset@start}{\pgfpointpolar{\pgf@nfold@cached@endangle}{\bevelextension}}
+ \pgfmathsetlengthmacro{\bevelextension}{\bevelouterprotrusion-\bevelshorten*\pgf@nfold@hwidth}
+ \pgfpointadd{\pgf@nfold@join@start}{\pgfpointpolar{\pgf@nfold@previous@endangle}{\bevelextension}}
\pgfpathlineto{}
- \pgfpointadd{\pgf@nfold@offset@end}{\pgfpointpolar{\pgfdecoratedinputsegmentstartangle}{-\bevelextension}}
+ \pgfpointadd{\pgf@nfold@join@end}{\pgfpointpolar{\pgf@nfold@cur@startangle}{-\bevelextension}}
\pgfpathlineto{}
\else
\pgf@nfold@miterjoin
@@ -129,49 +130,63 @@
\def\pgf@nfold@roundjoin{
% The outer half of the lines get arcs, the others get miters
\ifdim\insidepercentage pt<.5pt\relax
- \pgfpointadd{\pgf@nfold@offset@start}{\pgfpointpolar{\pgf@nfold@cached@endangle}{\pgf@nfold@shortenforjoin}}
+ \pgfpointadd{\pgf@nfold@join@start}{\pgfpointpolar{\pgf@nfold@previous@endangle}{\pgf@nfold@shortenstartjoin}}
\pgfpathlineto{}
% Check if the angles are in the correct range; under some conditions we must add or subtract 360
- \pgfmathparse{\turnindicator*(\pgfdecoratedinputsegmentstartangle-\pgf@nfold@cached@endangle)}
+ \pgfmathparse{\turnindicator*(\pgf@nfold@cur@startangle-\pgf@nfold@previous@endangle)}
\ifdim\pgfmathresult pt>0pt\relax
- \pgfmathsetmacro{\targetang}{\pgfdecoratedinputsegmentstartangle-\turnindicator*360}
+ \pgfmathsetmacro{\targetang}{\pgf@nfold@cur@startangle-\turnindicator*360}
\else
- \let\targetang\pgfdecoratedinputsegmentstartangle
+ \let\targetang\pgf@nfold@cur@startangle
\fi
\pgfpatharc%
- {\pgf@nfold@cached@endangle+90*\turnindicator}%
+ {\pgf@nfold@previous@endangle+90*\turnindicator}%
{\targetang+90*\turnindicator}%
- {abs(\pgf@shiftdec@fraction)*\pgf@decoration@nfold@hwidth}%
+ {abs(\pgf@nfold@shift@fraction)*\pgf@nfold@hwidth}%
\else
\pgf@nfold@miterjoin
\fi
}
\def\pgf@nfold@make@join{
- % The code must be invariant under deltaphi -> deltaphi + 360, which can be verified experimentally
- \pgfmathsetmacro{\pgf@nfold@deltaphi}{\pgfdecoratedinputsegmentstartangle-\pgf@nfold@cached@endangle}
- % Offset the start and end of this segment
- \pgfpointadd{\pgf@nfold@cached@endpoint}{\pgfpointpolar{\pgf@nfold@cached@endangle+90}{\pgf@shiftdec@amount}}
- \pgfstorepoint\pgf@nfold@offset@start
- \pgfpointadd{\pgf@nfold@segment@start}{\pgfpointpolar{\pgfdecoratedinputsegmentstartangle+90}{\pgf@shiftdec@amount}}
- \pgfstorepoint\pgf@nfold@offset@end
- \pgfpointdiff{\pgf@nfold@offset@start}{\pgf@nfold@offset@end}
- % Check if the start of this segment is too close to the end of the previous segment.
- % In that case we don't insert a join segment, as it would look rather glitchy.
- % We use the Manhattan length for performance and to avoid 'dimension too large' errors.
- \pgfmathparse{abs(\pgf@x) + abs(\pgf@y)}
- \ifdim\pgfmathresult pt>0.1pt\relax
- % First step: Check if left or right turn (-1 = left, 1=right)
- \pgfmathsetmacro{\turnindicator}{sign(sin(\pgf@nfold@cached@endangle-\pgfdecoratedinputsegmentstartangle)}
- % between 0 and 1; 0=no distance to cover in the join, 1=maximum distance to cover
- \pgfmathsetmacro{\insidepercentage}{-.5*\turnindicator*\pgf@shiftdec@fraction+.5}
- \if m\pgf@cached@linejoin % because both are only one character, \ifx is not needed
+ % Offset the start and end of this join
+ \pgfextract@process\pgf@nfold@join@start{%
+ \pgfpointadd{\pgf@nfold@previous@joinend}
+ {\pgfpointpolar{\pgf@nfold@previous@endangle+90}{\pgf@nfold@shiftamount}}}%
+ \pgfextract@process\pgf@nfold@join@end{%
+ \pgfpointadd{\pgf@nfold@segment@start}
+ {\pgfpointpolar{\pgf@nfold@cur@startangle+90}{\pgf@nfold@shiftamount}}}%
+ \pgf@process{\pgfpointdiff{\pgf@nfold@join@start}{\pgf@nfold@join@end}}
+ % Check if the start of this segment is very close to the end of the previous segment.
+ % In that case we don't need a join at all
+ \pgfpointtaxicabnorm\pgf@xa
+ \ifdim\pgf@xa>0.1pt\relax
+ % First step: Check if left or right turn (-1=left, 1=right)
+ \ifdim\pgf@nfold@deltaphi@start pt<0pt
+ \def\turnindicator{1}
+ \else
+ \def\turnindicator{-1}
+ \fi
+ % \insidepercentage: between 0.0 and 1.0;
+ % 0=no distance to cover in the join, 1=maximum distance to cover
+ \pgf@xa=\pgf@nfold@shift@fraction pt\relax
+ \pgf@xa=\turnindicator\pgf@xa
+ \advance\pgf@xa by-1pt\relax
+ \pgf@xa=-.5\pgf@xa
+ % \insidepercentage = .5 * (1 - \turnindicator*\pgf@nfold@shift@fraction)
+ \edef\insidepercentage{\pgf@sys@tonumber\pgf@xa}
+ \if m\pgf@cached@linejoin% \ifx is not needed because both are only one character
% miter join
% First we implement the miter limit: If the angle is too sharp, the miter join is replaced
% by a bevel join. This is controlled by /tikz/miter limit=..., initially 10.
- \pgfmathsetmacro{\mitercosine}{abs(cos(.5*\pgf@nfold@deltaphi))}
- \pgfmathparse{\mitercosine*\pgf@nfold@cached@miterlimit}
- \ifdim\pgfmathresult pt>1pt\relax
+ \pgf@xa=\pgf@nfold@deltaphi@start pt\relax
+ \pgf@xa=.5\pgf@xa
+ \pgfmathcos@{\pgf@sys@tonumber\pgf@xa}
+ \pgf@xa=\pgfmathresult pt\relax
+ \pgf@xa=\pgf@nfold@cached@miterlimit\pgf@xa
+ % Check if miterlimit*cos(.5*deltaphi) > 1;
+ % cos(.5*deltaphi) >= 0 because -180 <= deltaphi <= 180
+ \ifdim\pgf@xa>1pt\relax
\pgf@nfold@miterjoin
\else
\pgf@nfold@beveljoin
@@ -186,600 +201,933 @@
\fi
\fi
\fi
- % Common for all non-trivial joins: Connect to the starting point of the current segment.
- % Note that this code is skipped by the global \ifdim if the start and end of the join coincide,
- % so we never get a zero length segment here.
- \pgfpathlineto{\pgf@nfold@offset@end}
+ % All non-trivial joins connect to the end of the join, which is the starting point of the current segment.
+ % Note that this entire macro is skipped by if the start and end of the join coincide, so we never create a zero length segment here.
+ %
+ % There is one edge case here: If two subsequent joins are so close that \pgf@nfold@segment@end
+ % and \pgf@nfold@segment@start exchange places *and* we are on the outside of the join
+ % (implying that we are on the inside of the next join), \pgf@nfold@join@end will be located behind the next join.
+ % In this case we instead connect to the offset of \pgf@nfold@segment@end (which comes *before* \pgf@nfold@segment@start).
+ \pgf@nfold@join@end% default point to connect to
+ \ifpgf@nfold@closejoinsedgecase%
+ \ifdim\insidepercentage pt<.5pt\relax%
+ \pgf@process{\pgfpointadd%
+ {\pgf@nfold@segment@end}%
+ {\pgfpointpolar{\pgf@nfold@cur@startangle+90}{\pgf@nfold@shiftamount}}}
+ \fi%
+ \fi
+ \pgfpathlineto{}
\fi
}
%
-% Internal offset decoration
-% --------------------------
-%
-% This internal tikz decoration offsets a given path (but doesn't draw it multiple times).
+% Main rendering pipeline
+% -----------------------
%
-% pgfkeys interface, used in the decorations below
-\newcount\pgf@nfold@dec@order
-\pgf@nfold@dec@order=2
-\pgfkeys{
- % - use a counter so we get an error if something other than a number is provided
- % - if needed, throw an error that the intersection library needs to be loaded
- /pgf/decoration/nfold order/.code={
- \pgf@nfold@dec@order=#1
- \ifnum\pgf@nfold@dec@order>\tikz@arrow@intersec@numcached
- \ifdefined\pgfintersectionofpaths\else
- \pgferror{For 'nfold' larger than \tikz@arrow@intersec@numcached\space you need to say \string\usetikzlibrary{intersections}}
- \fi
- \fi
- },
- /pgf/decoration/nfold width/.code={\pgfmathsetlengthmacro{\pgf@decoration@nfold@hwidth}{.5*#1}},
- % integer between 1 and \pgf@nfold@dec@order
- /pgf/decoration/nfold index/.store in=\pgf@nfold@index
-}
-
-% In here we cache the end angle of the previous decorated segment, which is otherwise inaccessible
-\newdimen\pgf@nfold@cached@endangle
% This stores whether the current segment should begin with a moveto to its offset
\newif\ifpgf@nfold@continuesegment
+% This stores whether we are in some edge case of very close joins, see below for details
+\newif\ifpgf@nfold@closejoinsedgecase
+% This stores whether we are in an error case where we need to avoid dividing by zero
+\newif\ifpgf@nfold@angletoosharp
+% This stores whether we need the intersections library for an arrow tip but it is not loaded
+\newif\ifpgf@nfold@intersectionsnotloaded
+
-% some required computations for the current segment
-\def\pgf@nfold@shift@prepare@segment{%
- \pgftransformreset % important! otherwise we work in the local coordinate system
- % compute the relative deviation from the original path (between -1.0 and 1.0)
- \pgfmathsetmacro{\pgf@shiftdec@fraction}%
- {-1+2*(\pgf@nfold@index-1)/(\pgf@nfold@dec@order-1)}
- \pgfmathsetlengthmacro{\pgf@shiftdec@amount}{\pgf@decoration@nfold@hwidth*\pgf@shiftdec@fraction}
+\def\pgf@nfold@handlesegment{%
+ %%% Step 0: Preparation
% Set some default values; they might get overwritten later
- \let\pgf@nfold@segment@start\pgf@decorate@inputsegment@first
- \let\pgf@nfold@segment@end\pgf@decorate@inputsegment@last
+ \let\pgf@nfold@segment@start\pgf@nfold@inputsegment@first
+ \let\pgf@nfold@segment@end\pgf@nfold@inputsegment@last
% Set a default value for \ifpgf@nfold@continuesegment.
% It may be overwritten by \pgf@nfold@extendtotip
- \ifx\pgfdecorationpreviousinputsegment\pgfdecorationinputsegmentmoveto
+ \ifx\pgf@nfold@previousinputsegment\pgf@nfold@inputsegmentmoveto
\pgf@nfold@continuesegmentfalse
\else
\pgf@nfold@continuesegmenttrue
\fi
-}
-
-% draw the current segment including a join at the start (if present)
-\def\pgf@nfold@shift@handle@segment{%
+ %%% Step 1: Make space for joins if necessary
% In order to make space for the join, it may be necessary to shorten the current segment
% at the start and/or the end. In here we store by how much the segment needs to be shortened.
- \def\pgf@nfold@shortenforjoin{0pt}
+ \def\pgf@nfold@shortenstartjoin{0pt}
+ \def\pgf@nfold@shortenendjoin{0pt}
+ \pgf@nfold@closejoinsedgecasefalse
+ \pgf@nfold@angletoosharpfalse
% Make a join only if two adjacent segments are both visible
- \ifpgfsegmentvisible\pgfdecorationcurrentinputsegment
- % Step 1: Make space for the join at the start if needed
- \ifx\pgfdecorationpreviousinputsegment\pgfdecorationinputsegmentmoveto\else
- \ifdefined\pgfdecorationpreviousinputsegment
- % As far as I am aware, the previous input segment is either moveto or undefined
- \pgfwarning{'nfold': Unexpected previous input segment in decoration 'nfold' (\meaning\pgfdecorationpreviousinputsegment)}
+ \checkpgfsegmentvisible\pgf@nfold@currentinputsegment
+ \ifpgf@nfold@segm@visible%
+ % Step 1.1: Make space for the join at the start if needed
+ \ifx\pgf@nfold@previousinputsegment\pgf@nfold@inputsegmentmoveto\else
+ \pgf@xb=\pgf@nfold@deltaphi@start pt\relax
+ \ifdim\pgf@xb<0pt\relax
+ \pgf@xb=-\pgf@xb
\fi
- \pgf@xa=\pgfdecoratedinputsegmentstartangle pt\relax
- \advance\pgf@xa by-\pgf@nfold@cached@endangle\relax
- \pgfmathparse{abs(cos(0.5*\pgf@xa))}
- \ifdim\pgfmathresult pt<0.02pt\relax
- % we go full backwards, don't relocate the start
- \pgfwarning{Angle too sharp in decoration 'nfold', expect visual errors}
+ \ifdim\pgf@xb>178pt\relax
+ % we go full backwards, don't relocate the start and disable the join to avoid division by zero
+ \pgfutil@packagewarning{tikz-nfold}{Angle too sharp, expect visual errors}
+ \pgf@nfold@angletoosharptrue
\else
- \pgfmathsetlengthmacro{\pgf@nfold@shortenforjoin}{\pgf@decoration@nfold@hwidth*abs(tan(0.5*\pgf@xa))}
- \pgfpointadd{\pgf@decorate@inputsegment@first}{\pgfpointpolar{\pgfdecoratedinputsegmentstartangle}{\pgf@nfold@shortenforjoin}}
- \pgfstorepoint\pgf@nfold@segment@start
+ \ifdim\pgf@xb>0.5pt\relax
+ % make space for the start join if the angle is nonzero
+ \pgf@yb=.5\pgf@xb
+ \pgfmathtan@{\pgf@sys@tonumber\pgf@yb}
+ \pgf@yb=\pgf@nfold@hwidth\relax
+ \pgf@yb=\pgfmathresult\pgf@yb
+ % shortenstartjoin = hwidth*tan(.5*abs(deltaphi@start))
+ \edef\pgf@nfold@shortenstartjoin{\the\pgf@yb}
+ \pgfextract@process\pgf@nfold@segment@start{%
+ \pgfpointadd{\pgf@nfold@inputsegment@first}%
+ {\pgfqpointpolar{\pgf@nfold@cur@startangle}{\pgf@yb}}}%
+ \ifx\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentcurveto
+ % For a curve we need to relocate the @supporta point as well, as otherwise the @first point
+ % might overtake it
+ \pgf@process{\pgfpointdiff{\pgf@nfold@inputsegment@first}{\pgf@nfold@inputsegment@supporta}}
+ \pgfmathveclen@{\pgf@sys@tonumber\pgf@x}{\pgf@sys@tonumber\pgf@y}
+ \ifdim\pgfmathresult pt>0.1pt\relax
+ % regular curves (supporta != first):
+ % Increase dist(first, supporta) to sqrt(a^2 + b^2) where a=dist(first, supporta) and b=shortenstart. This way, the order of first and supporta is guaranteed to be preserved, and the change to supporta is as small as reasonably possible.
+ \pgfmathveclen@{\pgfmathresult}{\pgf@sys@tonumber\pgf@yb}
+ \pgfextract@process\pgf@nfold@inputsegment@supporta{\pgfpointadd%
+ {\pgf@nfold@inputsegment@first}%
+ {\pgfqpointpolar{\pgf@nfold@cur@startangle}{\pgfmathresult pt}}}%
+ \else
+ % special treatment for singular curves (supporta = first) to avoid rounding error glitches.
+ % In this special case, a slight corner at the end of the join is unavoidable
+ \let\pgf@nfold@inputsegment@supporta\pgf@nfold@segment@start
+ \fi
+ \fi
+ \fi
\fi
\fi
- % Step 2: Make space for the join at the end if needed
- \ifpgfsegmentvisible\pgfdecorationnextinputsegmentobject
- % for reasons unknown to me,
- % \pgfdecoratedangletonextinputsegment has a "pt" but the others do not
- \pgf@xa=\pgfdecoratedangle pt\relax
- \advance\pgf@xa by \pgfdecoratedangletonextinputsegment\relax
- \advance\pgf@xa by -\pgfdecoratedinputsegmentendangle pt\relax
- \pgfmathparse{abs(cos(0.5*\pgf@xa))}
- \ifdim\pgfmathresult pt<0.02pt\relax
- \pgfwarning{Angle too sharp in decoration 'nfold', expect visual errors}
+ % Step 1.2: Make space for the join at the end if needed
+ \checkpgfsegmentvisible\pgf@nfold@next@segmenttype
+ \ifpgf@nfold@segm@visible%
+ \pgf@xb=\pgf@nfold@deltaphi@end pt\relax
+ \ifdim\pgf@xb<0pt\relax
+ \pgf@xb=-\pgf@xb
+ \fi
+ \ifdim\pgf@xb>178pt\relax
+ \pgfutil@packagewarning{tikz-nfold}{Angle too sharp, expect visual errors}
+ \pgf@nfold@angletoosharptrue
\else
- \pgfmathparse{-\pgf@decoration@nfold@hwidth*abs(tan(0.5*\pgf@xa))}
- \pgfpointadd{\pgf@decorate@inputsegment@last}{\pgfpointpolar{\pgfdecoratedinputsegmentendangle}{\pgfmathresult pt}}
- \pgfstorepoint\pgf@nfold@segment@end
+ \ifdim\pgf@xb>0.5pt\relax
+ % make space for the start join if the angle is nonzero
+ \pgf@yb=.5\pgf@xb
+ \pgfmathtan@{\pgf@sys@tonumber\pgf@yb}
+ \pgf@yb=\pgf@nfold@hwidth\relax
+ \pgf@yb=\pgfmathresult\pgf@yb
+ % shortenendjoin = hwidth*tan(.5*abs(deltaphi@end))
+ \edef\pgf@nfold@shortenendjoin{\the\pgf@yb}
+ \pgf@yb=-\pgf@yb
+ \pgfextract@process\pgf@nfold@segment@end{%
+ \pgfpointadd{\pgf@nfold@inputsegment@last}%
+ {\pgfqpointpolar{\pgf@nfold@cur@endangle}{\pgf@yb}}}%
+ \ifx\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentcurveto
+ % Same procedure as above: relocate supportb if we have a curve
+ \pgf@process{\pgfpointdiff{\pgf@nfold@inputsegment@supportb}{\pgf@nfold@inputsegment@last}}
+ \pgfmathveclen@{\pgf@sys@tonumber\pgf@x}{\pgf@sys@tonumber\pgf@y}
+ \ifdim\pgfmathresult pt>0.1pt\relax
+ \pgfmathveclen@{\pgfmathresult}{\pgf@sys@tonumber\pgf@yb}
+ % can use qpoint and minus because \pgfmathresult is guaranteed to be positive
+ \pgfextract@process\pgf@nfold@inputsegment@supportb{\pgfpointadd%
+ {\pgf@nfold@inputsegment@last}%
+ {\pgfqpointpolar{\pgf@nfold@cur@endangle}{-\pgfmathresult pt}}}%
+ \else
+ \let\pgf@nfold@inputsegment@supportb\pgf@nfold@segment@end
+ \fi
+ \fi
+ \fi
\fi
- \fi
+ % Step 1.3: Detect an edge case
+ % This edge case appears whenever the current segment is such a short line that we would
+ % have to reduce its length to less than zero to make space for the joins. In such cases,
+ % the line is not drawn at all, and slight modifications must be made to the joins to ensure
+ % a correct output (i.e. one join is immediately followed by the next without a segment in between).
+ %
+ % This edge case can appear for curves as well, but they are much harder to deal with.
+ \ifx\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentlineto
+ % No need to check for \pgf@nfold@inputsegmentclosepath as it should not be followed by any further segments
+ % Now: Check if the total amount of shortening is larger than the length of the segment
+ \pgfpointdiff{\pgf@nfold@inputsegment@first}{\pgf@nfold@inputsegment@last}
+ \pgfmathveclen@{\pgf@sys@tonumber\pgf@x}{\pgf@sys@tonumber\pgf@y}
+ \pgf@xa=\pgf@nfold@shortenstartjoin\relax
+ \advance\pgf@xa by\pgf@nfold@shortenendjoin\relax
+ \ifdim\pgf@xa>\pgfmathresult pt\relax
+ \pgf@nfold@closejoinsedgecasetrue
+ \fi
+ \fi
+ \fi% end if next segment visible
%
- % Step 3: Draw the join at the start if applicable
+ % Step 2.1: Draw the join at the start if applicable
%
- \ifx\pgfdecorationpreviousinputsegment\pgfdecorationinputsegmentmoveto\else
- \pgf@nfold@make@join
+ \ifx\pgf@nfold@previousinputsegment\pgf@nfold@inputsegmentmoveto
+ \ifpgf@nfold@closejoinsedgecase
+ % If the previous segment is a moveto and the current segment is a "close joins" edge case,
+ % nothing needs to be drawn here (the relevant draw call will be made at the join of the subsequent
+ % segment). We must therefore make sure that we move to the correct end point of this segment.
+ % Counterintuitively, this is given by the offset of \pgf@nfold@segment@start since the start and end
+ % are reversed in the edge case.
+ \pgfpointadd%
+ {\pgf@nfold@segment@start}%
+ {\pgfpointpolar{\pgf@nfold@cur@startangle+90}{\pgf@nfold@shiftamount}}
+ \pgfpathmoveto{}
+ \fi
+ \else
+ % If we draw the join when the start angle is close to 180 degrees, we get a division by zero
+ \ifpgf@nfold@angletoosharp\else
+ \pgf@nfold@make@join
+ \fi
\fi
- \fi
+ \fi% end if current segment visible
+ % Step 2.2: Store where the current (non-offset) end point was relocated
+ % in order to make space for the end join. This may be used if the next
+ % segment begins with a join
+ \let\pgf@nfold@previous@joinend\pgf@nfold@segment@end
%
- % Step 4: Draw the new segment.
+ % Step 3: Draw the new segment.
%
% The value of \ifpgf@nfold@continuesegment decides whether we start with a moveto.
- \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentlineto
- \ifpgf@nfold@continuesegment
- \pgfoffsetlinenomove{\pgf@nfold@segment@start}{\pgf@nfold@segment@end}{\pgf@shiftdec@amount}
- \else
- \pgfoffsetline{\pgf@nfold@segment@start}{\pgf@nfold@segment@end}{\pgf@shiftdec@amount}
+ \ifx\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentlineto
+ \ifpgf@nfold@closejoinsedgecase\else
+ \ifpgf@nfold@continuesegment
+ \pgfoffsetlinenomove{\pgf@nfold@segment@start}{\pgf@nfold@segment@end}{\pgf@nfold@shiftamount}
+ \else
+ \pgfoffsetline{\pgf@nfold@segment@start}{\pgf@nfold@segment@end}{\pgf@nfold@shiftamount}
+ \fi
\fi
\fi
- \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentclosepath
- \pgfwarning{'nfold': The option 'cycle' is not yet properly supported.}
- \ifpgf@nfold@continuesegment
- \pgfoffsetlinenomove{\pgf@nfold@segment@start}{\pgf@nfold@segment@end}{\pgf@shiftdec@amount}
- \else
- \pgfoffsetline{\pgf@nfold@segment@start}{\pgf@nfold@segment@end}{\pgf@shiftdec@amount}
- \fi
+ \ifx\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentclosepath
+ % Incomplete: proper join is missing
+ % Idea: when parsing the path, add a detection if there is a closepath at the current segment,
+ % then cache the data of the last segment.
+ % We could then insert a "fake previous segment" into the path at the right place
+ \pgfutil@packagewarning{tikz-nfold}{The option `cycle' is not yet properly supported}
+ \pgfpathclose
\fi
- \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentcurveto
+ \ifx\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentcurveto
\ifpgf@nfold@continuesegment
- \pgfoffsetcurvenomove{\pgf@nfold@segment@start}{\pgf@decorate@inputsegment@supporta}{\pgf@decorate@inputsegment@supportb}{\pgf@nfold@segment@end}{\pgf@shiftdec@amount}
+ \pgfoffsetcurvenomove{\pgf@nfold@segment@start}{\pgf@nfold@inputsegment@supporta}{\pgf@nfold@inputsegment@supportb}{\pgf@nfold@segment@end}{\pgf@nfold@shiftamount}
\else
- \pgfoffsetcurve{\pgf@nfold@segment@start}{\pgf@decorate@inputsegment@supporta}{\pgf@decorate@inputsegment@supportb}{\pgf@nfold@segment@end}{\pgf@shiftdec@amount}
+ \pgfoffsetcurve{\pgf@nfold@segment@start}{\pgf@nfold@inputsegment@supporta}{\pgf@nfold@inputsegment@supportb}{\pgf@nfold@segment@end}{\pgf@nfold@shiftamount}
\fi
\fi
- \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentlast
- % Seems like this never happens
- \pgfwarning{'nfold': Unexpected state (last part in segment state).}
- \fi
- % cache the end angle and end point because we need it on the next pass
- \global\pgf@nfold@cached@endangle=\pgfdecoratedinputsegmentendangle pt\relax
- \ifx\pgfdecorationnextinputsegmentobject\pgfdecorationinputsegmentmoveto\else
- \global\let\pgf@nfold@cached@endpoint\pgf@nfold@segment@end
+ \ifx\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentmoveto
+ % Every moveto is executed in the offsetting of the subsequent draw command,
+ % therefore we do not need a \pgfpathmoveto call here
+ \ifx\pgf@nfold@previousinputsegment\pgf@nfold@inputsegmentfirst
+ % Draw the tip extension at the start (if present)
+ \ifnum\pgf@nfold@start@arrowcode=1
+ \pgf@nfold@extendtotip{s}%
+ \fi%
+ \fi
\fi
- % Step 5: Extend into the arrow tip at the end (if present)
- \ifx\pgfdecorationnextinputsegmentobject\pgfdecorationinputsegmentlast%
- \pgf@nfold@check@tip@end%
- \ifpgf@nfold@tip@implies%
+ % Step 4: Extend into the arrow tip at the end (if present)
+ \ifx\pgf@nfold@next@segmenttype\pgf@nfold@inputsegmentlast%
+ \ifnum\pgf@nfold@end@arrowcode=1
\pgf@nfold@extendtotip{e}%
\fi%
\fi%
}
-\pgfdeclaredecoration{pgf@nfold@shift}{start}{%
- \state{start}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{%
- \egroup\begingroup
- \pgf@nfold@shift@prepare@segment
- \pgf@nfold@check@tip@start%
- \ifpgf@nfold@tip@implies%
- \pgf@nfold@extendtotip{s}%
- \fi
- \pgf@nfold@shift@handle@segment
- \endgroup\bgroup%
- }
- \state{segment}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{%
- \egroup\begingroup
- \pgf@nfold@shift@prepare@segment
- \pgf@nfold@shift@handle@segment
- \endgroup\bgroup%
- }%
-}%
-
+% Rendering arrow tips
+% --------------------
-% Internal pre-pass decoration
-% ----------------------------
-%
-% This internal decoration is run as the first step in the decoration 'nfold'.
-% It caches some data that won't be accessible later, and it also makes space for the arrow tips
-% if needed.
+% Precomputed intersections
%
+% For arrows of order n > 2 with an Implies tip, the constituent parts of the n-fold arrow
+% end somewhere in the middle of the tip. The exact end point must be computed using
+% the intersections library. To speed up compilation times, the intersection points are precomputed
+% up to n = 5. If your document contains arrows of order 6 or larger, consider adding those
+% as well; the values are output in the log file.
+\expandafter\def\csname pgf@nfold@intersec@cache@2@3\endcsname{\pgfqpoint{2pt}{0pt}}
+\expandafter\def\csname pgf@nfold@intersec@cache@2@4\endcsname{\pgfqpoint{0.94063pt}{-0.33333pt}}
+\expandafter\def\csname pgf@nfold@intersec@cache@3@4\endcsname{\pgfqpoint{0.94063pt}{0.33333pt}}
+\expandafter\def\csname pgf@nfold@intersec@cache@2@5\endcsname{\pgfqpoint{0.64167pt}{-0.5pt}}
+\expandafter\def\csname pgf@nfold@intersec@cache@3@5\endcsname{\pgfqpoint{2pt}{0pt}}
+\expandafter\def\csname pgf@nfold@intersec@cache@4@5\endcsname{\pgfqpoint{0.64167pt}{0.5pt}}
+% intersections are precomputed up to this order
+\def\pgf@nfold@intersec@numcached{5}
-\def\pgf@nfold@storedata@handle@segment{%
- \let\pgf@nfold@segment@end\pgf@decorate@inputsegment@last%
- \ifx\pgfdecorationnextinputsegmentobject\pgfdecorationinputsegmentlast%
- % Implementing shorten > and making space for the arrow tip (if present)
- \pgf@nfold@check@tip@end%
- \ifpgf@nfold@tip@implies%
- \pgfmathparse{-\pgf@shorten@end@additional-2.06*\pgf@decoration@nfold@hwidth-0.5*\pgflinewidth}
- \else
- \pgfmathparse{-\pgf@shorten@end@additional}
- \fi
- % This is the point where the arrow body ends
- \pgfpointadd%
- {\pgf@decorate@inputsegment@last}%
- {\pgfpointpolar{\pgfdecoratedinputsegmentendangle}{\pgfmathresult pt}}%
- \pgfstorepoint\pgf@nfold@segment@end%
- % This is the tip of the arrow, required for drawing the arrow head
- \pgfpointadd%
- {\pgf@decorate@inputsegment@last}%
- {\pgfpointpolar%
- {\pgfdecoratedinputsegmentendangle}%
- {-\pgf@shorten@end@additional}}%
- \pgfglobalstorepoint\pgf@nfold@original@last%
- \fi%
- \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentmoveto%
- \pgfpathmoveto{\pgf@nfold@segment@end}%
- \fi%
- \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentlineto%
- \pgfpathlineto{\pgf@nfold@segment@end}%
- \fi%
- \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentclosepath%
- \pgfpathlineto{\pgf@nfold@segment@end}%
- \fi%
- \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentcurveto%
- \pgfpathcurveto%
- {\pgf@decorate@inputsegment@supporta}%
- {\pgf@decorate@inputsegment@supportb}%
- {\pgf@nfold@segment@end}%
- \fi%
-}
-
-\pgfdeclaredecoration{pgf@nfold@storedata}{start}{%
- \state{start}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{%
- \egroup\begingroup
- \pgftransformreset
- \iftikz@mode@double\else
- \pgferror{Must enable /tikz/double to use 'nfold' (e.g. using [double distance=5pt]).}
- \fi
- % Cache arrow settings
- \pgf@nfold@storearrows
- % Cache inner and outer line width from the '/tikz/double' settings. These are stored in
- % the macro \tikz@double@setup. Internally, the value of \pgfinnerlinewidth determines whether
- % double stroke is enabled or not. Calling \tikz@double@setup changes line widths globally, so
- % encapsulating the calls in a group would still have side effects. What we do instead is call
- % \tikz@double@setup and then restore \pgflinewidth.
- \tikz@double@setup
- \pgfmathsetlengthmacro{\pgf@nfold@temp@fullwidth}{.25*(\pgflinewidth+\pgfinnerlinewidth)}
- \global\edef\pgf@decoration@nfold@hwidth{\pgf@nfold@temp@fullwidth}
- % We can either store \pgflinewidth before calling \tikz@double@setup, or we can reconstruct
- % its old value from the new values. We do the latter here.
- \pgfmathparse{.5*(\pgflinewidth-\pgfinnerlinewidth)}
- \pgfsetlinewidth{\pgfmathresult pt}
- % shorten < and shorten > do not work well with this decoration for various reasons.
- % We "bake" them into the path in the pre-pass and then disable them for the rendering passes.
- \pgfpointadd%
- {\pgf@decorate@inputsegment@first}%
- {\pgfpointpolar{\pgfdecoratedinputsegmentstartangle}{\pgf@shorten@start@additional}}
- % store the tip of the arrow
- \pgfglobalstorepoint\pgf@nfold@original@first%
- % make space if applicable
- \pgf@nfold@check@tip@start%
- \ifpgf@nfold@tip@implies%
- \pgfmathparse{\pgf@shorten@start@additional+2.06*\pgf@decoration@nfold@hwidth+0.5*\pgflinewidth}
+% This macro extends the arrow body to the tips
+% parameter: s=start, e=end
+\def\pgf@nfold@extendtotip#1{
+ \ifpgf@nfold@intersectionsnotloaded
+ \pgfutil@packageerror{tikz-nfold}{%
+ If `nfold' is larger than \pgf@nfold@intersec@numcached\space and you use
+ an `Implies' arrow tip you need to say \string\usetikzlibrary{intersections}}{}
+ \else
+ % Do not extend the arrow for index=1 and index=order, it already ends in the right place
+ \ifnum\pgf@nfold@index>1\relax\ifnum\pgf@nfold@index<\pgf@nfold@order\relax%
+ % Step 1: Find the intersection of the arrow's path with the head
+ \ifcsname pgf@nfold@intersec@cache@\the\pgf@nfold@index @\the\pgf@nfold@order\endcsname
+ \pgfextract@process\pgf@nfold@arrowintersect
+ {\csname pgf@nfold@intersec@cache@\the\pgf@nfold@index @\the\pgf@nfold@order\endcsname}%
+ \else
+ % the intersection has not been precomputed, thus compute on the fly here
+ \pgfintersectionofpaths{
+ % specify the tip
+ \pgfpathmoveto{\pgfqpoint{-1.4pt}{2.65pt}}
+ \pgfpathcurveto{\pgfqpoint{-0.75pt}{1.25pt}}{\pgfqpoint{1pt}{0.05pt}}{\pgfqpoint{2pt}{0pt}}
+ \pgfpathcurveto{\pgfqpoint{1pt}{-0.05pt}}{\pgfqpoint{-0.75pt}{-1.25pt}}{\pgfqpoint{-1.4pt}{-2.65pt}}
+ }{
+ % extend the body to intersect the tip
+ \pgfpathmoveto{\pgfqpoint{-3pt}{\pgf@nfold@shift@fraction pt}}
+ \pgfpathlineto{\pgfqpoint{3pt}{\pgf@nfold@shift@fraction pt}}
+ }
+ \ifnum\pgfintersectionsolutions>0
+ \pgfextract@process\pgf@nfold@arrowintersect{\pgfpointintersectionsolution{1}}%
+ \immediate\write17{tikz-nfold: computed intersection cache@\the\pgf@nfold@index @\the\pgf@nfold@order: \string\pgfqpoint{\the\pgf@x}{\the\pgf@y}^^J}
+ % add the new intersection to the cache
+ \expandafter\xdef\csname pgf@nfold@intersec@cache@\the\pgf@nfold@index @\the\pgf@nfold@order\endcsname{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}
+ \else
+ % this is a failsafe and should never be reached
+ \pgfutil@packagewarning{tikz-nfold}{did not find intersection}
+ \pgfextract@process\pgf@nfold@arrowintersect{\pgfqpoint{0pt}{\pgf@nfold@shift@fraction pt}}%
+ \fi
+ \fi% if precomputed
+ % Step 2: Extend the arrow body to the intersection point.
+ % If the tip is at the beginning of the path, we have to move to the intersection
+ % and then draw a line to the "regular" starting point. The subsequent segment then
+ % should omit its moveto.
+ % If the tip is at the end, we are already in the right position and only need to extend
+ % the current path to the intersection point.
+ \begingroup
+ \pgftransformreset
+ \if#1s
+ \pgftransformshift{\pgf@nfold@segment@start}
+ \pgftransformrotate{\pgf@nfold@next@startangle}
+ \pgftransformxscale{-1}
+ \else
+ \pgftransformshift{\pgf@nfold@segment@end}
+ \pgftransformrotate{\pgf@nfold@cur@endangle}
+ \fi
+ % we don't want to undo the shift by .42\pgflinewidth after the scaling
+ \pgfutil@tempdima=\pgf@nfold@hwidth
+ \pgfutil@tempdima=\pgf@nfold@shift@fraction\pgfutil@tempdima
+ \pgfextract@process\pgf@nfold@startofextension
+ {\pgfpointtransformed{\pgfqpoint{0pt}{\pgfutil@tempdima}}}
+ % 0.5 - 0.06 = 0.42
+ \pgftransformshift{\pgfqpoint{.42\pgflinewidth}{0pt}}
+ \pgftransformscale{\pgf@nfold@hwidth}
+ \pgfextract@process\pgf@nfold@arrowintersect{\pgfpointtransformed{\pgf@nfold@arrowintersect}}
+ \global\let\pgf@nfold@startofextension\pgf@nfold@startofextension
+ \global\let\pgf@nfold@arrowintersect\pgf@nfold@arrowintersect
+ \endgroup
+ \if#1s
+ \pgfpathmoveto{\pgf@nfold@arrowintersect}
+ % This is precisely the start of the body, shifted vertically
+ \pgfpathlineto{\pgf@nfold@startofextension}
+ % hack: We make the next segment believe that this segment was a lineto
+ % so the path does not get interrupted
+ \let\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentlineto
+ \let\pgf@nfold@cur@endangle\pgf@nfold@next@startangle
+ \else\if#1e
+ \pgfpathlineto{\pgf@nfold@arrowintersect}
\else
- \pgfmathparse{\pgf@shorten@start@additional}
- \fi
- \pgfpointadd%
- {\pgf@decorate@inputsegment@first}%
- {\pgfpointpolar{\pgfdecoratedinputsegmentstartangle}{\pgfmathresult pt}}
- %
- \pgfpathmoveto{}
- \pgf@nfold@storedata@handle@segment
- \endgroup\bgroup
- }
- \state{segment}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{%
- \egroup\begingroup
+ \pgfutil@packageerror{tikz-nfold}{Invalid argument to \string\pgf@nfold@extendtotip: \meaning#1}{}
+ \fi\fi
\pgftransformreset
- \pgf@nfold@storedata@handle@segment
- \endgroup\bgroup
- }%
- \state{final}{}
-}%
+ \fi\fi% if 1 < i < nArrows
+ \fi% if intersections is needed and not loaded
+}
+% Parsing the arrow tips
+% ----------------------
+%
+% We need to detect if the user has set Implies[] arrows at the start and/or end tip.
+% To do so, we parse \pgf@start@tip@sequence. If the user specifies Implies[] manually,
+% we find that
+% pgf@start@tip@sequence=\pgf@arrow@handle{Implies}{...}
+% However, in other cases (like tikz-cd) we may find
+% \pgf@arrow@handle@shorthand@empty {\csname pgf@ar@means@tikzcd implies cap\endcsname }
+% In such cases we must expand the first parameter once and then match as above.
-% Detecting arrow tips
-% --------------------
+% Set global defaults
+\def\pgf@nfold@start@arrowcode{0}
+\def\pgf@nfold@end@arrowcode{0}
-% Old arrow matching
-%
-% This is the content of \pgf@arrow@tip@sequence for arrows | and Implies, respectively
-%
-%\edef\pgf@arrow@macro@verticalline{\noexpand\pgf@arrow@handle@shorthand@empty {\expandafter\noexpand\csname pgf@ar@means@|\endcsname }}
-%\def\pgf@arrow@macro@Implies{\pgf@arrow@handle {Implies}{}}
-% The next one is wrong; use \csname pgf@ar@means@tikzcd implies\endcsname
-%\def\tikzcd@arrow@implies{\pgf@arrow@handle@shorthand@empty {\pgf@ar@means@tikzcd implies }}
-
-% Intercept the arrows at definition
-\let\pgf@nfold@cachedarrows\pgfutil@empty % default value
-\let\pgf@nfold@oldsetarrows\pgfsetarrows
-\def\pgfsetarrows#1{%
- \def\pgf@nfold@cachedarrows{#1}%
- \pgf@nfold@oldsetarrows{#1}%
-}
-
-\let\pgf@nfold@cached@arrow@start\pgfutil@empty
-\let\pgf@nfold@cached@arrow@end\pgfutil@empty
-\def\pgf@nfold@storearrows{%
- \ifx\pgf@nfold@cachedarrows\pgfutil@empty
- % reset the cached values in case we don't have an arrow; this is important
- % because the cached values are set globally
- \global\let\pgf@nfold@cached@arrow@start\pgfutil@empty
- \global\let\pgf@nfold@cached@arrow@end\pgfutil@empty
- \else%
- \expandafter\pgf@nfold@parsearrows@\pgf@nfold@cachedarrows\pgf@stop%
- \fi%
+\def\pgf@nfold@parsearrows{
+ \ifpgfutil@tempswa% this is set in \pgfusepath and stores whether we draw arrow tips at all
+ \expandafter\pgf@nfold@parsearrowmacro\pgf@start@tip@sequence\relax
+ \let\pgf@nfold@start@arrowcode\pgf@nfold@detectedarrow
+ \expandafter\pgf@nfold@parsearrowmacro\pgf@end@tip@sequence\relax
+ \let\pgf@nfold@end@arrowcode\pgf@nfold@detectedarrow
+ \else
+ \def\pgf@nfold@start@arrowcode{0}
+ \def\pgf@nfold@end@arrowcode{0}
+ \fi
}
-\def\pgf@nfold@parsearrows@#1-#2\pgf@stop{%
- % These must be set globally because \pgf@nfold@storearrows is called from within a decoration;
- % also, this cached value must survive subsequent calls of \pgfsetarrows{} which disable arrows
- \gdef\pgf@nfold@cached@arrow@start{#1}%
- \gdef\pgf@nfold@cached@arrow@end{#2}%
-}
-
-% Here we match against the names of the arrows that were set in \pgfarrowsset{...}.
-% This does not respect aliases, i.e. redefining /tikzcd implies cap will not have
-% the desired effect. We may be able to fix this in the future, see below.
-\def\pgf@nfold@nonetip@i{tikzcd implies cap}
-\def\pgf@nfold@impliestip@i{Implies}
-\def\pgf@nfold@impliestip@ii{tikzcd implies}
-\def\pgf@nfold@mapstotip@i{|}
-\def\pgf@nfold@mapstotip@ii{tikzcd implies bar}
-\def\pgf@nfold@mapstotip@iii{Bar}
-\newif\ifpgf@nfold@tip@implies
-\newif\ifpgf@nfold@tip@mapsto
-
-\def\pgf@nfold@check@tip@start{%
-\pgf@nfold@check@tip{\pgf@nfold@cached@arrow@start}%
-}
-\def\pgf@nfold@check@tip@end{%
-\pgf@nfold@check@tip{\pgf@nfold@cached@arrow@end}%
-}
-
-\def\pgf@nfold@check@tip#1{%
- \pgf@nfold@tip@impliesfalse%
- \pgf@nfold@tip@mapstofalse%
- \ifx#1\pgfutil@empty%
- % no tip
+
+\def\pgf@nfold@parsearrowmacro#1{%
+ \def\pgf@nfold@detectedarrow{0}
+ \ifx#1\relax
+ \let\pgf@next\relax
\else
- \ifx#1\pgf@nfold@nonetip@i%
- % no tip
+ \ifx#1\pgf@arrow@handle
+ % found \pgf@arrow@handle{...}, now parse the first parameter
+ \let\pgf@next\pgf@nfold@parse@arrow@handle
\else
- \ifx#1\pgf@nfold@impliestip@i%
- \pgf@nfold@tip@impliestrue%
+ \ifx#1\pgf@arrow@handle@shorthand@empty
+ \let\pgf@next\pgf@nfold@parse@shorthandempty
\else
- \ifx#1\pgf@nfold@impliestip@ii%
- \pgf@nfold@tip@impliestrue%
- \else
- \ifx#1\pgf@nfold@mapstotip@i%
- \pgf@nfold@tip@mapstotrue%
- \else
- \ifx#1\pgf@nfold@mapstotip@ii%
- \pgf@nfold@tip@mapstotrue%
- \else
- \ifx#1\pgf@nfold@mapstotip@iii%
- \pgf@nfold@tip@mapstotrue%
- \else
- \pgfwarning{'nfold': Unsupported arrow tip "#1"}
- \fi
- \fi
- \fi
- \fi
+ % found nothing
+ \let\pgf@next\pgfutil@gobble@until@relax
\fi
\fi
\fi
+ \pgf@next
}
-% Future: In principle, we can dereference aliases (means=...) the following way:
+\def\pgf@nfold@param@Implies{Implies}
+
+\def\pgf@nfold@parse@arrow@handle#1{%
+ \def\pgf@tmp{#1}
+ \ifx\pgf@tmp\pgf@nfold@param@Implies
+ \def\pgf@nfold@detectedarrow{1}
+ \fi
+ \pgfutil@gobble@until@relax
+}
+
+\def\pgf@nfold@parse@shorthandempty#1{
+ % Expand #1 once (\pgf@arrow@handle@shorthand@empty is just an identity operator)
+ \expandafter\def\expandafter\pgf@tmp\expandafter{#1}
+ \expandafter\pgf@nfold@parsearrowmacro\pgf@tmp\relax
+ % still need to gobble the rest of the orginal arrow definition
+ \pgfutil@gobble@until@relax
+}
+
+
%
-% \ifcsname pgf@ar@means@tikzcd implies bar\endcsname
-% \expandafter\let\expandafter\tempmacro\csname pgf@ar@means@tikzcd implies bar\endcsname
-% \pgfwarning{\meaning\tempmacro}
-% \fi
+% Hooking into pgf's rendering pipeline
+% -------------------------------------
%
-% However, the alias is not a name, but a macro, which in turn could call dereference aliases.
-% We could go down this rabbit hole in the future, but I won't do that for now.
+% The new code has to be injected into \pgfusepath (pgfcorepathusage.code.tex). For rendering the new paths,
+% \pgf@stroke@inner@line is a natural choice as this is where /tikz/double is rendered. However, we also
+% need to disable rendering the ordinary path, which is not as easy. In the future I will make a pull request
+% to TikZ to simplify such injections.
+%
+% The call to draw the path comes right before \pgf@stroke@inner@line. The macro before \pgf@stroke@inner@line
+% is either \pgf@path@check@proper or \pgf@prepare@start@of@path (depending on the result of the proper check).
+% We therefore must inject code into both of them to see if nfold is enabled. If it is, we call the old macro,
+% cache and delete the current softpath (so the call to \pgfsyssoftpath@invokecurrentpath has no effect), then we
+% restore and offset the cached softpath in \pgf@stroke@inner@line.
+%
+% The macros \pgf@path@check@proper and \pgf@prepare@start@of@path are also used in \pgf@up@draw@arrows@only,
+% so we must make sure that the latter is unaffected by the modifications. Luckily, this turns out not to be
+% a problem - the only macros that are called after the modified ones are \pgf@add@arrow@at@start and
+% \pgf@add@arrow@at@end, which do not change their behaviour even if we modify the paths.
%
-% For now this means that redefining /tikz/commutative diagrams/tikzcd implies cap will not
-% have the desired effect.
+\newcount\pgf@nfold@order
+\pgf@nfold@order=1
+
+\def\pgf@nfold@preparenfoldpath{%
+ \ifnum\pgf@nfold@order>1\relax
+ \ifdim\pgfinnerlinewidth>0pt\relax
+ % Hack the rendering pipeline: There is a \pgfsyssoftpath@invokecurrentpath call following
+ % which we do not want if nfold is active. We therefore clear the current path here
+ % and then perform the nfold drawing in our modification of \pgf@stroke@inner@line
+ \pgfsyssoftpath@getcurrentpath\cachedpath%
+ \pgfsyssoftpath@setcurrentpath\pgfutil@empty%
+ \else
+ \pgfutil@packageerror{tikz-nfold}{Must set \string\pgfinnerlinewidth\space to use nfold, e.g. by setting /tikz/double distance}{}
+ \fi
+ \fi
+}
+
+\let\pgf@nfold@old@path@check@proper\pgf@path@check@proper
+\def\pgf@path@check@proper{%
+ \pgf@nfold@old@path@check@proper%
+ \ifpgfutil@tempswa\else%
+ % if \pgfutil@tempswa is false, this is the last macro we can overwrite before the draw call.
+ % Otherwise, we inject into \pgf@prepare@start@of@path%
+ \pgf@nfold@preparenfoldpath%
+ \fi%
+}
+
+\let\pgf@nfold@old@prepare@start@of@path\pgf@prepare@start@of@path
+\def\pgf@prepare@start@of@path{%
+ \pgf@nfold@old@prepare@start@of@path%
+ \pgf@nfold@preparenfoldpath%
+}
+
+\let\pgf@nfold@old@stroke@inner@line\pgf@stroke@inner@line
+\def\pgf@stroke@inner@line{%
+ \ifnum\pgf@nfold@order>1\relax%
+ \pgf@nfold@render@cached@softpath%
+ \else%
+ % Old behaviour
+ \pgf@nfold@old@stroke@inner@line%
+ \fi%
+}
-% Rendering arrow tips
-% --------------------
-% Precomputed intersections
%
-% For arrows of order n > 2 with an Implies tip, the constituent parts of the n-fold arrow
-% end somewhere in the middle of the tip. The exact end point must be computed using
-% the intersections library. To speed up compilation times, the intersection points are precomputed
-% up to n = 5. If your document contains arrows of order 6 or larger, consider adding those
-% as well; the values are output in the log file.
-\expandafter\def\csname tikz@arrow@intersec@cache@2@3\endcsname{\pgfqpoint{2pt}{0pt}}
-\expandafter\def\csname tikz@arrow@intersec@cache@2@4\endcsname{\pgfqpoint{0.94063pt}{-0.33333pt}}
-\expandafter\def\csname tikz@arrow@intersec@cache@3@4\endcsname{\pgfqpoint{0.94063pt}{0.33333pt}}
-\expandafter\def\csname tikz@arrow@intersec@cache@2@5\endcsname{\pgfqpoint{0.64167pt}{-0.5pt}}
-\expandafter\def\csname tikz@arrow@intersec@cache@3@5\endcsname{\pgfqpoint{2pt}{0pt}}
-\expandafter\def\csname tikz@arrow@intersec@cache@4@5\endcsname{\pgfqpoint{0.64167pt}{0.5pt}}
-% intersections are precomputed up to this order
-\def\tikz@arrow@intersec@numcached{5}
-
-% Decoration for the start tip
-\pgfdeclaredecoration{pgf@nfold@tip@start}{start}{%
- \state{start}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{%
- \egroup\begingroup%
- \pgf@nfold@check@tip@start%
- \pgf@nfold@setup@start@tip%
- \ifpgf@nfold@tip@implies%
- \pgf@nfold@setupimplies%
- \pgf@nfold@drawimplies%
- \fi%
- \ifpgf@nfold@tip@mapsto%
- \pgf@nfold@setupmapsto%
- \pgf@nfold@drawmapsto%
+% Parsing the soft path
+% ---------------------
+%
+% A significant part of the code below is based on pgfmoduledecorations.code.tex (c) 2019 Mark Wibrow and Till Tantau.
+% Quite similar to decorations we parse the current soft path and put it into a form that makes it easier
+% to iterate over.
+%
+%
+
+\def\pgf@nfold@parsesoftpath#1#2{%
+ \def\pgf@nfold@inputsegmentobjectsmacro{#2}%
+ \let\pgf@nfold@inputsegmentobjects\pgfutil@empty%
+ \pgfutil@tempdima0pt\relax%
+ \edef\pgfpoint@nfold@lastparsed{\pgf@x\the\pgf@path@lastx\pgf@y\the\pgf@path@lasty}%
+ \let\pgfpoint@nfold@lastnonmovetoparsed\pgfpoint@origin%
+ \let\pgf@nfold@queueinputsegmentobject\pgfutil@empty%
+ \let\pgfpoint@nfoldd@firstparsed\pgfutil@empty%
+ \expandafter\pgf@nfold@@parsesoftpath#1\pgf@stop%
+}%
+
+\def\pgf@nfold@@parsesoftpath#1{%
+ \ifx#1\pgf@stop%
+ \let\pgf@nfold@queueinputsegmentobject\pgfutil@empty% <- removes final moveto (may not be desirable).
+ \pgf@nfold@addtoinputsegmentobjects{\pgf@nfold@inputsegmentobject@endofinputsegments}%
+ % probably so that the last segment also has a well-defined next segment
+ \pgf@nfold@addtoinputsegmentobjects{\pgf@nfold@inputsegmentobject@endofinputsegments}%
+ \expandafter\let\pgf@nfold@inputsegmentobjectsmacro\pgf@nfold@inputsegmentobjects%
+ \let\pgf@next\relax%
+ \else%
+ \ifx#1\pgfsyssoftpath@movetotoken%
+ \let\pgf@next\pgf@nfold@parsemoveto%
+ \else%
+ \ifx#1\pgfsyssoftpath@linetotoken%
+ \let\pgf@next\pgf@nfold@parselineto%
+ \else%
+ \ifx#1\pgfsyssoftpath@curvetosupportatoken%
+ \let\pgf@next\pgf@nfold@parsecurveto%
+ \else%
+ \ifx#1\pgfsyssoftpath@closepathtoken%
+ \let\pgf@next\pgf@nfold@parseclosepath%
+ \else%
+ \ifx#1\pgfsyssoftpath@rectcornertoken%
+ \let\pgf@next\pgf@nfold@parserect%
+ \else%
+ \pgfutil@packageerror{tikz-nfold}{Unrecognised soft path token `#1'}{}%
+ \fi%
+ \fi%
+ \fi%
+ \fi%
\fi%
- \endgroup\bgroup%
+ \fi%
+ \pgf@next}%
+
+\def\pgf@nfold@parsemoveto#1#2{%
+ \def\pgf@nfold@queueinputsegmentobject{\pgf@nfold@inputsegmentobject@moveto{\pgf@x#1\pgf@y#2}}%
+ \def\pgfpoint@nfold@lastparsed{\pgf@x#1\pgf@y#2}%
+ \pgf@nfold@@parsesoftpath%
+}%
+
+% Convert \pgfsyssoftpath@linetotoken{<X>}{<Y>} into the following
+% representation:
+%
+% \pgf@nfold@inputsegmentobject@lineto{<length>}{\pgf@x <Last X> \pgf@y <Last Y>}{\pgf@x <X> \pgf@y <Y>}
+%
+\def\pgf@nfold@parselineto#1#2{%
+ % remove degenerate line segments (reduces glitches)
+ \pgf@process{\pgfpointdiff{\pgfpoint@nfold@lastparsed}{\pgf@x#1\pgf@y#2}}
+ \pgfpointtaxicabnorm\pgf@xa
+ \ifdim\pgf@xa>.1pt\relax
+ \edef\pgf@nfold@temp{%
+ \noexpand\pgf@nfold@inputsegmentobject@lineto{\pgfpoint@nfold@lastparsed}{\pgf@x#1\pgf@y#2}%
+ }%
+ \edef\pgfpoint@nfold@lastparsed{\pgf@x#1\pgf@y#2}%
+ \let\pgfpoint@nfold@lastnonmovetoparsed\pgfpoint@nfold@lastparsed%
+ \expandafter\pgf@nfold@addtoinputsegmentobjects\expandafter{\pgf@nfold@temp}%
+ \fi
+ \pgf@nfold@@parsesoftpath%
+}%
+
+\def\pgf@nfold@parsecurveto#1#2\pgfsyssoftpath@curvetosupportbtoken#3#4\pgfsyssoftpath@curvetotoken#5#6{%
+ \edef\pgf@nfold@temp{%
+ \noexpand\pgf@nfold@inputsegmentobject@curveto{\pgfpoint@nfold@lastparsed}%
+ {\pgf@x#1\pgf@y#2}{\pgf@x#3\pgf@y#4}{\pgf@x#5\pgf@y#6}%
}%
- \state{segment}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{}%
+ \expandafter\pgf@nfold@addtoinputsegmentobjects\expandafter{\pgf@nfold@temp}%
+ \edef\pgfpoint@nfold@lastparsed{\pgf@x#5\pgf@y#6}%
+ \let\pgfpoint@nfold@lastnonmovetoparsed\pgfpoint@nfold@lastparsed%
+ \pgf@nfold@@parsesoftpath%
}%
-% Decoration for the end tip
-\pgfdeclaredecoration{pgf@nfold@tip@end}{segment}{%
- \state{segment}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{}
- \state{final}{%
- \egroup\begingroup%
- \pgf@nfold@check@tip@end% this step already checks for unsupported arrows
- \pgf@nfold@setup@end@tip
- \ifpgf@nfold@tip@implies%
- \pgf@nfold@setupimplies%
- \pgf@nfold@drawimplies%
- \fi%
- \ifpgf@nfold@tip@mapsto
- \pgf@nfold@setupmapsto
- \pgf@nfold@drawmapsto
- \fi
- \endgroup\bgroup%
- }
+\def\pgf@nfold@parseclosepath#1#2{%
+ \edef\pgf@nfold@temp{%
+ \noexpand\pgf@nfold@inputsegmentobject@closepath{\pgfpoint@nfold@lastparsed}{\pgf@x#1\pgf@y#2}%
+ }%
+ \let\pgfpoint@nfold@lastnonmovetoparsed\pgfpoint@nfold@lastparsed%
+ \expandafter\pgf@nfold@addtoinputsegmentobjects\expandafter{\pgf@nfold@temp}%
+ \pgf@nfold@@parsesoftpath%
}%
+% Mostly for the sake of completeness; using TikZ' "\path (0,0) rectangle (1,1);" does not call this code
+\def\pgf@nfold@parserect#1#2\pgfsyssoftpath@rectsizetoken#3#4{%
+ \let\pgf@nfold@orig@@parsesoftpath\pgf@nfold@@parsesoftpath%
+ \let\pgf@nfold@@parsesoftpath\relax%
+ \pgf@nfold@parsemoveto{#1}{#2}%
+ \pgf@xb=#1\relax
+ \pgf@yb=#2\relax
+ \pgf@xc=#3\relax
+ \pgf@yc=#4\relax
+ \advance\pgf@yb\pgf@yc%
+ \edef\pgf@temp{{\the\pgf@xb}{\the\pgf@yb}}%
+ \expandafter\pgf@nfold@parselineto\pgf@temp%
+ \advance\pgf@xb\pgf@xc%
+ \edef\pgf@temp{{\the\pgf@xb}{\the\pgf@yb}}%
+ \expandafter\pgf@nfold@parselineto\pgf@temp%
+ \advance\pgf@yb-\pgf@yc%
+ \edef\pgf@temp{{\the\pgf@xb}{\the\pgf@yb}}%
+ \expandafter\pgf@nfold@parselineto\pgf@temp%
+ \advance\pgf@xb-\pgf@xc%
+ \edef\pgf@temp{{\the\pgf@xb}{\the\pgf@yb}}%
+ \expandafter\pgf@nfold@parselineto\pgf@temp%
+ \let\pgf@nfold@@parsesoftpath\pgf@nfold@orig@@parsesoftpath%
+ \edef\pgf@marshal{\noexpand\pgf@nfold@parsemoveto{\the\pgf@xb}{\the\pgf@yb}}%
+ \pgf@marshal%
+}
+
+\def\pgf@nfold@addtoinputsegmentobjects#1{%
+ %
+ % If there is an input segment object waiting (i.e. a moveto), insert it here.
+ %
+ \ifx\pgf@nfold@queueinputsegmentobject\pgfutil@empty%
+ \else%
+ \let\pgf@nfold@temp\pgf@nfold@queueinputsegmentobject%
+ \let\pgf@nfold@queueinputsegmentobject\pgfutil@empty%
+ \expandafter\pgf@nfold@addtoinputsegmentobjects\expandafter{\pgf@nfold@temp}%
+ \fi%
+ \ifx\pgfpoint@nfold@firstparsed\pgfutil@empty%
+ #1%
+ \let\pgfpoint@nfold@firstparsed\pgf@nfold@inputsegment@first%
+ \fi%
+ \expandafter\def\expandafter\pgf@nfold@inputsegmentobjects\expandafter%
+ {\pgf@nfold@inputsegmentobjects{#1}}%
+}%
+
+
+\def\pgf@nfold@inputsegmentfirst{first}%
+\def\pgf@nfold@inputsegmentmoveto{moveto}%
+\def\pgf@nfold@inputsegmentlineto{lineto}%
+\def\pgf@nfold@inputsegmentcurveto{curveto}%
+\def\pgf@nfold@inputsegmentclosepath{closepath}%
+\def\pgf@nfold@inputsegmentlast{last}%
+
+
+\def\pgf@nfold@inputsegmentobject@moveto#1{%
+ \def\pgf@nfold@inputsegment@first{#1}%
+ \def\pgf@nfold@inputsegment@supporta{#1}%
+ \def\pgf@nfold@inputsegment@supportb{#1}%
+ \def\pgf@nfold@inputsegment@last{#1}%
+ \edef\pgf@nfold@lastmoveto{#1}%
+ \let\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentmoveto%
+}%
+
+\def\pgf@nfold@inputsegmentobject@lineto#1#2{%
+ \def\pgf@nfold@inputsegment@first{#1}%
+ \def\pgf@nfold@inputsegment@last{#2}%
+ \let\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentlineto%
+}%
-\def\pgf@nfold@setup@start@tip{
+\def\pgf@nfold@inputsegmentobject@curveto#1#2#3#4{%
+ \def\pgf@nfold@inputsegment@first{#1}%
+ \def\pgf@nfold@inputsegment@supporta{#2}%
+ \def\pgf@nfold@inputsegment@supportb{#3}%
+ \def\pgf@nfold@inputsegment@last{#4}%
+ \let\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentcurveto%
+}%
+
+\def\pgf@nfold@inputsegmentobject@closepath#1#2{%
+ \def\pgf@nfold@inputsegment@first{#1}%
+ \def\pgf@nfold@inputsegment@last{#2}%
+ \let\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentclosepath%
+}%
+
+\def\pgf@nfold@inputsegmentobject@endofinputsegments{%
+ \let\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentlast%
+}%
+
+
+\def\pgf@nfold@traversepath{%
+ % Transformations are already baked into the path; without this call, they would be applied twice
\pgftransformreset
- \pgftransformshift{\pgf@nfold@original@first}
- \pgftransformrotate{\pgfdecoratedinputsegmentstartangle}
- \pgftransformshift{\pgfpoint{.5*\pgflinewidth}{0pt}}
- \pgftransformscale{\pgf@decoration@nfold@hwidth}
- \pgftransformxscale{-1.}
+ % could likely also use \let\pgf@nfold@currentinputsegmentobjects\parsedsoftpath here
+ \let\pgf@nfold@currentinputsegmentobjects\pgf@nfold@inputsegmentobjects%
+% \let\pgf@nfold@transformtoinputsegment\pgfutil@empty% we may need this one for closepath
+ \pgf@nfold@getnextinputsegmentobject\pgf@nfold@nextinputsegmentobject%
+ \let\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentfirst%
+ \def\pgf@nfold@cur@endangle{0.0}% put in some default value so \pgf@nfold@previous@endangle is not undefined
+ \pgf@nfold@traversepath@
}
-\def\pgf@nfold@setup@end@tip{
- \pgftransformreset
- \pgftransformshift{\pgf@nfold@original@last}
- \pgftransformrotate{\pgf@nfold@cached@endangle}
- \pgftransformshift{\pgfpoint{-.5*\pgflinewidth}{0pt}}
- \pgftransformscale{\pgf@decoration@nfold@hwidth}
+
+
+\def\pgf@nfold@traversepath@{
+ \pgf@nfold@processnextinputsegmentobject%
+ \ifx\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentlast\else
+ \pgf@nfold@handlesegment%
+ \fi%
+ \ifx\pgf@nfold@currentinputsegmentobjects\pgfutil@empty%
+ \let\pgf@next\relax%
+ \else%
+ \let\pgf@next\pgf@nfold@traversepath@%
+ \fi%
+ \pgf@next%
}
-\def\pgf@nfold@setupimplies{%
- \pgfsetroundjoin%
- \pgfsetroundcap%
+
+\def\pgf@nfold@computeangles{
+ \ifx\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentcurveto%
+ \pgf@offset@compute@tangents{\pgf@nfold@inputsegment@first}{\pgf@nfold@inputsegment@supporta}{\pgf@nfold@inputsegment@supportb}{\pgf@nfold@inputsegment@last}
+ \let\pgf@nfold@cur@startangle\pgf@tmp@angle@i
+ \let\pgf@nfold@cur@endangle\pgf@tmp@angle@ii
+ \else%
+ \pgfmathanglebetweenpoints{\pgf@nfold@inputsegment@first}{\pgf@nfold@inputsegment@last}%
+ \edef\pgf@nfold@cur@startangle{\pgfmathresult}
+ \edef\pgf@nfold@cur@endangle{\pgfmathresult}
+ \fi%
}
-\def\pgf@nfold@drawimplies{
- \pgftransformshift{\pgfqpoint{-2pt}{0pt}}
- \pgfpathmoveto{\pgfqpoint{-1.4pt}{2.65pt}}
- \pgfpathcurveto{\pgfqpoint{-0.75pt}{1.25pt}}{\pgfqpoint{1pt}{0.05pt}}{\pgfqpoint{2pt}{0pt}}
- \pgfpathcurveto{\pgfqpoint{1pt}{-0.05pt}}{\pgfqpoint{-0.75pt}{-1.25pt}}{\pgfqpoint{-1.4pt}{-2.65pt}}
+
+\def\pgf@nfold@clampangle{
+ % The computed angles are values between 0 and 360, so their difference is between 360 and -360;
+ % we want the difference to be between -180 and 180
+ \ifdim\pgfmathresult pt<-180pt\relax
+ \pgfutil@tempdima=\pgfmathresult pt
+ \advance\pgfutil@tempdima by 360pt
+ \edef\pgfmathresult{\pgf@sys@tonumber\pgfutil@tempdima}
+ \else\ifdim\pgfmathresult pt>180pt\relax
+ \pgfutil@tempdima=\pgfmathresult pt
+ \advance\pgfutil@tempdima by -360pt
+ \edef\pgfmathresult{\pgf@sys@tonumber\pgfutil@tempdima}
+ \fi\fi
}
-\def\pgf@nfold@setupmapsto{%
- \pgfsetbuttcap%
+
+\def\pgf@nfold@getnextinputsegmentobject#1{%
+ \ifx\pgf@nfold@currentinputsegmentobjects\pgfutil@empty%
+ \let\pgf@next\relax%
+ \else%
+ \def\pgf@nfold@temp{#1}%
+ \let\pgf@next\pgf@nfold@@getnextinputsegmentobject%
+ \fi%
+ \pgf@next%
+}%
+\def\pgf@nfold@@getnextinputsegmentobject{%
+ \expandafter\pgf@nfold@@@getnextinputsegmentobject\pgf@nfold@currentinputsegmentobjects\pgf@stop}%
+\def\pgf@nfold@@@getnextinputsegmentobject#1#2\pgf@stop{%
+ \expandafter\def\pgf@nfold@temp{#1}%
+ \def\pgf@nfold@currentinputsegmentobjects{#2}}%
+
+\def\pgf@nfold@processnextinputsegmentobject{%
+ \let\pgf@nfold@previousinputsegment\pgf@nfold@currentinputsegment%
+ \let\pgf@nfold@previous@endangle\pgf@nfold@cur@endangle%
+ \let\pgf@nfold@currentinputsegmentobject\pgf@nfold@nextinputsegmentobject%
+ \pgf@nfold@getnextinputsegmentobject\pgf@nfold@nextinputsegmentobject%
+ \pgf@nfold@nextinputsegmentobject% parse the *next* input segment so we can compute its angles
+ \pgf@nfold@computeangles%
+ \let\pgf@nfold@next@first\pgf@nfold@inputsegment@first%
+ \let\pgf@nfold@next@supporta\pgf@nfold@inputsegment@supporta%
+ \let\pgf@nfold@next@supportb\pgf@nfold@inputsegment@supportb%
+ \let\pgf@nfold@next@last\pgf@nfold@inputsegment@last%
+ \let\pgf@nfold@next@segmenttype\pgf@nfold@currentinputsegment%
+ \let\pgf@nfold@next@startangle\pgf@nfold@cur@startangle%
+ \pgf@nfold@currentinputsegmentobject% get the current segment into \pgf@nfold@inputsegment@first etc.
+ \pgf@nfold@computeangles%
+ % Compute the angle differences at the start and end (between -180 and +180 degrees)
+ % using \pgfmathsubtract@ is more readable and no less efficient than computing this manually
+ \pgfmathsubtract@{\pgf@nfold@cur@startangle}{\pgf@nfold@previous@endangle}
+ \pgf@nfold@clampangle
+ \edef\pgf@nfold@deltaphi@start{\pgfmathresult}
+ \pgfmathsubtract@{\pgf@nfold@next@startangle}{\pgf@nfold@cur@endangle}
+ \pgf@nfold@clampangle
+ \edef\pgf@nfold@deltaphi@end{\pgfmathresult}
}
-\def\pgf@nfold@drawmapsto{
- % Try: use the same width as the arrow head, minus the caps
- \pgfpathmoveto{\pgfqpoint{0pt}{2.65pt}}
- \pgfpathlineto{\pgfqpoint{0pt}{-2.65pt}}
+
+
+%
+% Iterating over the parsed soft path
+% -----------------------------------
+%
+
+\newcount\pgf@nfold@index
+\def\pgf@nfold@run@loop{%
+ \pgf@nfold@index=\pgf@nfold@order%
+ \pgf@nfold@run@loop@%
}
-% This macro extends the arrow body to the tips
-% parameter: s=start, e=end
-\def\pgf@nfold@extendtotip#1{
- % Do not extend the arrow for index=1 and index=order, it already ends in the right place
- \ifnum\pgf@nfold@index>1\ifnum\pgf@nfold@index<\pgf@nfold@dec@order\relax%
- % Step 1: Find the intersection of the arrow's path with the head. This is is computationally
- % expensive, so we first look if the value has been precomputed.
- \ifcsname tikz@arrow@intersec@cache@\pgf@nfold@index @\the\pgf@nfold@dec@order\endcsname
- \csname tikz@arrow@intersec@cache@\pgf@nfold@index @\the\pgf@nfold@dec@order\endcsname
- \pgfstorepoint\pgf@nfold@arrowintersect
- \else
- % the intersection has not been precomputed, thus compute on the fly here
- \pgfintersectionofpaths{
- % specify the tip
- \pgf@nfold@drawimplies
- }{
- % extend the body to intersect the tip
- \pgfpathmoveto{\pgfqpoint{-3pt}{\pgf@shiftdec@fraction pt}}
- \pgfpathlineto{\pgfqpoint{3pt}{\pgf@shiftdec@fraction pt}}
- }
- \ifnum\pgfintersectionsolutions>0
- \pgfpointintersectionsolution{1}
- \pgfstorepoint\pgf@nfold@arrowintersect
- \makeatother
- \typeout{tikz-nfold: computed intersection cache@\pgf@nfold@index @\the\pgf@nfold@dec@order: \string\pgfqpoint{\the\pgf@x}{\the\pgf@y}^^J}
- \makeatletter
- \else
- % this is a failsafe and should never be reached
- \pgfwarning{'nfold': did not find intersection}
- \pgfqpoint{0pt}{\pgf@shiftdec@fraction pt}
- \pgfstorepoint\pgf@nfold@arrowintersect
- \fi
- \fi% if precomputed
- % Step 2: Extend the arrow body to the intersection point.
- % If the tip is at the beginning of the path, we have to move to the intersection
- % and then draw a line to the "regular" starting point. The subsequent segment then
- % should omit its moveto.
- % If the tip is at the end, we are already in the right position and only need to extend
- % the current path to the intersection point.
- \pgftransformreset
- \if#1s
- \pgftransformshift{\pgf@nfold@original@first}
- \pgftransformrotate{\pgfdecoratedinputsegmentstartangle}
- \pgftransformxscale{-1}
- \else
- \pgftransformshift{\pgf@nfold@original@last}
- \pgftransformrotate{\pgfdecoratedinputsegmentendangle}
- \fi
- \pgftransformshift{\pgfpoint{-2*\pgf@decoration@nfold@hwidth-.5*\pgflinewidth}{0pt}}
- \pgftransformscale{\pgf@decoration@nfold@hwidth}
- \if#1s
- \pgfpathmoveto{\pgf@nfold@arrowintersect}
- % This is precisely the start of the body, shifted vertically
- \pgfpathlineto{\pgfqpoint{-0.06pt}{\pgf@shiftdec@fraction pt}}
- \pgf@nfold@continuesegmenttrue
- \else\if#1e
- \pgfpathlineto{\pgf@nfold@arrowintersect}
- \else
- \pgferror{Invalid argument to \string\pgf@nfold@extendtotip: \meaning#1}
- \fi\fi
- \pgftransformreset
-\fi\fi% if 1 < i < nArrows
+\def\pgf@nfold@run@loop@{%
+ \pgf@nfold@loop@inner%
+ \advance\pgf@nfold@index by -1\relax
+ \ifnum\pgf@nfold@index>0\relax%
+ \pgf@nfold@run@loop@%
+ \fi%
+}
+
+\def\pgf@nfold@loop@inner{%
+ \pgfmathsetmacro{\pgf@nfold@shift@fraction}%
+ {-1+2*(\pgf@nfold@index-1)/(\pgf@nfold@order-1)}
+ \pgfmathsetlengthmacro{\pgf@nfold@shiftamount}{\pgf@nfold@hwidth*\pgf@nfold@shift@fraction}
+ \pgf@nfold@traversepath%
+ \pgfsyssoftpath@flushcurrentpath%
+ \pgf@up@action%
+}
+
+% Computes both the width of the component lines into \pgf@x and the distance
+% from the center to the outermost line centers into \pgf@y
+% from the current values of \pgflinewidth and \pgfinnerlinewidth.
+\def\pgf@nfold@compute@widths@from@double{
+ \pgf@x=\pgflinewidth\relax%
+ \pgf@y=\pgf@x\relax%
+ \advance\pgf@x-\pgfinnerlinewidth\relax%
+ \advance\pgf@y+\pgfinnerlinewidth\relax%
+ \pgf@x=.5\pgf@x\relax%
+ \pgf@y=.25\pgf@y\relax%
+}
+
+\def\pgf@nfold@render@cached@softpath{%
+ \pgfscope% must use a scope, otherwise we break the arrow tips
+ \pgfprocessround{\cachedpath}{\cachedpath}% remove tokens from the soft path
+ \pgf@nfold@parsesoftpath{\cachedpath}{\parsedsoftpath}
+ \pgf@nfold@parsearrows
+ % Compute the full and constituent part line widths
+ \pgf@nfold@compute@widths@from@double%
+ \pgfsetlinewidth\pgf@x%
+ \edef\pgf@nfold@hwidth{\the\pgf@y}
+ \pgf@nfold@run@loop
+ \endpgfscope
}
%
-% n-fold decoration
-% -----------------
+% user interface and pgf/TikZ keys
+% --------------------------------
%
-% This decoration is intended to be used by the end user. It replaces a given path by n parallel versions of the path.
-
-\newcount\tikz@nfold@loop@index
-\def\tikz@nfold@run@loop#1{%
- \edef\tikz@nfold@postaction{%
- \tikz@nfold@postaction% this already includes a comma
- postaction={%
- draw,arrows=-,shorten <=0pt,shorten >=0pt,% disable shorten, is implemented in the pre-pass
- decorate,decoration={%
- pgf@nfold@shift, nfold order=#1, nfold index=\the\tikz@nfold@loop@index%
- }%
- },%
- }
- \advance\tikz@nfold@loop@index by -1\relax
- \ifnum\tikz@nfold@loop@index>0%
- \tikz@nfold@run@loop{#1}%
- \fi%
+
+% Outputs a provided soft path in #1 offset by a distance provided in #2.
+\def\pgfoffsetpath#1#2{%
+ \begingroup
+ \pgfmathsetlengthmacro\pgf@nfold@hwidth{#2}
+ % \pgf@nfold@hwidth must always be positive
+ \pgf@x=\pgf@nfold@hwidth\relax
+ \ifdim\pgf@x<0pt\relax
+ \pgf@x=-\pgf@x
+ \def\pgf@nfold@shift@fraction{-1}
+ \else
+ \def\pgf@nfold@shift@fraction{1}
+ \fi
+ \edef\pgf@nfold@hwidth{\the\pgf@x}
+ \pgfoffsetpathqfraction{#1}{\pgf@nfold@hwidth}{\pgf@nfold@shift@fraction}
+ \endgroup
+}
+
+% Outputs a provided soft path in #1 offset by #3*#2 where #2 is a length (>= 0 pt)
+% and #3 is a number between -1.0 and 1.0. This differs from \pgfoffsetpath{#1}{#2*#3}
+% in how the joins between segments are rendered. In particular, \pgfoffsetpathfraction{#1}{10pt}{0}
+% does *not* yield the original path, but a new path in the centre of #1 drawn at line width 20pt.
+%
+\def\pgfoffsetpathfraction#1#2#3{%
+ \begingroup
+ \pgfmathsetlengthmacro\pgf@nfold@hwidth{#2}
+ \pgfmathsetmacro\pgf@nfold@shift@fraction{#3}
+ \pgfoffsetpathqfraction{#1}{\pgf@nfold@hwidth}{\pgf@nfold@shift@fraction}
+ \endgroup
+}
+
+% This has the same output as the #3-th segment of nfold=#4.
+\def\pgfoffsetpathindex#1#2#3#4{%
+ \begingroup
+ \pgfmathsetmacro\pgf@nfold@shift@fraction{-1+2*(#3-1)/(#4-1)}
+ \pgfoffsetpathqfraction{#1}{#2}{\pgf@nfold@shift@fraction}
+ \endgroup
+}
+
+% A quick version that skips processing the input values
+\def\pgfoffsetpathqfraction#1#2#3{%
+ \begingroup
+ \pgfprocessround{#1}{\cachedpath}% remove tokens from the soft path
+ \pgf@nfold@parsesoftpath{\cachedpath}{\parsedsoftpath}
+ \pgf@x=#2\relax
+ \edef\pgf@nfold@hwidth{\the\pgf@x}
+ \edef\pgf@nfold@shift@fraction{#3}
+ \pgf@x=\pgf@nfold@shift@fraction\pgf@x\relax
+ \edef\pgf@nfold@shiftamount{\the\pgf@x}
+ \pgf@nfold@traversepath%
+ \endgroup
+}
+
+
+\pgfkeys{
+ /pgf/nfold/.code={%
+ \pgf@nfold@order=#1\relax%
+ \ifnum\pgf@nfold@order<1\relax%
+ \pgfutil@packageerror{tikz-nfold}{The key /pgf/nfold must take a value of at least 1, got \the\pgf@nfold@order}{}%
+ \fi%
+ % If nfold > numcached AND intersections is not loaded AND we draw an Implies tip, we get an error.
+ % We check the first two conditions now and set the respective flag
+ \ifnum\pgf@nfold@order>\pgf@nfold@intersec@numcached\relax
+ \ifdefined\pgfintersectionofpaths\else
+ \pgf@nfold@intersectionsnotloadedtrue
+ \fi
+ \fi
+ },
+ /pgf/nfold/.default=2
}
+
+
+% use \tikzset for scoping reasons, does not appear to be equivalent to \pgfset{/tikz/...=...}
\tikzset{
nfold/.code={
- \tikzset{draw=none}
- \pgf@nfold@dec@order=#1\relax
- \ifnum\pgf@nfold@dec@order<2
- \pgferror{'nfold' must take a value of at least 2, got \the\pgf@nfold@dec@order}
- \fi
- \tikz@nfold@loop@index=\pgf@nfold@dec@order
- % The final style has the following form:
- % [draw=none,
- % postaction={
- % draw=none,decorate,decoration=pgf@nfold@storedata,
- % postaction={draw,arrows=-,decorate,decoration={pgf@nfold@shift,nfold order=#1,nfold index=1}},
- % [...],
- % postaction={draw,arrows=-,decorate,decoration={pgf@nfold@shift,nfold order=#1,nfold index=#1}}
- % }
- % ]
- % We build the postactions piece by piece in the macro \tikz@nfold@postaction. Then we apply it
- % using \tikzset and .expand once, so potential future changes to \tikz@nfold@postaction do not
- % affect the settings in tikz.
- %
- \def\tikz@nfold@postaction{}
- \tikz@nfold@run@loop{#1}
- \edef\tikz@nfold@postaction{%
- draw=none,decorate,decoration=pgf@nfold@storedata,%
- \tikz@nfold@postaction%
- postaction={
- draw,arrows=-,shorten <=0pt,shorten >=0pt,
- decorate,decoration=pgf@nfold@tip@start},%
- postaction={
- draw,arrows=-,shorten <=0pt,shorten >=0pt,
- decorate,decoration=pgf@nfold@tip@end},%
+ \edef\pgf@tmp{\noexpand\pgfkeys{/pgf/nfold=#1}}
+ % patch \tikz@double@setup to set /pgf/nfold=#1 as well
+ \expandafter\expandafter\expandafter\def%
+ \expandafter\expandafter\expandafter\tikz@double@setup%
+ \expandafter\expandafter\expandafter{\expandafter\tikz@double@setup\pgf@tmp}
+ },
+ nfold/.default=2,
+ scaling nfold/.code={%
+ \pgfscope% scope to contain \tikz@double@setup
+ \tikz@double@setup
+ % extract double distance between line centers into \pgf@x
+ \pgf@nfold@compute@widths@from@double
+ \pgf@y=2\pgf@y
+ % store (order-1)*\pgf@x in \pgf@xa
+ \c@pgf@counta=#1
+ \advance\c@pgf@counta by -1\relax
+ \global\pgf@y=\c@pgf@counta\pgf@y
+ \endpgfscope
+ \tikzset{
+ double distance between line centers=\pgf@y,
+ nfold=#1
+ }
+ },
+ scaling nfold/.default=2,
+ % This simply defines the key if tikzcd is not loaded, so we don't run into any errors
+ commutative diagrams/scaling nfold/.code={
+ \pgfscope% scope to contain \tikz@double@setup
+ \tikz@double@setup
+ % extract double distance between line centers into \pgf@x
+ \pgf@nfold@compute@widths@from@double
+ % store (order-1)*\pgf@y in \pgf@ya
+ \c@pgf@counta=#1
+ \advance\c@pgf@counta by -1\relax
+ \pgf@ya=\c@pgf@counta\pgf@y
+ % compute the label offset, which is (order-2)*\pgf@y + .5*\pgf@x
+ \advance\c@pgf@counta by -1\relax
+ \pgf@xa=\c@pgf@counta\pgf@y
+ \advance\pgf@xa by .5\pgf@x
+ % save the results in \pgf@x and \pgf@y
+ \global\pgf@x=\pgf@xa
+ \global\pgf@y=2\pgf@ya
+ \endpgfscope
+ \tikzset{
+ commutative diagrams/every label/.append style/.expanded={outer sep=\the\pgf@x},
+ double distance between line centers=\pgf@y,
+ nfold=#1
}
- \tikzset{postaction/.expand once=\tikz@nfold@postaction}
},
- nfold/.default=2
+ commutative diagrams/scaling nfold/.default=2
}
\endinput