summaryrefslogtreecommitdiff
path: root/graphics/pgf/contrib
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2023-05-05 03:01:19 +0000
committerNorbert Preining <norbert@preining.info>2023-05-05 03:01:19 +0000
commit3cf2e76fc502cb474081c80838c95ecaba2f386d (patch)
tree5a1423600de3117087ef17bf28f4120c8f6c9fd6 /graphics/pgf/contrib
parentf5c7c9695f181a8fd6e28b572edea03ffc59de93 (diff)
CTAN sync 202305050301
Diffstat (limited to 'graphics/pgf/contrib')
-rw-r--r--graphics/pgf/contrib/tikz-nfold/README.md2
-rw-r--r--graphics/pgf/contrib/tikz-nfold/pgflibrarybezieroffset.code.tex291
-rw-r--r--graphics/pgf/contrib/tikz-nfold/tikz-nfold-doc.pdfbin302366 -> 302747 bytes
-rw-r--r--graphics/pgf/contrib/tikz-nfold/tikz-nfold-doc.tex30
-rw-r--r--graphics/pgf/contrib/tikz-nfold/tikzlibrarynfold.code.tex1040
5 files changed, 796 insertions, 567 deletions
diff --git a/graphics/pgf/contrib/tikz-nfold/README.md b/graphics/pgf/contrib/tikz-nfold/README.md
index 97d6549f60..bb89176466 100644
--- a/graphics/pgf/contrib/tikz-nfold/README.md
+++ b/graphics/pgf/contrib/tikz-nfold/README.md
@@ -1,5 +1,5 @@
# tikz-nfold
-## Version 0.1.0
+## Version 0.1.1
This library adds higher-order paths to [TikZ](https://ctan.org/pkg/pgf) and also fixes some graphical issues with TikZ' `double` paths, used e.g. in wide arrows. It is also compatible with [tikz-cd](https://ctan.org/pkg/tikz-cd), adding support for triple and higher arrows. See the [documentation](tikz-nfold-doc.pdf) for full details.
diff --git a/graphics/pgf/contrib/tikz-nfold/pgflibrarybezieroffset.code.tex b/graphics/pgf/contrib/tikz-nfold/pgflibrarybezieroffset.code.tex
index 7d9a86312f..275fa0fdf3 100644
--- a/graphics/pgf/contrib/tikz-nfold/pgflibrarybezieroffset.code.tex
+++ b/graphics/pgf/contrib/tikz-nfold/pgflibrarybezieroffset.code.tex
@@ -16,60 +16,102 @@
% This work consists of the files pgflibrarybezieroffset.code.tex,
% tikzlibrarynfold.code.tex, tikz-nfold-doc.tex, and tikz-nfold-doc.pdf.
+
+% Don't delete this code in case we need it in the future
+%
% Split a Bezier curve (de Casteljau's algorithm)
% #1 = time (between 0 and 1)
% #2-#5: control points
% Outputs the first part into \pgf@splitbezier@i@i, ... , \pgf@splitbezier@i@iv,
% and the second part into \pgf@splitbezier@ii@i, ... , \pgf@splitbezier@ii@iv.
+%\def\pgf@splitbezier#1#2#3#4#5{%
+% % based on pgfcorepoints.code.tex, \pgfpointcurveattime
+% \edef\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
+% \pgfextract@process\pgf@splitbezier@ii@iv{#5}%
+% \pgf@xc=\pgf@x%
+% \pgf@yc=\pgf@y%
+% % P^0_2
+% \pgf@process{#4}%
+% \pgf@xb=\pgf@x%
+% \pgf@yb=\pgf@y%
+% % P^0_1
+% \pgf@process{#3}%
+% \pgf@xa=\pgf@x%
+% \pgf@ya=\pgf@y%
+% % P^0_0
+% \pgfextract@process\pgf@splitbezier@i@i{#2}%
+% % First iteration:
+% % P^1_0
+% \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\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}%
+% % P^2_0
+% \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\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}%
+% % P^3_0
+% \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
+%}
-% TODO this code can be simplified to splitting at 0.5 only;
-% 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
-\def\pgf@splitbezier#1#2#3#4#5{%
+% Split the Bezier curve defined by #1-#4 at t=0.5 using de Casteljau's algorithm
+\def\pgf@halfsplitbezier#1#2#3#4{%
% based on pgfcorepoints.code.tex, \pgfpointcurveattime
- \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
- \pgfextract@process\pgf@splitbezier@ii@iv{#5}%
+ \pgfextract@process\pgf@splitbezier@ii@iv{#4}%
\pgf@xc=\pgf@x%
\pgf@yc=\pgf@y%
% P^0_2
- \pgf@process{#4}%
+ \pgf@process{#3}% removing these does not yield a significant speedup
\pgf@xb=\pgf@x%
\pgf@yb=\pgf@y%
% P^0_1
- \pgf@process{#3}%
+ \pgf@process{#2}%
\pgf@xa=\pgf@x%
\pgf@ya=\pgf@y%
% P^0_0
- \pgfextract@process\pgf@splitbezier@i@i{#2}%
+ \pgfextract@process\pgf@splitbezier@i@i{#1}%
% First iteration:
% P^1_0
- \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{}%
+ \pgf@x=.5\pgf@x\advance\pgf@x by.5\pgf@xa%
+ \pgf@y=.5\pgf@y\advance\pgf@y by.5\pgf@ya%
+ \edef\pgf@splitbezier@i@ii{\pgf@x\the\pgf@x\pgf@y\the\pgf@y}%
% 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%
+ \pgf@xa=.5\pgf@xa\advance\pgf@xa by.5\pgf@xb%
+ \pgf@ya=.5\pgf@ya\advance\pgf@ya by.5\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\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}%
+ \pgf@xb=.5\pgf@xb\advance\pgf@xb by.5\pgf@xc%
+ \pgf@yb=.5\pgf@yb\advance\pgf@yb by.5\pgf@yc%
+ \edef\pgf@splitbezier@ii@iii{\pgf@x\the\pgf@xb\pgf@y\the\pgf@yb}%
% P^2_0
- \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{}%
+ \pgf@x=.5\pgf@x\advance\pgf@x by.5\pgf@xa%
+ \pgf@y=.5\pgf@y\advance\pgf@y by.5\pgf@ya%
+ \edef\pgf@splitbezier@i@iii{\pgf@x\the\pgf@x\pgf@y\the\pgf@y}%
% 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\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}%
+ \pgf@xa=.5\pgf@xa\advance\pgf@xa by.5\pgf@xb%
+ \pgf@ya=.5\pgf@ya\advance\pgf@ya by.5\pgf@yb%
+ \edef\pgf@splitbezier@ii@ii{\pgf@x\the\pgf@xa\pgf@y\the\pgf@ya}%
% P^3_0
- \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{}%
+ \pgf@x=.5\pgf@x\advance\pgf@x by.5\pgf@xa%
+ \pgf@y=.5\pgf@y\advance\pgf@y by.5\pgf@ya%
+ \edef\pgf@splitbezier@i@iv{\pgf@x\the\pgf@x\pgf@y\the\pgf@y}%
\let\pgf@splitbezier@ii@i\pgf@splitbezier@i@iv
}
@@ -130,6 +172,7 @@
\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.
@@ -173,6 +216,11 @@
% 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@bezier@segment@{#1}{#2}{#3}{#4}{#5}%
+}
+
+% this version assumes that the tangents have been precomputed in \pgf@tmp@tang@i and @ii
+\def\pgf@offset@bezier@segment@#1#2#3#4#5{%
% offset A1
% compute the normal
\pgf@tmp@tang@i
@@ -272,13 +320,6 @@
% #1-#4: control points of the whole Bezier curve
% #5: offset
-% \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}%
}
@@ -291,95 +332,122 @@
% #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)
\def\pgf@nfold@callback@move#1#2#3#4#5{%
- \ifnum#5=0\relax\pgfpathmoveto{#1}\fi%
+ \if#50\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.
\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.
+% This macro subdivides and offsets a curve and executes a given callback on every offset simple segment.
+% #1-#4: control points of the segment
+% #5: =0 if this is the first segment of the curve, =1 otherwise
+% #6: callback (see \pgf@nfold@callback@move) for the parameters
\def\pgfoffsetcurvecallback#1#2#3#4#5#6{%
- \pgf@subdivideandoffsetcurve{#1}{#2}{#3}{#4}{#5}{\pgf@offset@max@recursion}{0}{#6}%
+ \edef\pgf@offset@tmp@callback##1##2##3##4##5{%
+ \noexpand\pgf@offset@bezier@segment{##1}{##2}{##3}{##4}{#5}%
+ \noexpand#6{\noexpand\pgf@bezier@offset@i}{\noexpand\pgf@bezier@offset@ii}{\noexpand\pgf@bezier@offset@iii}{\noexpand\pgf@bezier@offset@iv}{##5}%
+ }
+ \pgf@subdividecurve{#1}{#2}{#3}{#4}{\pgf@offset@max@recursion}{0}{\pgf@offset@tmp@callback}%
}
-% 6 parameters:
+% This macro subdivides (but does not offset) a curve and executes a callback on every simple segment.
% #1-#4: control points
-% #5: offset
-% #6: recursion limit (decreases on every recursive call)
-% #7: =0 if this is the start of the curve, =1 otherwise;
-% #8: callback for output (see above)
+% #5: recursion limit (decreases on every recursive call)
+% #6: =0 if this is the start of the curve, =1 otherwise
+% #7: callback for output (see above)
\newif\ifpgf@offset@subdivide
-\def\pgf@subdivideandoffsetcurve#1#2#3#4#5#6#7#8{%
- % we need a group to avoid overwriting variables in recursive calls
+\def\pgf@subdividecurve#1#2#3#4#5#6#7{%
\begingroup%
- \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@subdividecurve@{#5}{#6}{#7}%
+ \endgroup%
+}
+
+% This macro assumes that the curve is defined in \pgf@ctrl@i-\pgf@ctrl@iv
+% and that its tangents have already been computed.
+\def\pgf@subdividecurve@#1#2#3{%
+ \pgf@offset@subdividefalse%
+ \c@pgf@counta=#1\relax
+ \advance\c@pgf@counta by -1
+ % Use the non-degenerate tangents for the simplicity check
+ \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%
+ % 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%
- \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%
- \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%
+ \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(iv-i) < veclen(ii-i) + veclen(iv-iii)
+ \ifdim\pgf@xa<\pgf@xb
+ \pgf@offset@subdividetrue
+ \fi
\fi%
- \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}%
+ \fi%
+ \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
+ #3{\pgf@ctrl@i}{\pgf@ctrl@ii}{\pgf@ctrl@iii}{\pgf@ctrl@iv}{#2}%
+ \else
+ % split the non-simple segment and execute recursive calls
+ \pgf@halfsplitbezier{\pgf@ctrl@i}{\pgf@ctrl@ii}{\pgf@ctrl@iii}{\pgf@ctrl@iv}%
+ % compute the tangent at the spitting point; this vector is only zero if the curve
+ % is degenerate to a point, in which case we can't compute the tangent anyway
+ \pgfextract@process\pgf@middletangent{%
+ \pgfpointnormalised{\pgfpointdiff{\pgf@splitbezier@i@iii}{\pgf@splitbezier@i@iv}}}%
+ \begingroup%
+ % we need a group to avoid overwriting variables in recursive calls
+ \let\pgf@tmp@tang@ii\pgf@middletangent%
+ \let\pgf@ctrl@i\pgf@splitbezier@i@i%
+ \let\pgf@ctrl@ii\pgf@splitbezier@i@ii%
+ \let\pgf@ctrl@iii\pgf@splitbezier@i@iii%
+ \let\pgf@ctrl@iv\pgf@splitbezier@i@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}%
+ \pgf@subdividecurve@{\c@pgf@counta}{#2}{#3}%
+ \endgroup%
+ \begingroup%
+ \let\pgf@tmp@tang@i\pgf@middletangent%
+ \let\pgf@ctrl@i\pgf@splitbezier@ii@i%
+ \let\pgf@ctrl@ii\pgf@splitbezier@ii@ii%
+ \let\pgf@ctrl@iii\pgf@splitbezier@ii@iii%
+ \let\pgf@ctrl@iv\pgf@splitbezier@ii@iv%
+ \pgf@subdividecurve@{\c@pgf@counta}{1}{#3}%
+ \endgroup%
\fi%
- \endgroup%
+ \else%
+ % curve is simple
+ #3{\pgf@ctrl@i}{\pgf@ctrl@ii}{\pgf@ctrl@iii}{\pgf@ctrl@iv}{#2}%
+ \fi%
}
@@ -391,14 +459,25 @@
% similar to the macros for curves.
%
\def\pgfoffsetline#1#2#3{%
- \pgfpointscale{#3}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}%
+ \pgfmathparse{#3}%
+ \pgfoffsetline@{#1}{#2}{\pgfmathresult}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}%
+}
+
+% a quicker version in case we already know the tangent and #3 is a number without unit
+\def\pgfoffsetline@#1#2#3#4{%
+ \pgfqpointscale{#3}{#4}%
\pgf@xc=-\pgf@y
\pgf@yc=\pgf@x
\pgfpathmoveto{\pgfpointadd{#1}{\pgfqpoint{\pgf@xc}{\pgf@yc}}}%
\pgfpathlineto{\pgfpointadd{#2}{\pgfqpoint{\pgf@xc}{\pgf@yc}}}%
}
+
\def\pgfoffsetlinenomove#1#2#3{%
- \pgfpointscale{#3}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}%
+ \pgfoffsetlinenomove@{#1}{#2}{#3}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}%
+}
+
+\def\pgfoffsetlinenomove@#1#2#3#4{%
+ \pgfqpointscale{#3}{#4}%
\pgf@xc=-\pgf@y
\pgf@yc=\pgf@x
\pgfpathlineto{\pgfpointadd{#2}{\pgfqpoint{\pgf@xc}{\pgf@yc}}}%
diff --git a/graphics/pgf/contrib/tikz-nfold/tikz-nfold-doc.pdf b/graphics/pgf/contrib/tikz-nfold/tikz-nfold-doc.pdf
index 706890046b..a02b30a571 100644
--- a/graphics/pgf/contrib/tikz-nfold/tikz-nfold-doc.pdf
+++ b/graphics/pgf/contrib/tikz-nfold/tikz-nfold-doc.pdf
Binary files differ
diff --git a/graphics/pgf/contrib/tikz-nfold/tikz-nfold-doc.tex b/graphics/pgf/contrib/tikz-nfold/tikz-nfold-doc.tex
index 45cdf906c7..60b1d03202 100644
--- a/graphics/pgf/contrib/tikz-nfold/tikz-nfold-doc.tex
+++ b/graphics/pgf/contrib/tikz-nfold/tikz-nfold-doc.tex
@@ -44,8 +44,8 @@
\newcommand{\tikznfold}{\texttt{/tikz/nfold}}
\pgfkeys{/tkzexample/every tkzexample/.style={
- code=violet!15, graphic=orange!20, very small,above skip={\vskip1em},below skip={\vskip1em}
- }
+ code=violet!15, graphic=orange!20, very small,above skip={\vskip1em},below skip={\vskip1em}
+ }
}
\newcommand{\tv}{\vec{t}}
@@ -203,8 +203,8 @@ This package is by no means perfect, and even if it were, there would still be s
\subsection{Fixable / wish list}
\begin{itemize}
\item \nfold{} is significantly slower than \tikzdouble. Part of the reason is that the construction is far more complex, but the code is also far from fully optimised.
- \item Closing paths (i.e.\ using \texttt{-- cycle}) is not yet fully supported: The output will not be correct if the angle between the first and last segment differs from zero.
\item Some rare cases of curves are not offset correctly. The reasons for that are discussed below in \cref{subsec:subdivisionUsed}. Usually, slightly changing the control points or values of the curve will fix the problem. If you find any, please open an issue.
+ \item Closed paths can glitch slightly when the final segment is very short and has non-zero angles on both ends.
\end{itemize}
\subsection{Impossible or very hard to fix}
@@ -240,17 +240,17 @@ This package is by no means perfect, and even if it were, there would still be s
\end{tikzpicture}
\end{tkzexample}
This cannot be fixed without extensive use of the \texttt{intersections} library, hurting the performance, and the result might still not look great for orders $\geq 3$.
- \item Very short \emph{curves} with large angles at the ends result in a glitched output:
+ \item Very short \emph{curves} with large angles at the ends result in a glitched output:\nopagebreak
\begin{tkzexample}[latex=3.75cm,pre=\disablewarnings]
-\begin{tikzpicture}
+\begin{tikzpicture}[line join=round]
\draw[black] (1,0) -- (3,0) -- (1.5,1.2) -- (3.8,0.85);
- \draw[red, line width=1pt, double distance=.6cm, nfold]
+ \draw[red, line width=1pt, double distance=.7cm, nfold]
(1,0) -- (3,0) -- (1.5, 1.2) -- (3.8,0.85);
- \draw[blue, double distance=.6cm, nfold] (1,0) -- (3,0)
+ \draw[blue, double distance=.7cm, nfold] (1,0) -- (3,0)
to[relative, out=1, in=179] (1.5, 1.2) -- (3.8,0.85);
\end{tikzpicture}
\end{tkzexample}
- This issue has been fixed for \emph{straight lines} in version \texttt{0.1.0} (note how the red line is offset correctly), but it is much harder to fix for curves.
+ This issue has been fixed for \emph{straight} lines in version \texttt{0.1.0} (note how the red line is offset correctly), but it is much harder to fix for curves.
\item Changing joins in \verb|\pgfsys@beginscope| without an accompanying \TeX{} group may cause inconsistent behaviour in the joins:
\begin{tkzexample}[latex=2cm]
\makeatletter
@@ -329,6 +329,12 @@ Here we see how the commands can be used to customise $n$-fold paths:
\section{Version history}
\begin{itemize}
+ \item \textbf{v0.1.1}: Closing paths and structural changes
+ \begin{itemize}
+ \item Support for closed paths (\texttt{cycle} and \verb|\pgfpathclose|)
+ \item Significant performance improvements due to structural changes
+ \item Minor bug fixes and optimisations
+\end{itemize}
\item \textbf{v0.1.0}: Major overhaul
\begin{itemize}
\item Support for arbitrary arrow tips
@@ -395,17 +401,17 @@ In this library we instead take a recursive approach:
\begin{verbatim}
def makeSimple(A, level):
if isSimple(A):
- segments += [A]
+ segments.append(A)
else:
if level < 0:
Display a warning
- segments += [A]
+ segments.append(A)
else:
first, second = split(A, t=0.5)
makeSimple(first, level-1)
makeSimple(second, level-1)
\end{verbatim}
-The default maximum depth is 5, so the curve is split into at most $2^5 = 32$ segments. This has the downside that some simple but not fully simple curves may remain undetected and be offset slightly incorrectly. If you encounter examples of such curves with bad outputs or if you have any ideas for additional constraints to add to \cref{def:simpleCurve} that can be checked with reasonable computational effort, please be in touch.
+The default maximum depth is 5, so the curve is split into at most $2^5 = 32$ segments. This has the downside that some simple but not fully simple curves may remain undetected and be offset slightly incorrectly. If you encounter examples of such curves with bad outputs, or if you have any ideas for additional constraints to add to \cref{def:simpleCurve} that can be checked with reasonable computational effort, please be in touch.
\subsection{Offsetting simple Bézier curves}
@@ -414,7 +420,7 @@ Disregarding edge cases (which will be discussed later), offsetting the curve wo
\begin{enumerate}
\item Construct lines orthogonal to the tangent in $A_1$ and $A_4$ and find their intersection. This point is called the \emph{origin} of the curve, denoted by $O$.
\item The new control points $A'_1$ and $A'_4$ are given by $A_1$ and $A_4$ offset orthogonally to the tangent.
- \item Construct a ray from $A'_1$ parallel to the tangent in $A_1$, and construct another ray from the origin through $A_2$. Now $A'_2$ is given by the intersection of those rays.
+ \item Construct a ray from $A'_1$ parallel to the tangent in $A_1$, and construct another ray from $O$ through $A_2$. Now $A'_2$ is defined to be the intersection of those rays.
\item $A'_3$ can be constructed similarly.
\end{enumerate}
The construction is shown in the following picture:
diff --git a/graphics/pgf/contrib/tikz-nfold/tikzlibrarynfold.code.tex b/graphics/pgf/contrib/tikz-nfold/tikzlibrarynfold.code.tex
index 75c3cdc0d3..4cc014355b 100644
--- a/graphics/pgf/contrib/tikz-nfold/tikzlibrarynfold.code.tex
+++ b/graphics/pgf/contrib/tikz-nfold/tikzlibrarynfold.code.tex
@@ -21,6 +21,20 @@
%
+% General idea
+% ============
+%
+% In order to offset a path, we must first analyse and slightly modify it. For example,
+% the start and end points of segments need to be relocated slightly in order to make room
+% for the joins, and most curves must be subdivided for the offsetting algorithm. To avoid
+% redundant computations in n-fold paths, we pre-compute as much data as possible and store
+% all this information in a "parsed path" macro. The structure of the parsed path resembles
+% the structure of a pgf softpath, but it consists of different tokens.
+%
+
+
+
+%
% Intercepting join settings
% --------------------------
%
@@ -57,23 +71,6 @@
\let\pgf@cached@linejoin=r%
}
-%
-% Various helper commands
-% -----------------------
-
-% check if a segment is visible, i.e. not moveto or last
-\newif\ifpgf@nfold@segm@visible
-\def\checkpgfsegmentvisible#1{%
- \pgf@nfold@segm@visiblefalse%
- \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
@@ -87,13 +84,12 @@
\def\pgf@nfold@miterjoin{
% 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@nfold@inputsegment@first}%
- {\pgfpointpolar%
- {\pgf@nfold@previous@endangle+.5*\pgf@nfold@deltaphi@start+90}% do not change
- {\pgf@nfold@shiftamount/cos(.5*\pgf@nfold@deltaphi@start)}%
+ \pgfpathlineto{
+ \pgfpointadd{\pgf@nfold@join@centre}{%
+ \pgfpointpolar{\pgf@nfold@firstang+.5*\pgf@nfold@deltaphi+90}% do not change
+ {\pgf@nfold@shiftamount/cos(.5*\pgf@nfold@deltaphi)}%
}%
- \pgfpathlineto{}%
+ }%
}
\def\pgf@nfold@beveljoin{
@@ -110,17 +106,17 @@
% 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.
\pgfmathsetlengthmacro{\bevelouterprotrusion}%
- {\pgf@nfold@shortenstartjoin - .5*\pgflinewidth*abs(tan(.25*\pgf@nfold@deltaphi@start))}
+ {\pgf@nfold@shortenstartjoin pt - .5*\pgflinewidth*abs(tan(.25*\pgf@nfold@deltaphi))}
% 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@start))}
+ \pgfmathsetmacro{\bevelshorten}{2*\insidepercentage*abs(tan(.25*\pgf@nfold@deltaphi))}
% This threshold decides if the inside line has a bevel or a miter join
- \pgfmathparse{\bevelshorten < abs(sin(.5*\pgf@nfold@deltaphi@start))}
+ \pgfmathparse{\bevelshorten < abs(sin(.5*\pgf@nfold@deltaphi))}
\ifnum\pgfmathresult=1\relax
\pgfmathsetlengthmacro{\bevelextension}{\bevelouterprotrusion-\bevelshorten*\pgf@nfold@hwidth}
- \pgfpointadd{\pgf@nfold@join@start}{\pgfpointpolar{\pgf@nfold@previous@endangle}{\bevelextension}}
+ \pgfpointadd{\pgf@nfold@join@start}{\pgfpointpolar{\pgf@nfold@firstang}{\bevelextension}}
\pgfpathlineto{}
- \pgfpointadd{\pgf@nfold@join@end}{\pgfpointpolar{\pgf@nfold@cur@startangle}{-\bevelextension}}
+ \pgfpointadd{\pgf@nfold@join@end}{\pgfpointpolar{\pgf@nfold@secondang}{-\bevelextension}}
\pgfpathlineto{}
\else
\pgf@nfold@miterjoin
@@ -130,94 +126,140 @@
\def\pgf@nfold@roundjoin{
% The outer half of the lines get arcs, the others get miters
\ifdim\insidepercentage pt<.5pt\relax
- \pgfpointadd{\pgf@nfold@join@start}{\pgfpointpolar{\pgf@nfold@previous@endangle}{\pgf@nfold@shortenstartjoin}}
+ \pgfpointadd{\pgf@nfold@join@start}{\pgfpointpolar{\pgf@nfold@firstang}{\pgf@nfold@shortenstartjoin}}
\pgfpathlineto{}
- % Check if the angles are in the correct range; under some conditions we must add or subtract 360
- \pgfmathparse{\turnindicator*(\pgf@nfold@cur@startangle-\pgf@nfold@previous@endangle)}
- \ifdim\pgfmathresult pt>0pt\relax
- \pgfmathsetmacro{\targetang}{\pgf@nfold@cur@startangle-\turnindicator*360}
- \else
- \let\targetang\pgf@nfold@cur@startangle
- \fi
+ % TODO needs unit tests for all cases left, right, across the 360 gap etc.
+ \pgfmathsetmacro\pgf@tmp@firstang{\pgf@nfold@firstang+90*\turnindicator}
\pgfpatharc%
- {\pgf@nfold@previous@endangle+90*\turnindicator}%
- {\targetang+90*\turnindicator}%
+ {\pgf@tmp@firstang}%
+ {\pgf@tmp@firstang+\pgf@nfold@deltaphi}%
{abs(\pgf@nfold@shift@fraction)*\pgf@nfold@hwidth}%
\else
\pgf@nfold@miterjoin
\fi
}
-\def\pgf@nfold@make@join{
+% The handler that will be added to the parsed path
+% #1: The centre of the join
+% #2: The end point of the first segment
+% #3: The starting point of the second segment
+% #4: The angle of the first segment
+% #5: The angle of the second segment
+% #6: deltaphi in the range [-180, 180]
+% #7: the distance between #1 and #2
+% #8: the join + finish macro
+\def\pgf@nfold@token@join#1#2#3#4#5#6#7#8{%
+ \def\pgf@nfold@join@centre{#1}%
+ \def\pgf@nfold@join@prevend{#2}%
+ \def\pgf@nfold@join@nextstart{#3}%
+ \def\pgf@nfold@firstang{#4}%
+ \def\pgf@nfold@secondang{#5}%
% 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}}}%
+ \pgfpointadd{#2}{\pgfpointpolar{\pgf@nfold@firstang+90}{\pgf@nfold@shiftamount}}}%
\pgfextract@process\pgf@nfold@join@end{%
- \pgfpointadd{\pgf@nfold@segment@start}
- {\pgfpointpolar{\pgf@nfold@cur@startangle+90}{\pgf@nfold@shiftamount}}}%
+ \pgfpointadd{#3}{\pgfpointpolar{\pgf@nfold@secondang+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
+ \pgfpointtaxicabnorm\pgfutil@tempdima
+ \ifdim\pgfutil@tempdima>0.1pt\relax
+ \def\pgf@nfold@deltaphi{#6}%
+ \def\pgf@nfold@shortenstartjoin{#7}%
% First step: Check if left or right turn (-1=left, 1=right)
- \ifdim\pgf@nfold@deltaphi@start pt<0pt
+ \ifdim\pgf@nfold@deltaphi 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
+ \pgfutil@tempdima=\pgf@nfold@shift@fraction pt\relax
+ \pgfutil@tempdima=\turnindicator\pgfutil@tempdima
+ \advance\pgfutil@tempdima by-1pt\relax
+ \pgfutil@tempdima=-.5\pgfutil@tempdima
% \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
+ \edef\insidepercentage{\pgf@sys@tonumber\pgfutil@tempdima}%
+ #8%
+ \fi
+}
+
+
+%
+% Parser: Join handler
+%
+\def\pgf@nfold@parser@handlejoin{%
+ % \pgf@xa := abs(deltaphi@start)
+ \pgf@xa=\pgf@nfold@deltaphi@start pt\relax
+ \ifdim\pgf@xa<0pt\relax
+ \pgf@xa=-\pgf@xa
+ \fi
+ % Skip the entire join if abs(deltaphi) is too small
+ \ifdim\pgf@xa>1pt\relax
+ \edef\pgf@nfold@jointype{\pgf@cached@linejoin}
+ \if\pgf@cached@linejoin m% \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.
- \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
+ % Switch to bevel if miterlimit*cos(.5*abs(deltaphi)) <= 1
\ifdim\pgf@xa>1pt\relax
- \pgf@nfold@miterjoin
+ \def\pgf@nfold@tmp@joinmacro{\pgf@nfold@miterjoin}
\else
- \pgf@nfold@beveljoin
+ \def\pgf@nfold@tmp@joinmacro{\pgf@nfold@beveljoin}
\fi
\else
\if b\pgf@cached@linejoin\relax
- \pgf@nfold@beveljoin
+ \def\pgf@nfold@tmp@joinmacro{\pgf@nfold@beveljoin}
\else
- \if r\pgf@cached@linejoin
- % round join
- \pgf@nfold@roundjoin
- \fi
+ \def\pgf@nfold@tmp@joinmacro{\pgf@nfold@roundjoin}
\fi
\fi
- % 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
+ % The last parameter is a macro to be called when this segment of the join is non-trivial.
+ % It consists of
+ % - the macro to actually draw the join,
+ % - either finish@normal or finish@edgecase.
+ \edef\pgf@nfold@macrotoadd{%
+ \noexpand\pgf@nfold@token@join{\pgf@nfold@cur@first}{\pgf@nfold@prev@segment@end}
+ {\pgf@nfold@cur@movedfirst}{\pgf@nfold@prev@angle@ii}{\pgf@nfold@cur@angle@i}%
+ {\pgf@nfold@deltaphi@start}{\pgf@nfold@shortenstartjoin}{%
+ \expandafter\noexpand\pgf@nfold@tmp@joinmacro%
+ \expandafter\noexpand\ifpgf@nfold@closejoinsedgecase%
+ \pgf@nfold@token@finish@edgecase{\pgf@nfold@cur@movedlast}%
+ \else%
+ \pgf@nfold@token@finish@normal%
+ \fi%
+ }%
+ }%
+ \pgf@nfold@addmacro\pgf@parsed@cur@conn@seg%
+ \fi% end if abs(deltaphi) > 1
+}
+
+
+% All non-trivial joins connect to the end of the join, which is the starting point of the next 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@cur@movedlast
+% and \pgf@nfold@cur@movedfirst exchange places *and* we are on the outside of the first join
+% (implying that we are on the inside of the second join), the first join must not connect all the way to
+% to \pgf@nfold@cur@movedfirst, because that would overshoot the second join. Instead we connect to
+% \pgf@nfold@cur@movedlast (which is *closer* to the first join than \pgf@nfold@cur@movedfirst in this edge case).
+% To accomodate for the vertical offset we connect to \pgf@nfold@join@end which is the vertical
+% offset of \pgf@nfold@cur@movedlast by definition.
+\def\pgf@nfold@token@finish@normal{%
+ \pgfpathlineto{\pgf@nfold@join@end}%
+}
+
+% parameter #1: @movedlast of the next segment
+\def\pgf@nfold@token@finish@edgecase#1{%
+ \pgf@nfold@join@end%
+ \ifdim\insidepercentage pt<.5pt\relax%
+ \pgf@process{\pgfpointadd{#1}{\pgfpointpolar{\pgf@nfold@secondang+90}{\pgf@nfold@shiftamount}}}
+ \fi%
+ \pgfpathlineto{}%
}
@@ -236,74 +278,101 @@
\newif\ifpgf@nfold@intersectionsnotloaded
-\def\pgf@nfold@handlesegment{%
- %%% Step 0: Preparation
- % Set some default values; they might get overwritten later
- \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\pgf@nfold@previousinputsegment\pgf@nfold@inputsegmentmoveto
- \pgf@nfold@continuesegmentfalse
- \else
- \pgf@nfold@continuesegmenttrue
- \fi
- %%% 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@shortenstartjoin{0pt}
- \def\pgf@nfold@shortenendjoin{0pt}
- \pgf@nfold@closejoinsedgecasefalse
- \pgf@nfold@angletoosharpfalse
- % Make a join only if two adjacent segments are both visible
- \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
+\def\pgf@nfold@parser@handlesegment{%
+ \if\pgf@nfold@cur@visible 0
+ % first, last and moveto are invisible
+ \if\pgf@nfold@cur@type m
+ % We don't need to do anything for a moveto: If a visible segment follows, it will move to
+ % its starting location by itself. However, we might need to draw the arrow tip extension
+ % at the start (if present).
+ \if\pgf@nfold@start@arrowcode1
+ \if\pgf@nfold@prev@type f
+ \if\pgf@nfold@next@visible1
+ \edef\pgf@nfold@macrotoadd{%
+ \noexpand\pgf@nfold@extendtotip{s}{\pgf@nfold@cur@last}{\pgf@nfold@next@angle@i}
+ }%
+ \pgf@nfold@addmacro\pgf@parsed@cur@conn@seg%
+ % hack: We make the next segment believe that this segment was a lineto
+ % so the path does not get interrupted
+ \let\pgf@nfold@cur@type l
+ \let\pgf@nfold@cur@visible 1
+ \let\pgf@nfold@cur@angle@ii\pgf@nfold@next@angle@i
+ \let\pgf@nfold@cur@tang@ii\pgf@nfold@next@tang@i
+ \fi
+ \fi%
+ \fi
+ \fi%
+ \else%
+ \let\pgf@nfold@cur@movedfirst\pgf@nfold@cur@first
+ \let\pgf@nfold@cur@movedlast\pgf@nfold@cur@last
+ %%% Step 1: Make room for joins if necessary
+ % In order to make room 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@shortenstartjoin{0}
+ \def\pgf@nfold@shortenendjoin{0}
+ \pgf@nfold@closejoinsedgecasefalse
+ \pgf@nfold@angletoosharpfalse
+ % Step 1.1: Make room for the join at the start if needed
+ \if\pgf@nfold@prev@visible1
+ \pgf@nfold@continuesegmenttrue
+ % TODO can we just use deltaphi@end from the previous round?
+ % Compute the angle difference at the start (between -180 and +180 degrees)
+ % using \pgfmathsubtract@ is more readable and no less efficient than computing this manually
+ \pgfmathsubtract@{\pgf@nfold@cur@angle@i}{\pgf@nfold@prev@angle@ii}
+ \pgf@nfold@clampangle
+ \edef\pgf@nfold@deltaphi@start{\pgfmathresult}
\pgf@xb=\pgf@nfold@deltaphi@start pt\relax
\ifdim\pgf@xb<0pt\relax
\pgf@xb=-\pgf@xb
\fi
\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}
+ % don't need an error message here, it has already been displayed in the previous segment
\pgf@nfold@angletoosharptrue
\else
\ifdim\pgf@xb>0.5pt\relax
- % make space for the start join if the angle is nonzero
+ % make room for the start join if the angle is nonzero;
+ % shortenstartjoin := hwidth*tan(.5*abs(deltaphi@start))
\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}}
+ \edef\pgf@nfold@shortenstartjoin{\pgf@sys@tonumber\pgf@yb}
+ \pgfextract@process\pgf@nfold@cur@movedfirst{%
+ \pgfpointadd{\pgf@nfold@cur@first}%
+ {\pgfqpointscale{\pgf@nfold@shortenstartjoin}{\pgf@nfold@cur@tang@i}}}%
+ % If the current segment is a curve, we need to relocate @supporta point as well,
+ % as otherwise the @first point could overtake it
+ \if\pgf@nfold@cur@type c
+ \pgf@process{\pgfpointdiff{\pgf@nfold@cur@first}{\pgf@nfold@cur@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}}}%
+ \pgfextract@process\pgf@nfold@cur@supporta{\pgfpointadd%
+ {\pgf@nfold@cur@first}%
+ % TODO migrate to tangent
+ {\pgfqpointpolar{\pgf@nfold@cur@angle@i}{\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
+ % In this special case, a slight corner at the end of the join is unavoidable unless we
+ % also relocate @supportb, which may have unintended side effects
+ \let\pgf@nfold@cur@supporta\pgf@nfold@cur@movedfirst
\fi
\fi
\fi
\fi
+ \else
+ \pgf@nfold@continuesegmentfalse
\fi
- % Step 1.2: Make space for the join at the end if needed
- \checkpgfsegmentvisible\pgf@nfold@next@segmenttype
- \ifpgf@nfold@segm@visible%
+ % Step 1.2: Make room for the join at the end if needed
+ \if\pgf@nfold@next@visible1
+ % Compute the angle difference at the start (between -180 and +180 degrees)
+ % using \pgfmathsubtract@ is more readable and no less efficient than computing this manually
+ \pgfmathsubtract@{\pgf@nfold@next@angle@i}{\pgf@nfold@cur@angle@ii}
+ \pgf@nfold@clampangle
+ \edef\pgf@nfold@deltaphi@end{\pgfmathresult}
\pgf@xb=\pgf@nfold@deltaphi@end pt\relax
\ifdim\pgf@xb<0pt\relax
\pgf@xb=-\pgf@xb
@@ -313,29 +382,34 @@
\pgf@nfold@angletoosharptrue
\else
\ifdim\pgf@xb>0.5pt\relax
- % make space for the start join if the angle is nonzero
+ % make room for the start join if the angle is nonzero
+ % shortenendjoin := hwidth*tan(.5*abs(deltaphi@end))
\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}
+ \edef\pgf@nfold@shortenendjoin{\pgf@sys@tonumber\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
+ \pgfextract@process\pgf@nfold@cur@movedlast{%
+ % the use of the minus sign is fine here because \pgf@nfold@shortenendjoin >= 0.0
+ \pgfpointadd{\pgf@nfold@cur@last}%
+ {\pgfqpointscale{-\pgf@nfold@shortenendjoin}{\pgf@nfold@cur@tang@ii}}}%
+ \pgfextract@process\pgf@nfold@cur@movedlast{%
+ \pgfpointadd{\pgf@nfold@cur@last}%
+ {\pgfqpointpolar{\pgf@nfold@cur@angle@ii}{\pgf@yb}}}%
+ \if\pgf@nfold@cur@type c
% Same procedure as above: relocate supportb if we have a curve
- \pgf@process{\pgfpointdiff{\pgf@nfold@inputsegment@supportb}{\pgf@nfold@inputsegment@last}}
+ \pgf@process{\pgfpointdiff{\pgf@nfold@cur@supportb}{\pgf@nfold@cur@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}}}%
+ \pgfextract@process\pgf@nfold@cur@supportb{\pgfpointadd%
+ {\pgf@nfold@cur@last}%
+ % TODO migrate to tangent
+ {\pgfqpointpolar{\pgf@nfold@cur@angle@ii}{-\pgfmathresult pt}}}%
\else
- \let\pgf@nfold@inputsegment@supportb\pgf@nfold@segment@end
+ \let\pgf@nfold@cur@supportb\pgf@nfold@cur@movedlast
\fi
\fi
\fi
@@ -347,13 +421,13 @@
% 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
+ \if\pgf@nfold@cur@type l
% 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}
+ \pgf@process{\pgfpointdiff{\pgf@nfold@cur@first}{\pgf@nfold@cur@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
+ \pgf@xa=\pgf@nfold@shortenstartjoin pt\relax
+ \advance\pgf@xa by\pgf@nfold@shortenendjoin pt\relax
\ifdim\pgf@xa>\pgfmathresult pt\relax
\pgf@nfold@closejoinsedgecasetrue
\fi
@@ -362,75 +436,96 @@
%
% Step 2.1: Draw the join at the start if applicable
%
- \ifx\pgf@nfold@previousinputsegment\pgf@nfold@inputsegmentmoveto
+ \if\pgf@nfold@prev@visible0%
\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
+ % Counterintuitively, this is given by the offset of \pgf@nfold@cur@movedfirst 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
+ \edef\pgf@nfold@macrotoadd{%
+ \noexpand\pgf@nfold@token@edgecase@movetostart{\pgf@nfold@cur@movedfirst}{\pgf@nfold@cur@angle@i}%
+ }%
+ \pgf@nfold@addmacro\pgf@parsed@cur@conn@seg%
+ \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
+ \pgf@nfold@parser@handlejoin
\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 3: Draw the new segment.
- %
- % The value of \ifpgf@nfold@continuesegment decides whether we start with a moveto.
- \ifx\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentlineto
- \ifpgf@nfold@closejoinsedgecase\else
+ % 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@prev@segment@end\pgf@nfold@cur@movedlast
+ %
+ % Step 3: Draw the new segment.
+ %
+ % The value of \ifpgf@nfold@continuesegment decides whether we start with a moveto.
+ % curveto
+ \if\pgf@nfold@cur@type l
+ % In the edge case, one join is followed immediately by the next. The line segment
+ % thus has a negative length and will be skipped.
+ \ifpgf@nfold@closejoinsedgecase\else
+ \edef\pgf@nfold@macrotoadd{%
+ \expandafter\noexpand\ifpgf@nfold@continuesegment%
+ \pgf@nfold@token@lineto@continue%
+ \else%
+ \pgf@nfold@token@lineto%
+ \fi{\pgf@nfold@cur@movedfirst}{\pgf@nfold@cur@movedlast}{\pgf@nfold@cur@tang@i}%
+ }%
+ \pgf@nfold@addmacro\pgf@parsed@cur@conn@seg%
+ \fi
+ \fi
+ \if\pgf@nfold@cur@type o
+ \def\pgf@nfold@macrotoadd{\pgf@nfold@token@closepath}%
+ \pgf@nfold@addmacro\pgf@parsed@cur@conn@seg%
+ \fi
+ \if\pgf@nfold@cur@type c
\ifpgf@nfold@continuesegment
- \pgfoffsetlinenomove{\pgf@nfold@segment@start}{\pgf@nfold@segment@end}{\pgf@nfold@shiftamount}
+ \pgf@subdividecurve{\pgf@nfold@cur@movedfirst}{\pgf@nfold@cur@supporta}{\pgf@nfold@cur@supportb}{\pgf@nfold@cur@movedlast}{\pgf@offset@max@recursion}{0}{\pgf@nfold@addcurvesegment@callback@continue}
\else
- \pgfoffsetline{\pgf@nfold@segment@start}{\pgf@nfold@segment@end}{\pgf@nfold@shiftamount}
+ \pgf@subdividecurve{\pgf@nfold@cur@movedfirst}{\pgf@nfold@cur@supporta}{\pgf@nfold@cur@supportb}{\pgf@nfold@cur@movedlast}{\pgf@offset@max@recursion}{0}{\pgf@nfold@addcurvesegment@callback}%
\fi
\fi
- \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\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentcurveto
- \ifpgf@nfold@continuesegment
- \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@nfold@inputsegment@supporta}{\pgf@nfold@inputsegment@supportb}{\pgf@nfold@segment@end}{\pgf@nfold@shiftamount}
+ \if\pgf@nfold@cur@type i
+ \edef\pgf@nfold@macrotoadd{%
+ \noexpand\pgf@nfold@token@invisibleline{\pgf@nfold@cur@movedfirst}{\pgf@nfold@cur@movedlast}{\pgf@nfold@cur@tang@i}%
+ }%
+ \pgf@nfold@addmacro\pgf@parsed@cur@conn@seg%
\fi
- \fi
- \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}%
+ % Step 4: Extend into the arrow tip at the end (if present)
+ \if\pgf@nfold@next@type t%
+ \ifnum\pgf@nfold@end@arrowcode=1
+ \edef\pgf@nfold@macrotoadd{%
+ \noexpand\pgf@nfold@extendtotip{e}{\pgf@nfold@cur@last}{\pgf@nfold@cur@angle@ii}}%
+ \pgf@nfold@addmacro\pgf@parsed@cur@conn@seg%
\fi%
- \fi
- \fi
- % 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% end if current visible
+}
+
+
+\def\pgf@nfold@addcurvesegment@callback#1#2#3#4#5{%
+ \if#50%
+ \edef\pgf@nfold@macrotoadd{%
+ % The subdivision algorithm has already computed the tangents
+ \noexpand\pgf@nfold@token@curveto{#1}{#2}{#3}{#4}{\pgf@tmp@tang@i}{\pgf@tmp@tang@ii}%
+ }%
+ \pgf@nfold@addmacro\pgf@parsed@cur@conn@seg%
+ \else%
+ \pgf@nfold@addcurvesegment@callback@continue{#1}{#2}{#3}{#4}{#5}%
\fi%
}
+\def\pgf@nfold@addcurvesegment@callback@continue#1#2#3#4#5{%
+ % The subdivision algorithm has already computed the tangents
+ \edef\pgf@nfold@macrotoadd{%
+ \noexpand\pgf@nfold@token@curveto@continue{#1}{#2}{#3}{#4}{\pgf@tmp@tang@i}{\pgf@tmp@tang@ii}%
+ }%
+ \pgf@nfold@addmacro\pgf@parsed@cur@conn@seg%
+}
+
% Rendering arrow tips
% --------------------
@@ -452,42 +547,54 @@
\def\pgf@nfold@intersec@numcached{5}
% This macro extends the arrow body to the tips
-% parameter: s=start, e=end
-\def\pgf@nfold@extendtotip#1{
+% #1: s=start, e=end
+% #2: start/end point of the path
+% #3: angle
+\newif\ifpgf@nfold@ontheedge
+\def\pgf@nfold@extendtotip#1#2#3{
\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}%
+ \pgf@nfold@ontheedgetrue
+ % 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%
+ \pgf@nfold@ontheedgefalse
+ \fi\fi
+ \ifpgf@nfold@ontheedge%
+ % the extension at the start needs a moveto to the correct starting point
+ \if#1s
+ \pgfpathmoveto{\pgfpointadd{#2}{\pgfpointpolar{#3+90}{\pgf@nfold@shiftamount}}}%
+ \fi
+ \else
+ % 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
- % 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
+ % 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
@@ -496,13 +603,10 @@
% the current path to the intersection point.
\begingroup
\pgftransformreset
+ \pgftransformshift{#2}
+ \pgftransformrotate{#3}
\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
@@ -520,17 +624,13 @@
\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
\pgfutil@packageerror{tikz-nfold}{Invalid argument to \string\pgf@nfold@extendtotip: \meaning#1}{}
\fi\fi
\pgftransformreset
- \fi\fi% if 1 < i < nArrows
+ \fi% if 1 < i < nArrows
\fi% if intersections is needed and not loaded
}
@@ -630,7 +730,7 @@
% 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@getcurrentpath\pgf@nfold@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}{}
@@ -675,220 +775,275 @@
%
%
+
+%
+% In order to correctly implement \pgfpathclose we already need to know about
+% the \pgfpathclose (and the penultimate point) at the beginning of this
+% connected segment as these data affect the first/last join.
+% We therefore parse one connected segment of the softpath and store it in a modified
+% form in \pgf@cur@conn@segment. When reaching the end of the connected segment we call
+% the proper parser to turn \pgf@cur@conn@segment into a parsed path. We do this for
+% all connected segments and concatenate all the parsed paths.
+%
+
+
\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%
+ \let\pgf@cur@conn@segment\pgfutil@empty%
+ \let\pgf@all@parsed@segments\pgfutil@empty%
+ \edef\pgf@nfold@parser@last@moveto{{\the\pgf@path@lastx}{\the\pgf@path@lasty}}%
+ \let\pgf@nfold@last@closepath@from\pgfutil@empty%
+ \pgf@nfold@parse@setupfirst%
+ \expandafter\pgf@nfold@@parsesoftpath#1\pgf@stop{}{}%
+ \let#2\pgf@all@parsed@segments%
}%
-\def\pgf@nfold@@parsesoftpath#1{%
+\def\pgf@nfold@@parsesoftpath#1#2#3{%
+ \let\pgf@next\pgf@nfold@@parsesoftpath%
\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%
+ \def\pgf@nfold@macrotoadd{\pgf@nfold@parselast}%
+ \pgf@nfold@addmacro\pgf@cur@conn@segment%
+ \pgf@nfold@process@conn@segment%
\let\pgf@next\relax%
\else%
\ifx#1\pgfsyssoftpath@movetotoken%
- \let\pgf@next\pgf@nfold@parsemoveto%
+ \def\pgf@nfold@macrotoadd{\pgf@nfold@parsemoveto{#2}{#3}}%
+ \pgf@nfold@addmacro\pgf@cur@conn@segment%
+ % A moveto marks the beginning/end of one connected segment
+ \pgf@nfold@process@conn@segment%
+ \let\pgf@nfold@last@closepath@from\pgfutil@empty%
+ \let\pgf@cur@conn@segment\pgfutil@empty%
+ \def\pgf@nfold@parser@last@moveto{{#2}{#3}}%
\else%
\ifx#1\pgfsyssoftpath@linetotoken%
- \let\pgf@next\pgf@nfold@parselineto%
+ \def\pgf@nfold@macrotoadd{\pgf@nfold@parselineto{#2}{#3}}%
+ \pgf@nfold@addmacro\pgf@cur@conn@segment%
\else%
\ifx#1\pgfsyssoftpath@curvetosupportatoken%
- \let\pgf@next\pgf@nfold@parsecurveto%
+ \def\pgf@nfold@parse@supporta{{#2}{#3}}%
\else%
- \ifx#1\pgfsyssoftpath@closepathtoken%
- \let\pgf@next\pgf@nfold@parseclosepath%
+ \ifx#1\pgfsyssoftpath@curvetosupportbtoken%
+ \def\pgf@nfold@parse@supportb{{#2}{#3}}%
\else%
- \ifx#1\pgfsyssoftpath@rectcornertoken%
- \let\pgf@next\pgf@nfold@parserect%
+ \ifx#1\pgfsyssoftpath@curvetotoken%
+ \edef\pgf@nfold@macrotoadd{\noexpand\pgf@nfold@parsecurveto\pgf@nfold@parse@supporta\pgf@nfold@parse@supportb{#2}{#3}}%
+ \pgf@nfold@addmacro\pgf@cur@conn@segment%
\else%
- \pgfutil@packageerror{tikz-nfold}{Unrecognised soft path token `#1'}{}%
+ \ifx#1\pgfsyssoftpath@closepathtoken%
+ \let\pgf@nfold@last@closepath@from\pgf@nfold@parser@previous@pt%
+ \def\pgf@nfold@macrotoadd{\pgf@nfold@parseclosepath{#2}{#3}}%
+ \pgf@nfold@addmacro\pgf@cur@conn@segment%
+ \else%
+ \ifx#1\pgfsyssoftpath@rectcornertoken%
+ \def\pgf@nfold@parse@rectcorner{{#2}{#3}}%
+ \else%
+ \ifx#1\pgfsyssoftpath@rectsizetoken%
+ \edef\pgf@nfold@macrotoadd{\noexpand\pgf@nfold@parserect\pgf@nfold@parse@rectcorner{#2}{#3}}%
+ \pgf@nfold@addmacro\pgf@cur@conn@segment%
+ \else%
+ \pgfutil@packageerror{tikz-nfold}{Unrecognised soft path token `#1'}{}%
+ \fi%
+ \fi%
+ \fi%
\fi%
\fi%
\fi%
\fi%
\fi%
\fi%
+ \def\pgf@nfold@parser@previous@pt{{#2}{#3}}%
\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%
-}%
+\def\pgf@nfold@process@conn@segment{%
+ \let\pgf@parsed@cur@conn@seg\pgfutil@empty%
+ \ifx\pgf@nfold@last@closepath@from\pgfutil@empty%
+ \expandafter\pgf@nfold@parsemoveto\pgf@nfold@parser@last@moveto%
+ \else%
+ % This connected segment ends on a closepath. In order to get
+ % the join right, we prepend the current segment with an invisible line
+ % identical to the line of the \pgfpathclose. This way,
+ \expandafter\pgf@nfold@parsemoveto\pgf@nfold@last@closepath@from%
+ \expandafter\pgf@nfold@parseinvisibleline\pgf@nfold@parser@last@moveto%
+ \fi%
+ \pgf@cur@conn@segment%
+ \let\pgf@nfold@macrotoadd\pgf@parsed@cur@conn@seg%
+ \pgf@nfold@addmacro\pgf@all@parsed@segments%
+}
-% 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>}
+% Values for \pgf@nfold@next@type:
+% f=first, m=moveto, c=curveto, l=lineto, r=rect, o=closepath, t=last, i=invisibleline
%
+
+\def\pgf@nfold@parsemoveto#1#2{%
+ \pgf@nfold@parser@moveup%
+ \let\pgf@nfold@next@type=m%
+ \let\pgf@nfold@next@visible=0%
+ \edef\pgf@nfold@next@last{\pgf@x#1\pgf@y#2}%
+ \pgf@nfold@parser@handlesegment%
+}%
+
+
+% Common code for parselineto and parseclosepath
+\def\pgf@nfold@parse@line@common#1#2#3{%
+ \pgf@nfold@parser@moveup%
+ \pgfextract@process\pgf@tmp@tang@i{\pgfpointnormalised{}\global\let\pgf@nfold@tmp\pgf@tmp}
+ \let\pgf@nfold@next@type=#3%
+ \let\pgf@nfold@next@visible=1%
+ \let\pgf@nfold@next@tang@i\pgf@tmp@tang@i
+ \let\pgf@nfold@next@tang@ii\pgf@tmp@tang@i
+ \let\pgf@nfold@next@angle@i\pgf@nfold@tmp
+ \let\pgf@nfold@next@angle@ii\pgf@nfold@tmp
+ \let\pgf@nfold@next@first\pgf@nfold@cur@last
+ \def\pgf@nfold@next@last{\pgf@x#1\pgf@y#2}%
+ \pgf@nfold@parser@handlesegment%
+}
+
\def\pgf@nfold@parselineto#1#2{%
+ % the current end is still stored in next@last because parser@moveup has not been called yet
+ \pgf@process{\pgfpointdiff{\pgf@nfold@next@last}{\pgf@x#1\pgf@y#2}}%
+ \pgfpointtaxicabnorm\pgf@xa%
% 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%
+ \ifdim\pgf@xa>.1pt\relax%
+ \pgf@nfold@parse@line@common{#1}{#2}{l}%
+ \fi%
}%
-\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}%
- }%
- \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%
+\def\pgf@nfold@parsecurveto#1#2#3#4#5#6{%
+ \pgf@nfold@parser@moveup%
+ \pgf@offset@compute@tangents{\pgf@nfold@cur@last}{\pgf@x#1\pgf@y#2}{\pgf@x#3\pgf@y#4}{\pgf@x#5\pgf@y#6}%
+ \let\pgf@nfold@next@type=c%
+ \let\pgf@nfold@next@visible=1%
+ % the following only need to be set if visible=1
+ \let\pgf@nfold@next@tang@i=\pgf@tmp@tang@i
+ \let\pgf@nfold@next@tang@ii=\pgf@tmp@tang@ii
+ \let\pgf@nfold@next@angle@i\pgf@tmp@angle@i
+ \let\pgf@nfold@next@angle@ii\pgf@tmp@angle@ii
+ \let\pgf@nfold@next@first\pgf@nfold@cur@last
+ \def\pgf@nfold@next@supporta{\pgf@x#1\pgf@y#2}
+ \def\pgf@nfold@next@supportb{\pgf@x#3\pgf@y#4}
+ \def\pgf@nfold@next@last{\pgf@x#5\pgf@y#6}
+ \pgf@nfold@parser@handlesegment%
}%
\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%
+ \pgf@process{\pgfpointdiff{\pgf@nfold@next@last}{\pgf@x#1\pgf@y#2}}%
+ % closepath segments should always be processed even if they have zero length
+ \pgf@nfold@parse@line@common{#1}{#2}{o}%
+}%
+
+\def\pgf@nfold@parseinvisibleline#1#2{%
+ \pgf@process{\pgfpointdiff{\pgf@nfold@next@last}{\pgf@x#1\pgf@y#2}}%
+ % invisibleline segments should always be processed even if they have zero length.
+ % Counterintuitively, an "invisibleline" segment has next@visible=1 because
+ % we want its end join to be rendered.
+ \pgf@nfold@parse@line@common{#1}{#2}{i}%
}%
+\def\pgf@nfold@parselast{%
+ \pgf@nfold@parser@moveup%
+ \let\pgf@nfold@next@type=t%
+ \let\pgf@nfold@next@visible=0%
+ \pgf@nfold@parser@handlesegment%
+}
+
% 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%
+\def\pgf@nfold@parserect#1#2#3#4{%
\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@xc=#1\relax
+ \pgf@yc=#2\relax
+ \pgf@xd=#3\relax
+ \pgf@yd=#4\relax
+ \advance\pgf@yc\pgf@yd%
+ \edef\pgf@temp{{\the\pgf@xc}{\the\pgf@yc}}%
+ \expandafter\pgf@nfold@parselineto\pgf@temp%
+ \advance\pgf@xc\pgf@xd%
+ \edef\pgf@temp{{\the\pgf@xc}{\the\pgf@yc}}%
+ \expandafter\pgf@nfold@parselineto\pgf@temp%
+ \advance\pgf@yc-\pgf@yd%
+ \edef\pgf@temp{{\the\pgf@xc}{\the\pgf@yc}}%
+ \expandafter\pgf@nfold@parselineto\pgf@temp%
+ \advance\pgf@xc-\pgf@xd%
+ \edef\pgf@temp{{\the\pgf@xc}{\the\pgf@yc}}%
+ \expandafter\pgf@nfold@parselineto\pgf@temp%
+ \edef\pgf@marshal{\noexpand\pgf@nfold@parsemoveto{\the\pgf@xc}{\the\pgf@yc}}%
\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}%
-
+% adds the contents of \pgf@nfold@macrotoadd to #1
+\def\pgf@nfold@addmacro#1{%
+ % need a \gdef because we have nested groups in the subdivision
+ \expandafter\expandafter\expandafter\gdef%
+ \expandafter\expandafter\expandafter#1%
+ \expandafter\expandafter\expandafter{%
+ \expandafter#1\pgf@nfold@macrotoadd}%
+}
-\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%
-}%
+%
+% Tokens for the parsed path
+%
-\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%
-}%
+%#3: cached normalised tangent (for efficiency)
+\def\pgf@nfold@token@lineto#1#2#3{%
+ \pgfoffsetline@{#1}{#2}{\pgf@nfold@shiftamount}{#3}%
+}
-\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@token@lineto@continue#1#2#3{%
+ \pgfoffsetlinenomove@{#1}{#2}{\pgf@nfold@shiftamount}{#3}%
+}
-\def\pgf@nfold@inputsegmentobject@endofinputsegments{%
- \let\pgf@nfold@currentinputsegment\pgf@nfold@inputsegmentlast%
-}%
+\def\pgf@nfold@token@invisibleline#1#2#3{%
+ % An invisible line should execute a moveto to the end point of the line
+ \pgfqpointscale{\pgf@nfold@shiftamount}{#3}%
+ \pgf@xc=-\pgf@y%
+ \pgf@y=\pgf@x%
+ \pgf@x=\pgf@xc%
+ \pgfpathmoveto{\pgfpointadd{}{#2}}%
+}
+% #5, #6: cached normalised tangents at start and end (for efficiency)
+\def\pgf@nfold@token@curveto#1#2#3#4#5#6{%
+ \def\pgf@tmp@tang@i{#5}%
+ \def\pgf@tmp@tang@ii{#6}%
+ \pgf@offset@bezier@segment@{#1}{#2}{#3}{#4}{\pgf@nfold@shiftamount}%
+ \pgfpathmoveto{\pgf@bezier@offset@i}%
+ \pgfpathcurveto{\pgf@bezier@offset@ii}{\pgf@bezier@offset@iii}{\pgf@bezier@offset@iv}%
+}
-\def\pgf@nfold@traversepath{%
- % Transformations are already baked into the path; without this call, they would be applied twice
- \pgftransformreset
- % 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@token@curveto@continue#1#2#3#4#5#6{%
+ \def\pgf@tmp@tang@i{#5}%
+ \def\pgf@tmp@tang@ii{#6}%
+ \pgf@offset@bezier@segment@{#1}{#2}{#3}{#4}{\pgf@nfold@shiftamount}%
+ \pgfpathcurveto{\pgf@bezier@offset@ii}{\pgf@bezier@offset@iii}{\pgf@bezier@offset@iv}%
}
+\def\pgf@nfold@token@closepath{%
+ \pgfpathclose%
+}
-\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%
+% This is needed for some edge case explained above.
+% #1: segment@start, which is actually the end point of the current segment in the edge case
+% #2: the angle of the current segment (start or end does not matter for straight lines)
+\def\pgf@nfold@token@edgecase@movetostart#1#2{%
+ \pgfpathmoveto{\pgfpointadd%
+ {#1}{\pgfpointpolar{#2+90}{\pgf@nfold@shiftamount}}}%
}
-\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@parse@setupfirst{%
+ \let\pgf@nfold@next@type=f%
+ % technically, @visible can be derived from @type, but I think this is more efficient
+ \let\pgf@nfold@next@visible=0%
+ \edef\pgf@nfold@next@last{\pgf@x\the\pgf@path@lastx\pgf@y\the\pgf@path@lasty}%
+ % the following only need to be set if visible=1
+ \let\pgf@nfold@next@tang@i=\pgfpointorigin%
+ \let\pgf@nfold@next@tang@ii=\pgfpointorigin%
+ \def\pgf@nfold@next@angle@i{0}%
+ \def\pgf@nfold@next@angle@ii{0}%
+ \let\pgf@nfold@next@first\pgfpointorigin%
+ % the following only need to be set if type=c
+ \let\pgf@nfold@next@supporta\pgfpointorigin%
+ \let\pgf@nfold@next@supportb\pgfpointorigin%
}
\def\pgf@nfold@clampangle{
@@ -905,47 +1060,31 @@
\fi\fi
}
-\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@parser@moveup{%
+ \let\pgf@nfold@prev@type\pgf@nfold@cur@type%
+ \let\pgf@nfold@prev@visible\pgf@nfold@cur@visible%
+ \let\pgf@nfold@prev@tang@i\pgf@nfold@cur@tang@i%
+ \let\pgf@nfold@prev@tang@ii\pgf@nfold@cur@tang@ii%
+ \let\pgf@nfold@prev@angle@i\pgf@nfold@cur@angle@i%
+ \let\pgf@nfold@prev@angle@ii\pgf@nfold@cur@angle@ii%
+ \let\pgf@nfold@prev@first\pgf@nfold@cur@first%
+ \let\pgf@nfold@prev@supporta\pgf@nfold@cur@supporta%
+ \let\pgf@nfold@prev@supportb\pgf@nfold@cur@supportb%
+ \let\pgf@nfold@prev@last\pgf@nfold@cur@last%
+ \let\pgf@nfold@cur@type\pgf@nfold@next@type%
+ \let\pgf@nfold@cur@visible\pgf@nfold@next@visible%
+ \let\pgf@nfold@cur@tang@i\pgf@nfold@next@tang@i%
+ \let\pgf@nfold@cur@tang@ii\pgf@nfold@next@tang@ii%
+ \let\pgf@nfold@cur@angle@i\pgf@nfold@next@angle@i%
+ \let\pgf@nfold@cur@angle@ii\pgf@nfold@next@angle@ii
+ \let\pgf@nfold@cur@first\pgf@nfold@next@first%
+ \let\pgf@nfold@cur@supporta\pgf@nfold@next@supporta%
+ \let\pgf@nfold@cur@supportb\pgf@nfold@next@supportb%
+ \let\pgf@nfold@cur@last\pgf@nfold@next@last%
+}
+
%
% Iterating over the parsed soft path
% -----------------------------------
@@ -967,9 +1106,12 @@
\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%
+ {-1+2*(\pgf@nfold@index-1)/(\pgf@nfold@order-1)}%
+ % Do not store shiftamount as a length (i.e. with pt) because we want to use it in \pgfqpointscale{}{}
+ \pgfmathsetmacro{\pgf@nfold@shiftamount}{\pgf@nfold@hwidth*\pgf@nfold@shift@fraction}
+ % Transformations are already baked into the path; without this call, they would be applied twice
+ \pgftransformreset%
+ \parsedpath%
\pgfsyssoftpath@flushcurrentpath%
\pgf@up@action%
}
@@ -988,15 +1130,15 @@
\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
+ \edef\pgf@nfold@hwidth{\the\pgf@y}%
+ \pgfprocessround{\pgf@nfold@cachedpath}{\pgf@nfold@cachedpath}% remove tokens from the soft path
+ \pgf@nfold@parsearrows%
+ \pgf@nfold@parsesoftpath{\pgf@nfold@cachedpath}{\parsedpath}%
+ \pgf@nfold@run@loop%
+ \endpgfscope%
}
@@ -1008,17 +1150,17 @@
% Outputs a provided soft path in #1 offset by a distance provided in #2.
\def\pgfoffsetpath#1#2{%
\begingroup
- \pgfmathsetlengthmacro\pgf@nfold@hwidth{#2}
+ \pgfmathsetlengthmacro\pgf@nfold@parsed@hwidth{#2}
% \pgf@nfold@hwidth must always be positive
- \pgf@x=\pgf@nfold@hwidth\relax
+ \pgf@x=\pgf@nfold@parsed@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}
+ \edef\pgf@nfold@parsed@hwidth{\the\pgf@x}
+ \pgfoffsetpathqfraction{#1}{\pgf@nfold@parsed@hwidth}{\pgf@nfold@shift@fraction}
\endgroup
}
@@ -1029,9 +1171,9 @@
%
\def\pgfoffsetpathfraction#1#2#3{%
\begingroup
- \pgfmathsetlengthmacro\pgf@nfold@hwidth{#2}
+ \pgfmathsetlengthmacro\pgf@nfold@parsed@hwidth{#2}
\pgfmathsetmacro\pgf@nfold@shift@fraction{#3}
- \pgfoffsetpathqfraction{#1}{\pgf@nfold@hwidth}{\pgf@nfold@shift@fraction}
+ \pgfoffsetpathqfraction{#1}{\pgf@nfold@parsed@hwidth}{\pgf@nfold@shift@fraction}
\endgroup
}
@@ -1046,14 +1188,16 @@
% 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%
+ \edef\pgf@nfold@shiftamount{\pgf@sys@tonumber\pgf@x}
+ \pgfprocessround{#1}{\pgf@nfold@cachedpath}% remove tokens from the soft path
+ \pgf@nfold@parsesoftpath{\pgf@nfold@cachedpath}{\parsedpath}%
+ % Transformations are already baked into the path; without this call, they would be applied twice
+ \pgftransformreset%
+ \parsedpath%
\endgroup
}