texlive[66928] Master/texmf-dist: tikz-nfold (24apr23)

commits+karl at tug.org commits+karl at tug.org
Mon Apr 24 22:15:03 CEST 2023


Revision: 66928
          http://tug.org/svn/texlive?view=revision&revision=66928
Author:   karl
Date:     2023-04-24 22:15:03 +0200 (Mon, 24 Apr 2023)
Log Message:
-----------
tikz-nfold (24apr23)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/latex/tikz-nfold/README.md
    trunk/Master/texmf-dist/doc/latex/tikz-nfold/tikz-nfold-doc.pdf
    trunk/Master/texmf-dist/doc/latex/tikz-nfold/tikz-nfold-doc.tex
    trunk/Master/texmf-dist/tex/latex/tikz-nfold/pgflibrarybezieroffset.code.tex
    trunk/Master/texmf-dist/tex/latex/tikz-nfold/tikzlibrarynfold.code.tex

Modified: trunk/Master/texmf-dist/doc/latex/tikz-nfold/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/tikz-nfold/README.md	2023-04-24 20:11:42 UTC (rev 66927)
+++ trunk/Master/texmf-dist/doc/latex/tikz-nfold/README.md	2023-04-24 20:15:03 UTC (rev 66928)
@@ -1,5 +1,5 @@
 # tikz-nfold 
-## Version 0.0.1
+## Version 0.1.0
 
 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.
 

Modified: trunk/Master/texmf-dist/doc/latex/tikz-nfold/tikz-nfold-doc.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/tikz-nfold/tikz-nfold-doc.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/tikz-nfold/tikz-nfold-doc.tex	2023-04-24 20:11:42 UTC (rev 66927)
+++ trunk/Master/texmf-dist/doc/latex/tikz-nfold/tikz-nfold-doc.tex	2023-04-24 20:15:03 UTC (rev 66928)
@@ -32,6 +32,8 @@
 \usepackage{amsmath}
 \usepackage{amsthm}
 \usepackage[capitalize]{cleveref}
+\usepackage{datetime}
+\usepackage{enumitem}
 
 \theoremstyle{definition}
 \newtheorem{definition}{Definition}[section]
@@ -41,26 +43,34 @@
 \newcommand{\nfold}{\texttt{nfold}}
 \newcommand{\tikznfold}{\texttt{/tikz/nfold}}
 
-\pgfkeys{/tkzexample/every tkzexample/.style={code=violet!15, graphic=orange!20, very small}}
+\pgfkeys{/tkzexample/every tkzexample/.style={
+		code=violet!15, graphic=orange!20, very small,above skip={\vskip1em},below skip={\vskip1em}
+	}
+}
 
 \newcommand{\tv}{\vec{t}}
 \newcommand{\uv}{\vec{u}}
 \newcommand{\vv}{\vec{v}}
 
+% to be used in pre=... to stop the proparation of warnings outside of tkzexample environments
+\makeatletter
+\def\disablewarnings{\def\pgfutil at packagewarning##1##2{}}
+\makeatother
+
 \begin{document}
 
 \title{The \textsf{tikz-nfold} package}
 \author{Jonathan Schulz}
-\date{March 2023}
+\date{\monthname{} \the\year}
 
 \maketitle
 
 \begin{abstract}
-  This package provides an alternative to TikZ' \verb|/tikz/double| option, avoiding some shortcomings of the original approach. It also provides an option to draw triple, quadruple, and n-fold paths.
+  This package provides an alternative to TikZ' \verb|/tikz/double| option, avoiding some shortcomings of the original approach. Further features include options to draw triple, quadruple, and n-fold paths as well as macros to offset arbitrary paths.
 \end{abstract}
 
 \section*{Compatibility}
-This package has been tested with \texttt{pdflatex}, \texttt{lualatex} and \texttt{xelatex}. Support for plain \TeX{} could, in principle, be implemented in the future as well.
+This package has been tested with \texttt{pdflatex}, \texttt{lualatex}, \texttt{xelatex}, and plain \texttt{pdftex}.
 
 \section{Quick start}
 
@@ -68,8 +78,7 @@
 \begin{verbatim}
 \usetikzlibrary{nfold}
 \end{verbatim}
-to your preamble. Now you can add the style \tikznfold{} to any path that uses \tikzdouble:
-
+to your preamble. Now you can add \tikznfold{} to any path that uses \tikzdouble. Be sure to specify \verb|/tikz/double distance| before \tikznfold, as otherwise the latter will not be applied.
 \begin{tkzexample}[latex=4cm]
 \begin{tikzpicture}
   \draw[double distance=3pt]
@@ -80,8 +89,7 @@
   \node[right] at (0,1) {nfold:};
 \end{tikzpicture}
 \end{tkzexample}
-While it appears that adding \nfold{} does not do much here, it avoids some rendering issues of \tikzdouble, hence I recommend using it in most cases (see \cref{subsec:doubleIssues} for details).
-
+While it appears that adding \nfold{} does not do much here, it avoids some rendering issues of \tikzdouble, hence I recommend using it in most cases (see \cref{sec:doubleIssues} for details).
 Specify a number for n-fold lines:
 \begin{tkzexample}[latex=4cm]
 \begin{tikzpicture}
@@ -89,17 +97,27 @@
     (0,0) to[out=0, in=180] (3,1) -- (3,0);
 \end{tikzpicture}
 \end{tkzexample}
-
-The arrow tips \texttt{Implies} and \texttt{Bar} are supported (the latter can also be aliased by \texttt{|}):
-\begin{tkzexample}[, latex=4cm]
+All arrow tips are supported, and there is special treatment for the \texttt{Implies} tip:
+\begin{tkzexample}[latex=4cm]
 \begin{tikzpicture}
-  \draw[double distance=7pt, nfold=5, arrows=Bar-Implies]
+  \draw[double distance=7pt, nfold=5,
+        arrows={Bar[width=18pt]-Implies[red]}]
     (0,1.5) to[bend left] (3,1.5);
   \draw[double distance=7pt, nfold=5, arrows=Implies-Implies]
     (0,0) to[bend left] (3,0);
 \end{tikzpicture}
 \end{tkzexample}
-
+Use \texttt{/tikz/scaling nfold} to preserve the distance between component lines instead of the overall width of the arrow:
+\begin{tkzexample}[latex=4cm,pre={\usetikzlibrary{intersections}}]
+\begin{tikzpicture}
+  \draw[double equal sign distance, nfold,
+        arrows=-Implies] (0,.75) -- (3,.75);
+  \draw[double equal sign distance, scaling nfold=4,
+        arrows=-Implies] (0,0) -- (3,0);
+  \draw[double equal sign distance, scaling nfold=6,
+        arrows=-Implies] (0,-1) -- (3,-1);
+\end{tikzpicture}
+\end{tkzexample}
 Different line joins are supported:
 \begin{tkzexample}[latex=2cm]
 \begin{tikzpicture}[line join=bevel]
@@ -111,18 +129,19 @@
     (0,0) -- (1, 0) -- (1,.5);
 \end{tikzpicture}
 \end{tkzexample}
-
-There is also support for \texttt{tikz-cd}:
+There is also support for \texttt{tikz-cd} with custom label positions for \texttt{scaling nfold}:
 \begin{tkzexample}[pre={\usetikzlibrary{cd}}, latex=3cm]
 \begin{tikzcd}
-  a \ar[r, Rightarrow, bend right, nfold=3] &
-  b \ar[d, Mapsto, nfold=3, "\alpha", "\beta"'] \\
-  c \ar[r, Mapsfrom, double distance=4pt, nfold=4] &
+  a \ar[r, Mapsto, bend left, scaling nfold=3] &
+  b \ar[d, Rightarrow, nfold, "\alpha", "\beta"'] \\
+  c \ar[r, Mapsfrom, scaling nfold=4, "\gamma" near end] &
   d
 \end{tikzcd}
 \end{tkzexample}
 
+
 \section{Comparison to \tikzdouble}
+\label{sec:doubleIssues}
 
 This package does \emph{not} aim to supersede \tikzdouble, as both the original and the \nfold{} approach have their own strengths and weaknesses. The main difference is that \tikzdouble{} achieves its goal by drawing the original path twice, once very thick with the foreground colour and then slightly less thick with the background colour. By contrast, \nfold{} offsets the path:
 \begin{equation}
@@ -143,12 +162,8 @@
     \draw[double distance=5pt, nfold] (4,0) -- (5,0);
   \end{tikzpicture}
 \end{equation}
-
-
-\subsection{Issues with \tikzdouble}
-\label{subsec:doubleIssues}
 While the approach of \tikzdouble{} is very robust and efficient, it does have a few pitfalls:
-\begin{itemize}
+\begin{itemize}[beginpenalty=10000]
   \item Different types of visual glitches can occur in PDF renderers:
   \begin{itemize}
     \item One common issue is that the white foreground piece completely covers the black background piece at certain zoom levels, leading to the top or bottom part of the doubled path missing (depending on your PDF viewer and zoom level, this issue might be visible in \cref{eq:compareApproaches}).
@@ -179,13 +194,23 @@
 \end{tkzexample}
   \item Triple and n-fold paths are not supported (although this could be implemented in principle).
 \end{itemize}
+However, there are still situations where \tikznfold{} struggles and \tikzdouble{} is the only viable option, which will be discussed in the next section.
 
-\subsection{Issues with \tikznfold}
+\section{Known issues}
 
-This package is far from perfect, and even if it were, there would still be some cases where the approach of \tikzdouble{} is better suited. Here are some shortcomings of \nfold:
+This package is by no means perfect, and even if it were, there would still be some cases where the approach of \tikzdouble{} is better suited. The known issues are roughly sorted into those that can be fixed in principle and the fundamental limitations of this approach. If you find any bugs not listed here, please report them \href{https://github.com/jonschz/tikz-nfold/issues/}{here}.
+
+\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.
+\end{itemize}
+
+\subsection{Impossible or very hard to fix}
+\begin{itemize}
   \item \nfold{} struggles with high curvatures and wide paths: Let $\kappa(t)$ be the curvature of the path in a given point, and let $\texttt{double distance} = \alpha$. If $\kappa(t) > \frac{2}{\alpha}$ (i.e. the radius of the osculating circle is smaller than half the width of the path) for some $0 \leq t \leq 1$, the output of \nfold{} will not be correct:
-\begin{tkzexample}[latex=3.5cm]
+\begin{tkzexample}[latex=4.25cm]
 \begin{tikzpicture}
   \draw[double distance=5pt, line width=1pt]
     (0,2) .. controls (4,2) and (0,3) .. (3,2.5);
@@ -195,30 +220,14 @@
   \node[right] at (0,1) {nfold:};
 \end{tikzpicture}
 \end{tkzexample}
-  Some, but not all of these cases raise warnings (this feature is on the wish list).
-  \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.
-  \item Closing paths (i.e.\ using \texttt{-- cycle}) is not yet supported (this feature is on the wish list).
-  \item \nfold{} is significantly slower than \tikzdouble. Part of the reason is that the construction is far more complex, other reasons can be fixed in principle. Specifically, the use of the \texttt{decorations} library is rather inefficient for this purpose.
-  \item While I did my best trying to break \nfold{} with as many of TikZ' options as possible, there are most definitely some problematic options I have not tested. If you find any bugs, please report them \href{https://github.com/jonschz/tikz-nfold/issues/}{here}.
-\end{itemize}
-
-
-\section{Known issues}
-
-\subsection{Fixable / wish list}
-
-\begin{itemize}
-  \item Closing paths (e.g.\ \texttt{-- cycle}) is not yet properly supported.
-  \item The arrow detection is still quite rough and likely has some bugs. One known issue is that you cannot provide parameters to the arrow tips (e.g. \texttt{Implies[red]}).
-  \item Similarly, redefining arrows does not have the desired effect (usually, it has no effect at all).
-  \item It would not be very hard to check for too much curvature in the offsetting algorithm and throw warnings in these cases.
-  \item Migrating away from the \texttt{decorations} library and integrating this algorithm more tightly with the rendering pipeline would be possible. I expect that doing so will significantly improve the performance and also fix most arrow-related issues. This idea is on the back burner for now, as it likely requires changes to the TikZ rendering pipeline. If the TikZ team shows interest in integrating this library, I will reconsider this.
-  \item Discontinuous paths with an arrow tip at the start are rendered differently in \tikzdouble{} and \nfold. As I do not really see a use case for such paths (and a rendering pipeline integration would likely fix it anyway), this issue is not a priority for now.
-\end{itemize}
-
-\subsection{Impossible or very hard to fix}
-\begin{itemize}
-  \item Correctly rendering paths with too much curvature is borderline impossible with this approach. This is one of the cases where using \tikzdouble{} is the only viable option.
+  Some, but not all of these cases raise warnings (this feature is on the wish list). This is one of the cases where using \tikzdouble{} is the only viable option.
+  \item Dashed paths with significant curvature will desynchronise:
+\begin{tkzexample}[latex=3cm]
+\begin{tikzpicture}
+  \draw[arrows=-Implies, double equal sign distance, dashed,
+        scaling nfold=4] (0,0) to[out=30, in=150] (2,0);
+\end{tikzpicture}
+\end{tkzexample}
   \item Curves of \nfold{} slightly deviate from the curves of \tikzdouble{} near joins with a non-zero angle:
 \begin{tkzexample}[latex=3.5cm]
 \begin{tikzpicture}[line width=1pt]
@@ -231,7 +240,18 @@
 \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 changing joins in \verb|\pgfsys at beginscope| without an accompanying \TeX{} group may cause inconsistent behaviour in the joins. For example,
+  \item Very short \emph{curves} with large angles at the ends result in a glitched output:
+\begin{tkzexample}[latex=3.75cm,pre=\disablewarnings]
+\begin{tikzpicture}
+  \draw[black] (1,0) -- (3,0) -- (1.5,1.2) -- (3.8,0.85);
+  \draw[red, line width=1pt, double distance=.6cm, nfold]
+    (1,0) -- (3,0) -- (1.5, 1.2) -- (3.8,0.85);
+  \draw[blue, double distance=.6cm, 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.
+  \item Changing joins in \verb|\pgfsys at beginscope| without an accompanying \TeX{} group may cause inconsistent behaviour in the joins:
 \begin{tkzexample}[latex=2cm]
 \makeatletter
 \begin{tikzpicture}[line join=miter, line width=2pt]
@@ -242,27 +262,89 @@
 \end{tikzpicture}
 \makeatother
 \end{tkzexample}
-  has \texttt{round} joins on the large path, but \texttt{miter} joins on the constituent paths. This problem does not occur with \verb|\pgfscope|.
+  This example has \texttt{round} joins on the large path but \texttt{miter} joins on the constituent paths. This problem does not occur with \verb|\pgfscope|.
 \end{itemize}
 
+
 \section{The basic layer \texttt{pgf} commands}
 
-This package also provides some basic layer commands for offsetting curves and straight lines. Use
+\subsection{Offsetting curves}
+This package provides some basic layer commands for offsetting curves and straight lines. Use
 \begin{verbatim}
 \usepgflibrary{bezieroffset}
 \end{verbatim}
 to only import the base layer library. The following commands are provided:
 \begin{itemize}
-  \item \verb|\pgfoffsetcurve|: This macro draws the parallel of a Bézier curve. It takes five parameters, the first four being the four control points of the Bézier curve (e.g.\ in the form of \verb|\pgfpoint{}{}|), the fifth parameter is the distance by which the curve should be offset. A negative value offsets the curve in the opposite direction. This macro begins with a \verb|\pgfpointmoveto| to the offset first control point.
-  \item \verb|\pgfoffsetcurvenomove|: The only difference to the previous macro is that this version does not move to the offset first control point. This is useful if one wants to offset an uninterrupted path consisting of several curves. The output will only be correct if the previous path segment ends on the offset first control point.
-  \item \verb|\pgfoffsetline|: This macro offsets a straight line. It takes two points and the distance as parameters.
-  \item \verb|\pgfoffsetlinenomove|: This macro is analogous to \verb|\pgfoffsetcurvenomove|.
+  \item \verb|\pgfoffsetcurve{pt1}{pt2}{pt3}{pt4}{distance}|\\%
+    This macro draws the parallel of a Bézier curve. The first four parameters are the control points of the Bézier curve (e.g.\ in the form of \verb|\pgfpoint{}{}|), the fifth parameter is the distance by which the curve should be offset. A negative value offsets the curve in the opposite direction. This macro begins with a \verb|\pgfpointmoveto| to the offset of \texttt{pt1}.
+  \item \verb|\pgfoffsetcurvenomove{pt1}{pt2}{pt3}{pt4}{distance}|\\%
+    The only difference to the previous macro is that this version does not move to the offset of \texttt{pt1}. This is useful if one wants to offset an uninterrupted path consisting of several curves. The output will only be correct if the previous \verb|\pgfpath...| call ends on the offset of \texttt{pt1}.
+  \item \verb|\pgfoffsetline{pt1}{pt2}{distance}|\\%
+    This macro offsets a straight line. It takes two points and the distance as parameters, and starts by moving to the offset of the first point.
+  \item \verb|\pgfoffsetlinenomove{pt1}{pt2}{distance}|\\%
+    This macro is analogous to \verb|\pgfoffsetcurvenomove|.
 \end{itemize}
 
+\subsection{Offsetting paths}
+The following macros are part of the TikZ library \nfold{} and offset an entire soft path.
+\begin{itemize}
+  \item \verb|\pgfoffsetpath{softpath}{distance}|\\%
+    This macro offsets \texttt{softpath} by \texttt{distance}. The latter may be negative.
+  \item \verb|\pgfoffsetpathfraction{softpath}{hwidth}{fraction}|\\%
+    This macro offsets \texttt{softpath} by \texttt{fraction*hwidth}. Note that this is \emph{not} equivalent to the previous macro with \texttt{length=fraction*hwidth} because the joins are treated differently, as can be seen in the examples below. Further note that \texttt{hwidth} must not be negative, and that \texttt{fraction=0} does \emph{not} reproduce the input path.
+  \item \verb|\pgfoffsetpathqfraction{softpath}{hwidth}{fraction}|\\%
+    This macro is a quicker version of the previous macro  does not parse the input values using the \texttt{pgfmath}-engine.
+  \item \verb|\pgfoffsetpathindex{softpath}{width}{i}{n}|\\%
+    In this convenience method, \texttt{i} and \texttt{n} are integers with $1 \leq i \leq n$. It calls the previous macro with \texttt{fraction=-1.0} for \texttt{i=1} and with \texttt{fraction=1.0} for \texttt{i=n} and is thus capable of reproducing the output of \texttt{/tikz/nfold=n} (albeit in a less efficient way).
+\end{itemize}
+In the following example we see how \verb|\pgfoffsetpath{..}{0pt}| reproduces the input path (rendered in black) and how \verb|\pgfoffsetpathfraction{..}{8pt}{0}| differs.
+\begin{tkzexample}[latex=4cm]
+\begin{tikzpicture}[line join=bevel]
+  \path[save path=\savedpath] (0,0) -- (1,0)
+    to[out=0, in=-80] (1,3) -- (3,2);
+  \draw[color=lightgray,line width=16pt,use path=\savedpath];
+  \pgfoffsetpathfraction{\savedpath}{8pt}{0}
+  \pgfsetlinewidth{1pt} \color{red} \pgfusepathqstroke
+  \pgfoffsetpath{\savedpath}{8pt}
+  \color{blue} \pgfusepathqstroke
+  \pgfoffsetpath{\savedpath}{-8pt}
+  \color{green} \pgfusepathqstroke
+  \pgfoffsetpath{\savedpath}{0pt}
+  \pgfsetlinewidth{.4pt} \color{black} \pgfusepathqstroke
+\end{tikzpicture}
+\end{tkzexample}
+Here we see how the commands can be used to customise $n$-fold paths:
+\begin{tkzexample}[latex=4cm]
+\begin{tikzpicture}
+  \path[save path=\mypath] (0,0) -- (2,0) arc(-90:90:1)
+    to[out=180, in=0] (0,1) -- (0,2);
+  \foreach \mycolor [count=\i] in {red,green,blue,violet}
+    \pgfoffsetpathindex{\mypath}{6pt}{\i}{4}
+    \color{\mycolor} \pgfusepathqstroke;
+\end{tikzpicture}
+\end{tkzexample}
 
+
+\newpage
+\section{Version history}
+
+\begin{itemize}
+  \item \textbf{v0.1.0}: Major overhaul
+  \begin{itemize}
+    \item Support for arbitrary arrow tips
+    \item Support for directly offsetting soft paths
+    \item New key \texttt{/tikz/scaling nfold}
+    \item The \texttt{decorations} library was dropped
+    \item Various performance improvements in \texttt{bezieroffset} (thanks to \hbox{Qrrbrbirlbel})
+    \item Very short lines with large angles were fixed (e.g.\ in \texttt{tikzcd} with \texttt{squiggly})
+    \item Numerous bugs fixed
+  \end{itemize}
+  \item \textbf{v0.0.1}: First public version
+\end{itemize}
+
 \appendix
 \newpage
-\section{The offsetting algorithm}
+\section{The Bézier offsetting algorithm}
 
 This algorithm is based on an algorithm by \href{https://github.com/Pomax/}{Pomax}. See \href{https://pomax.github.io/bezierinfo/#offsetting}{A Primer on Bézier curves}, the source code can be found \href{https://github.com/Pomax/bezierinfo/blob/bcfce2149fa5e5540a2a2605986adab3b2a9a3bf/js/graphics-element/lib/bezierjs/bezier.js}{here}.
 
@@ -288,7 +370,7 @@
 
 \subsection{Subdivision}
 
-It is well known that at every point $0 < t < 1$, a Bézier curve $A = (A_1, A_2, A_3, A_4)$ can be subdivided into two Bézier curves $B$ and $C$ using de Casteljau's algorithm (which naturally fulfil $A_1 = B_1$ and $A_4 = C_4$). A more or less heuristic fact is that $B$ and $C$ are ``more likely'' to be simple than $A$ (if you can prove any of the statements here, please contact me). Hence, if one wants to offset a non-simple curve $A$, one could try to subdivide $A$ until all of its segments are simple, then offset each segment.
+It is well known that at every point $0 < t < 1$, de Casteljau's algorithm can subdivide a Bézier curve $A = (A_1, A_2, A_3, A_4)$ into two Bézier curves $B$ and $C$ (which naturally fulfil $A_1 = B_1$ and $A_4 = C_4$). A more or less heuristic fact is that $B$ and $C$ are ``more likely'' to be simple than $A$ (if you can prove any of the statements here, please contact me). Hence, if one wants to offset a non-simple curve $A$, one could try to subdivide $A$ until all of its segments are simple, then offset each segment.
 
 \subsection{Pomax' approach}
 The original approach by Pomax consists of two passes. The first pass subdivides $A$ on all extrema in $x$ or $y$. In a second pass, each segment $A^{(i)}$ is made simple in steps of $t \mapsto t + 0.01$, roughly using the following pseudocode:
@@ -298,20 +380,20 @@
   while t_2 < 1.0:
     S = segment(A from t_1 to t_2+0.01)
     if not isSimple(S):
-      segments += [S]
+      segments += [segment(A from t_1 to t_2)]
       t_1 = t_2
     t_2 += 0.01
 \end{verbatim}
 \end{samepage}
-Essentially, this verifies with great certainty that the segment is fully simple in the sense of 
+Essentially, this ensures with great certainty that the segment is fully simple in the sense of \cref{def:fullySimpleCurve}.
 
-The main reason this approach is not used in this library is performance, as the library is slow enough already. Other minor reasons include that the original approach is not invariant under reversals or rotations: Reversing and/or rotating a curve yields a different subdivision and hence potentially a slightly different-looking curve.
+The main reason this approach is not used in this library is performance, since the loop is quite expensive computationally. Other minor reasons include that the original approach is not invariant under reversals or rotations: Reversing and/or rotating a curve yields a different subdivision and hence potentially a slightly different-looking curve.
 
 \subsection{The approach used here}
 \label{subsec:subdivisionUsed}
-In this library, we instead take a recursive approach:
+In this library we instead take a recursive approach:
 \begin{verbatim}
-  def split(A, level):
+  def makeSimple(A, level):
     if isSimple(A):
       segments += [A]
     else:
@@ -320,8 +402,8 @@
         segments += [A]
       else:
         first, second = split(A, t=0.5)
-        split(first, level-1)
-        split(second, level-1)
+        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.
 
@@ -330,7 +412,7 @@
 
 Disregarding edge cases (which will be discussed later), offsetting the curve works as follows:
 \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.
+  \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 $A'_3$ can be constructed similarly.
@@ -401,7 +483,7 @@
 \begin{equation}
   \frac{\sin(\tfrac{\pi}{2} - \alpha)}{\overline{OA_4}} = \frac{\sin(\alpha + \beta)}{\overline{A_1A_4}} = \frac{\sin(\tfrac{\pi}{2} - \beta)}{\overline{OA_1}} \implies \frac{1}{\overline{OA_1}} =  \frac{1}{\overline{A_1A_4}}\cdot\frac{\sin(\alpha + \beta)}{\cos(\beta)} \ .
 \end{equation}
-Note that $-\tfrac{\pi}{2} < \beta < \tfrac{\pi}{2}$ (and hence $\cos(\beta) > 0$) is guaranteed if the curve is simple --- in fact, simplicity guarantees $\alpha \cdot \beta > 0$ and $|\alpha| + |\beta| \leq \tfrac{\pi}{3}$. Using the sine addition theorem we can further rewrite the fraction to
+Note that $-\tfrac{\pi}{3} \leq \beta \leq \tfrac{\pi}{3}$ (and hence $\cos(\beta) \geq \tfrac{1}{2}$) is guaranteed if the curve is simple --- in fact, simplicity guarantees $\alpha \cdot \beta > 0$ and $|\alpha| + |\beta| \leq \tfrac{\pi}{3}$. Using the sine addition theorem we can further rewrite the fraction to
 \begin{equation}
   \frac{\sin(\alpha + \beta)}{\cos(\beta)} = \sin(\alpha) + \cos(\alpha) \frac{\sin(\beta)}{\cos(\beta)} \ ,
 \end{equation}
@@ -423,7 +505,7 @@
 
 \subsection{Edge cases}
 
-\subsubsection{Overlaps: $A_i = A_{i+1}$}
+\subsubsection{Overlaps: \texorpdfstring{$A_i = A_{i+1}$}{A\_i = A\_(i+1)}}
 
 If there is one overlap $A_1=A_2$, $A_2=A_3$ or $A_3=A_4$, the cubic Bézier curve reduces to a quadratic one. For two overlaps, we get a linear Bézier curve (i.e.\ a straight line), and for three overlaps we get a point. The main problem to watch out for is that the tangents $\tv_0$ and $\tv_1$ need to be computed differently:
 \begin{itemize}
@@ -434,7 +516,7 @@
 \end{itemize}
 The analogous statement hold for $\tv_1$. In practice we test for approximate, not exact equality.
 
-\subsubsection{Overlaps $A_1 = A_4$}
+\subsubsection{Overlaps \texorpdfstring{$A_1 = A_4$}{A\_1 = A\_4}}
 
 \Cref{eq:finalPrimeDistance} has one remaining singularity, namely for $A_1 \approx A_4$. This singularity is fundamental and not an artefact: As $A_1$ approaches $A_4$ while $\overline{A_1A_2}$ and $\overline{A_3A_4}$ stay constant, $O$ also approaches $A_4$, hence the angle between $\overrightarrow{A_1A_2}$ and $\overrightarrow{OA_2}$ approaches zero, sending the intersection point $A'_2$ to infinity.
 

Modified: trunk/Master/texmf-dist/tex/latex/tikz-nfold/pgflibrarybezieroffset.code.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/tikz-nfold/pgflibrarybezieroffset.code.tex	2023-04-24 20:11:42 UTC (rev 66927)
+++ trunk/Master/texmf-dist/tex/latex/tikz-nfold/pgflibrarybezieroffset.code.tex	2023-04-24 20:15:03 UTC (rev 66928)
@@ -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 at x and \pgf at y in #1
-\def\pgfstorepoint#1{\edef#1{\noexpand\pgfpoint{\the\pgf at x}{\the\pgf at y}}}
-% global version in case we need it:
-\def\pgfglobalstorepoint#1{\xdef#1{\noexpand\pgfpoint{\the\pgf at x}{\the\pgf at 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 at splitbezier}[5]{
+\def\pgf at splitbezier#1#2#3#4#5{%
   % based on pgfcorepoints.code.tex, \pgfpointcurveattime
-  \pgfmathparse{#1}%
-  \let\pgf at time@s=\pgfmathresult%
-  \global\pgf at x=\pgfmathresult pt%
-  \global\pgf at x=-\pgf at x%
+  \pgfmathsetmacro\pgf at time@s{#1}%
+  \pgf at x=-\pgf at time@s pt%
   \advance\pgf at x by 1pt%
   \edef\pgf at time@t{\pgf at sys@tonumber{\pgf at x}}%
   % P^0_3
-  \pgf at process{#5}%
+  \pgfextract at process\pgf at splitbezier@ii at iv{#5}%
   \pgf at xc=\pgf at x%
   \pgf at yc=\pgf at y%
-  \pgfstorepoint{\pgf at splitbezier@ii at iv}
   % P^0_2
   \pgf at process{#4}%
   \pgf at xb=\pgf at x%
@@ -54,13 +45,12 @@
   \pgf at xa=\pgf at x%
   \pgf at ya=\pgf at y%
   % P^0_0
-  \pgf at process{#2}%
-  \pgfstorepoint{\pgf at splitbezier@i at i}
+  \pgfextract at process\pgf at splitbezier@i at i{#2}%
   % First iteration:
   % P^1_0
-  \global\pgf at x=\pgf at time@t\pgf at x\global\advance\pgf at x by\pgf at time@s\pgf at xa%
-  \global\pgf at y=\pgf at time@t\pgf at y\global\advance\pgf at y by\pgf at time@s\pgf at ya%
-  \pgfstorepoint{\pgf at splitbezier@i at ii}
+  \pgf at x=\pgf at time@t\pgf at x\advance\pgf at x by\pgf at time@s\pgf at xa%
+  \pgf at y=\pgf at time@t\pgf at y\advance\pgf at y by\pgf at time@s\pgf at ya%
+  \pgfextract at process\pgf at splitbezier@i at ii{}%
   % P^1_1
   \pgf at xa=\pgf at time@t\pgf at xa\advance\pgf at xa by\pgf at time@s\pgf at xb%
   \pgf at ya=\pgf at time@t\pgf at ya\advance\pgf at ya by\pgf at time@s\pgf at yb%
@@ -67,77 +57,111 @@
   % P^1_2
   \pgf at xb=\pgf at time@t\pgf at xb\advance\pgf at xb by\pgf at time@s\pgf at xc%
   \pgf at yb=\pgf at time@t\pgf at yb\advance\pgf at yb by\pgf at time@s\pgf at yc%
-  \edef\pgf at splitbezier@ii at iii{\noexpand\pgfpoint{\the\pgf at xb}{\the\pgf at yb}}
+  \edef\pgf at splitbezier@ii at iii{\noexpand\pgfqpoint{\the\pgf at xb}{\the\pgf at yb}}%
   % P^2_0
-  \global\pgf at x=\pgf at time@t\pgf at x\global\advance\pgf at x by\pgf at time@s\pgf at xa%
-  \global\pgf at y=\pgf at time@t\pgf at y\global\advance\pgf at y by\pgf at time@s\pgf at ya%
-  \pgfstorepoint{\pgf at splitbezier@i at iii}
+  \pgf at x=\pgf at time@t\pgf at x\advance\pgf at x by\pgf at time@s\pgf at xa%
+  \pgf at y=\pgf at time@t\pgf at y\advance\pgf at y by\pgf at time@s\pgf at ya%
+  \pgfextract at process\pgf at splitbezier@i at iii{}%
   % P^2_1
   \pgf at xa=\pgf at time@t\pgf at xa\advance\pgf at xa by\pgf at time@s\pgf at xb%
   \pgf at ya=\pgf at time@t\pgf at ya\advance\pgf at ya by\pgf at time@s\pgf at yb%
-  \edef\pgf at splitbezier@ii at ii{\noexpand\pgfpoint{\the\pgf at xa}{\the\pgf at ya}}
+  \edef\pgf at splitbezier@ii at ii{\noexpand\pgfqpoint{\the\pgf at xa}{\the\pgf at ya}}%
   % P^3_0
-  \global\pgf at x=\pgf at time@t\pgf at x\global\advance\pgf at x by\pgf at time@s\pgf at xa%
-  \global\pgf at y=\pgf at time@t\pgf at y\global\advance\pgf at y by\pgf at time@s\pgf at ya%
-  \pgfstorepoint{\pgf at splitbezier@i at iv}
-  \pgfstorepoint{\pgf at splitbezier@ii at i}
+  \pgf at x=\pgf at time@t\pgf at x\advance\pgf at x by\pgf at time@s\pgf at xa%
+  \pgf at y=\pgf at time@t\pgf at y\advance\pgf at y by\pgf at time@s\pgf at ya%
+  \pgfextract at process\pgf at splitbezier@i at iv{}%
+  \let\pgf at splitbezier@ii at i\pgf at splitbezier@i at iv
 }
 
 
 % computes the cross product and puts it into \pgfmathresult
-\newcommand{\pgfcrossproduct}[2]{
-  \pgf at process{#1}%
-  \pgf at xa=\pgf at x%
-  \pgf at ya=\pgf at y%
-  \pgf at process{#2}%
-  \pgfmathparse{\pgf at xa*\pgf at y-\pgf at ya*\pgf at x}%
+\def\pgfmathcrossproduct#1#2{%
+  \begingroup
+    \pgf at process{#1}%
+    \pgf at xa=\pgf at x%
+    \pgf at ya=\pgf at y%
+    \pgf at process{#2}%
+    \pgf at y=\pgf at sys@tonumber\pgf at xa\pgf at y
+    \advance\pgf at y by -\pgf at sys@tonumber\pgf at ya\pgf at x
+  \pgfmath at returnone\pgf at y
+  \endgroup
 }
 
-\newcommand{\pgfdotproduct}[2]{
-  \pgf at process{#1}%
-  \pgf at xa=\pgf at x%
-  \pgf at ya=\pgf at y%
-  \pgf at process{#2}%
-  \pgfmathparse{\pgf at xa*\pgf at x+\pgf at ya*\pgf at y}%
+\def\pgfmathdotproduct#1#2{%
+  \begingroup
+    \pgf at process{#1}%
+    \pgf at xa=\pgf at x%
+    \pgf at ya=\pgf at y%
+    \pgf at process{#2}%
+    \pgf at x=\pgf at sys@tonumber\pgf at xa\pgf at x
+    \advance\pgf at x by \pgf at sys@tonumber\pgf at ya\pgf at y
+  \pgfmath at returnone\pgf at x
+  \endgroup
 }
 
-\newcommand{\pgfcrossdot}[2]{
-  \pgf at process{#1}%
-  \pgf at xa=\pgf at x%
-  \pgf at ya=\pgf at y%
-  \pgf at process{#2}%
-  \pgfmathsetlengthmacro{\pgf at tmp@dot}{\pgf at xa*\pgf at x+\pgf at ya*\pgf at y}%
-  \pgfmathsetlengthmacro{\pgf at tmp@cross}{\pgf at xa*\pgf at y-\pgf at ya*\pgf at x}%
+\def\pgfmathcrossdot#1#2{%
+  \begingroup
+    \pgf at process{#1}%
+    \pgf at xa=\pgf at x%
+    \pgf at ya=\pgf at y%
+    \pgf at process{#2}%
+    \pgf at xb=\pgf at sys@tonumber\pgf at xa\pgf at x
+    \pgf at yb=\pgf at sys@tonumber\pgf at xa\pgf at y
+    \advance\pgf at xb by \pgf at sys@tonumber\pgf at ya\pgf at y
+    \advance\pgf at yb by -\pgf at sys@tonumber\pgf at ya\pgf at x
+    \edef\pgf at temp{%
+      \edef\noexpand\pgf at tmp@dot{\pgf at sys@tonumber\pgf at xb}%
+      \edef\noexpand\pgf at tmp@cross{\pgf at sys@tonumber\pgf at yb}%
+    }%
+  \expandafter
+  \endgroup\pgf at temp
 }
 
-
+% Calculates abs(\pgf at x) + abs(\pgf at y) in #1
+\def\pgfpointtaxicabnorm#1{%
+  \ifdim\pgf at x<0pt
+    #1=-\pgf at x
+  \else
+    #1=\pgf at x
+  \fi
+  \ifdim\pgf at y<0pt
+    \advance#1 by -\pgf at y
+  \else
+    \advance#1 by \pgf at y
+  \fi
+}
 % Computes the normalised tangents of a given Bezier curve and stores them in \pgf at tmp@tang at i and \pgf at tmp@tang at ii.
+% Also computes the angles and stores them in  \pgf at tmp@angle at i and \pgf at tmp@angle at ii.
 % All degenerate cases are covered. For a triple degenerate curve (all points equal), the vector (1,0) is returned.
-\newcommand{\pgf at offset@compute at tangents}[4]{
-  \pgfpointdiff{#1}{#2}  % unintuitively, this is PTii - PTi
-  \pgfmathparse{abs(\pgf at x) + abs(\pgf at y)}
-  \ifdim\pgfmathresult pt<0.1pt\relax
+\def\pgf at offset@compute at tangents#1#2#3#4{%
+  \pgf at process{\pgfpointdiff{#1}{#2}}% unintuitively, this is PTii - PTi
+  \pgfpointtaxicabnorm\pgf at xa
+  \ifdim\pgf at xa<0.1pt\relax
     % edge case: first point and first control point are equal
-    \pgfpointdiff{#1}{#3}
-    \pgfmathparse{abs(\pgf at x) + abs(\pgf at y)}
-    \ifdim\pgfmathresult pt<0.1pt\relax
+    \pgf at process{\pgfpointdiff{#1}{#3}}%
+    \pgfpointtaxicabnorm\pgf at xa
+    \ifdim\pgf at xa<0.1pt\relax
       % edge case: first three points are equal
-      \pgfpointdiff{#1}{#4}
+      \pgf at process{\pgfpointdiff{#1}{#4}}%
     \fi
   \fi
-  \pgfpointnormalised{}
-  \pgfstorepoint\pgf at tmp@tang at i
-  \pgfpointdiff{#3}{#4}
-  \pgfmathparse{abs(\pgf at x) + abs(\pgf at y)}
-  \ifdim\pgfmathresult pt<0.1pt\relax
-    \pgfpointdiff{#2}{#4}
-    \pgfmathparse{abs(\pgf at x) + abs(\pgf at y)}
-    \ifdim\pgfmathresult pt<0.1pt\relax
-      \pgfpointdiff{#1}{#4}
+  \pgfextract at process\pgf at tmp@tang at i{%
+    \pgfpointnormalised{}%
+    % \pgfpointnormalised stores the angle of the vector in \pgf at tmp
+    \global\let\pgf at nfold@tmp\pgf at tmp%
+  }%
+  \let\pgf at tmp@angle at i\pgf at nfold@tmp%
+  \pgf at process{\pgfpointdiff{#3}{#4}}%
+  \pgfpointtaxicabnorm\pgf at xa
+  \ifdim\pgf at xa<0.1pt\relax
+    \pgf at process{\pgfpointdiff{#2}{#4}}%
+    \pgfpointtaxicabnorm\pgf at xa
+    \ifdim\pgf at xa<0.1pt\relax
+      \pgf at process{\pgfpointdiff{#1}{#4}}%
     \fi
   \fi
-  \pgfpointnormalised{}
-  \pgfstorepoint\pgf at tmp@tang at ii
+  \pgfextract at process\pgf at tmp@tang at ii{\pgfpointnormalised{}\global\let\pgf at nfold@tmp\pgf at tmp}%
+  \let\pgf at tmp@angle at ii\pgf at nfold@tmp%
 }
 
 
@@ -145,73 +169,89 @@
 % Offsetting a simple section %
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
-\newcommand{\pgf at offset@bezier at segment}[5]{%
-  % TODO would it make sense to use \pgf at process here?
+\def\pgf at offset@bezier at 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 at offset@compute at tangents{#1}{#2}{#3}{#4}
+  \pgf at offset@compute at tangents{#1}{#2}{#3}{#4}%
   % offset A1
   % compute the normal
   \pgf at tmp@tang at i
-  \pgf at xa=\pgf at x
-  \pgf at x=-\pgf at y
-  \pgf at y=\pgf at xa
-  \pgfstorepoint\pgf at tmp@normal at i
-  % Leaving this parameter empty amounts to working directly on the register
-  \pgfpointadd{\pgfpointscale{#5}{}}{#1}
-  \pgfstorepoint{\pgf at bezier@offset at i}
+  \edef\pgf at tmp@normal at i{\noexpand\pgfqpoint{-\the\pgf at y}{\the\pgf at x}}%
+  \pgfextract at process\pgf at bezier@offset at i
+    {\pgfpointadd{\pgfpointscale{#5}{\pgf at tmp@normal at i}}{#1}}%
   % offset A4
   \pgf at tmp@tang at ii
-  \pgf at xa=\pgf at x
-  \pgf at x=-\pgf at y
-  \pgf at y=\pgf at xa
-  \pgfstorepoint\pgf at tmp@normal at ii
-  \pgfpointadd{\pgfpointscale{#5}{}}{#4}
-  \pgfstorepoint\pgf at bezier@offset at iv
+  \edef\pgf at tmp@normal at ii{\noexpand\pgfqpoint{-\the\pgf at y}{\the\pgf at x}}%
+  \pgfextract at process\pgf at bezier@offset at iv
+    {\pgfpointadd{\pgfpointscale{#5}{\pgf at tmp@normal at ii}}{#4}}%
   % now compute A'_2 and A'_3
-  \pgfpointdiff{#1}{#4}
-  \pgfmathsetmacro{\pgf at tmp@secantlen}{veclen(\pgf at x,\pgf at y)}
+  \pgf at process{\pgfpointdiff{#1}{#4}}%
+  \pgfmathveclen@{\pgf at sys@tonumber\pgf at x}{\pgf at sys@tonumber\pgf at y}%
+  \let\pgf at tmp@secantlen\pgfmathresult
   \ifdim\pgf at 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 at bezier@offset at i}{\pgfpointdiff{#1}{#2}}
-    \pgfstorepoint\pgf at bezier@offset at ii
-    \pgfpointadd{\pgf at bezier@offset at iv}{\pgfpointdiff{#4}{#3}}
-    \pgfstorepoint\pgf at bezier@offset at iii
+    \pgfutil at packagewarning{tikz-nfold}{first and last point are too close, expect glitches}%
+    \pgfextract at process\pgf at bezier@offset at ii
+      {\pgfpointadd{\pgf at bezier@offset at i}{\pgfpointdiff{#1}{#2}}}%
+    \pgfextract at process\pgf at bezier@offset at iii
+      {\pgfpointadd{\pgf at bezier@offset at iv}{\pgfpointdiff{#4}{#3}}}%
   \else
-    \pgfpointnormalised{}
-    \pgfstorepoint\pgf at tmp@secant
-    \pgfcrossdot{}{\pgf at tmp@tang at ii}
-     \ifdim\pgf at tmp@dot<.5pt\relax%
+    \pgfextract at process\pgf at tmp@secant{\pgfpointnormalised{}}%
+    \pgfmathcrossdot{}{\pgf at tmp@tang at ii}%
+     \ifdim\pgf at tmp@dot pt<.5pt\relax%
        % this can only happen in non-simple curves
-       \pgfwarning{pgf-offset: cosine of \pgf at tmp@dot\space clamped to 0.5 in non-simple segment}%
-       \def\pgf at tmp@dot{.5pt}%
+       \pgfutil at packagewarning{tikz-nfold}{cosine of \pgf at tmp@dot\space clamped to 0.5 in non-simple segment}%
+       \def\pgf at tmp@dot{.5}%
     \fi%
-    \pgfmathsetmacro{\pgf at tmp@tanbeta}{\pgf at tmp@cross/\pgf at tmp@dot}%
-    \pgfcrossdot{\pgf at tmp@secant}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}
+    \pgfmathdivide@{\pgf at tmp@cross}{\pgf at tmp@dot}%
+    \let\pgf at tmp@tanbeta\pgfmathresult
+    \pgfmathcrossdot{\pgf at 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 at tmp@secantlen*(\pgf at tmp@cross - \pgf at tmp@dot*\pgf at tmp@tanbeta)}
-    \pgfpointadd%
-      {\pgf at bezier@offset at i}%
-      {\pgfpointscale{\pgfmathresult pt}{\pgfpointdiff{#1}{#2}}}%
-    \pgfstorepoint\pgf at bezier@offset at ii
+    % \pgfmathparse{1 + #5/\pgf at tmp@secantlen*(\pgf at tmp@cross - \pgf at tmp@dot*\pgf at tmp@tanbeta)}%
+    \pgfmath at offset@calculate at scale{\pgf at tmp@secantlen}{\pgf at tmp@cross}{\pgf at tmp@dot}{\pgf at tmp@tanbeta}{#5}%
+    \pgfextract at process\pgf at bezier@offset at ii{%
+      \pgfpointadd
+        {\pgf at bezier@offset at i}
+        {\pgfqpointscale{\pgfmathresult}{\pgfpointdiff{#1}{#2}}}%
+    }%
     % third control point
-    \pgfcrossdot{\pgf at tmp@secant}{\pgf at tmp@tang at i}
-    \ifdim\pgf at tmp@dot<.5pt\relax%
-      \pgfwarning{pgf-offset: cosine of \pgf at tmp@dot\space clamped to 0.5 in non-simple segment}%
-      \def\pgf at tmp@dot{.5pt}%
-    \fi%
-    \pgfmathsetmacro{\pgf at tmp@tanbeta}{\pgf at tmp@cross/\pgf at tmp@dot}%
-    \pgfcrossdot{\pgf at tmp@secant}{\pgfpointnormalised{\pgfpointdiff{#4}{#3}}}%
-    \pgfmathparse{1 + #5/\pgf at tmp@secantlen*(\pgf at tmp@cross - \pgf at tmp@dot*\pgf at tmp@tanbeta)}%
-    \pgfpointadd%
-      {\pgf at bezier@offset at iv}%
-      {\pgfpointscale{\pgfmathresult pt}{\pgfpointdiff{#4}{#3}}}%
-    \pgfstorepoint\pgf at bezier@offset at iii%
+    \pgfmathcrossdot{\pgf at tmp@secant}{\pgf at tmp@tang at i}%
+    \ifdim\pgf at tmp@dot pt<.5pt\relax
+      \pgfutil at packagewarning{tikz-nfold}{cosine of \pgf at tmp@dot\space clamped to 0.5 in non-simple segment}%
+      \def\pgf at tmp@dot{.5}%
+    \fi
+    \pgfmathdivide@{\pgf at tmp@cross}{\pgf at tmp@dot}%
+    \let\pgf at tmp@tanbeta\pgfmathresult
+    \pgfmathcrossdot{\pgf at tmp@secant}{\pgfpointnormalised{\pgfpointdiff{#4}{#3}}}%
+    % \pgfmathparse{1 + #5/\pgf at tmp@secantlen*(\pgf at tmp@cross - \pgf at tmp@dot*\pgf at tmp@tanbeta)}%
+    \pgfmath at offset@calculate at scale{\pgf at tmp@secantlen}{\pgf at tmp@cross}{\pgf at tmp@dot}{\pgf at tmp@tanbeta}{#5}%
+    \pgfextract at process\pgf at bezier@offset at iii{%
+      \pgfpointadd
+        {\pgf at bezier@offset at 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 at offset@calculate at scale#1#2#3#4#5{%
+  \begingroup
+    \pgfmathmultiply@{#3}{#4}%
+    \pgfmathsubtract@{#2}{\pgfmathresult}%
+    \let\pgfmath at temp\pgfmathresult
+    \pgfmathreciprocal@{#1}%
+    \pgfmathmultiply@{\pgfmathresult}{\pgfmath at temp}%
+    \pgfmathmultiply{\pgfmathresult}{#5}%
+    \pgfmathadd@{\pgfmathresult}{1}%
+    \pgfmath at smuggleone\pgfmathresult
+  \endgroup
+}
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 % Subdividing and offsetting %
@@ -225,7 +265,7 @@
 %
 % 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:
@@ -232,11 +272,18 @@
 % #1-#4: control points of the whole Bezier curve
 % #5: offset
 
-\newcommand{\pgfoffsetcurve}[5]{%
-  \pgf at subdivideandoffsetcurve{#1}{#2}{#3}{#4}{#5}{\pgf at offset@max at recursion}{0}{\pgf at nfold@callback at move}%
+% \def\pgfoffsetcurve#1#2#3#4#5{%
+%   \pgf at subdivideandoffsetcurve{#1}{#2}{#3}{#4}{#5}{\pgf at offset@max at recursion}{0}{\pgf at nfold@callback at move}%
+% }
+% \def\pgfoffsetcurvenomove#1#2#3#4#5{%
+%   \pgf at subdivideandoffsetcurve{#1}{#2}{#3}{#4}{#5}{\pgf at offset@max at recursion}{0}{\pgf at nfold@callback at nomove}%
+% }
+
+\def\pgfoffsetcurve#1#2#3#4#5{%
+  \pgfoffsetcurvecallback{#1}{#2}{#3}{#4}{#5}{\pgf at nfold@callback at move}%
 }
-\newcommand{\pgfoffsetcurvenomove}[5]{%
-  \pgf at subdivideandoffsetcurve{#1}{#2}{#3}{#4}{#5}{\pgf at offset@max at recursion}{0}{\pgf at nfold@callback at nomove}%
+\def\pgfoffsetcurvenomove#1#2#3#4#5{%
+  \pgfoffsetcurvecallback{#1}{#2}{#3}{#4}{#5}{\pgf at nfold@callback at nomove}%  
 }
 
 % Arguments:
@@ -243,17 +290,17 @@
 % #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 at nfold@callback at move}[5]{%
+\def\pgf at nfold@callback at 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 at nfold@callback at nomove}[5]{\pgfpathcurveto{#2}{#3}{#4}}
+\def\pgf at nfold@callback at 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 at subdivideandoffsetcurve{#1}{#2}{#3}{#4}{#5}{\pgf at offset@max at 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 at offset@subdivide
-\newcommand{\pgf at subdivideandoffsetcurve}[8]{%
+\def\pgf at subdivideandoffsetcurve#1#2#3#4#5#6#7#8{%
   % we need a group to avoid overwriting variables in recursive calls
   \begingroup%
-  \pgf at offset@subdividefalse%
-  \c at pgf@counta=#6%
-  \advance\c at pgf@counta by-1%
-  \pgf at process{#1}\pgfstorepoint{\pgf at ctrl@i}%
-  \pgf at process{#2}\pgfstorepoint{\pgf at ctrl@ii}%
-  \pgf at process{#3}\pgfstorepoint{\pgf at ctrl@iii}%
-  \pgf at process{#4}\pgfstorepoint{\pgf at ctrl@iv}%
-  % Use the non-degenerate tangents for the simplicity check
-  \pgf at offset@compute at tangents{\pgf at ctrl@i}{\pgf at ctrl@ii}{\pgf at ctrl@iii}{\pgf at ctrl@iv}%
-  \pgfpointdiff{\pgf at ctrl@i}{\pgf at ctrl@iv}\pgfstorepoint{\pgf at itoiv}%
-  \pgfcrossproduct{\pgf at itoiv}{\pgf at tmp@tang at i}%
-  \edef\firstcross{\pgfmathresult}%
-  \pgfcrossproduct{\pgf at itoiv}{\pgf at tmp@tang at 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 at offset@subdividetrue%
-  \else%
-    % Second simplicity check: How large is the angle between the tangents in A1 and A4?
-    \pgfdotproduct{\pgf at tmp@tang at i}{\pgf at tmp@tang at ii}%
-    \ifdim\pgfmathresult pt<.5pt\relax%
+    \pgf at offset@subdividefalse%
+    \c at pgf@counta=#6\relax
+    \advance\c at pgf@counta by -1
+    \pgfextract at process\pgf at ctrl@i{#1}%
+    \pgfextract at process\pgf at ctrl@ii{#2}%
+    \pgfextract at process\pgf at ctrl@iii{#3}%
+    \pgfextract at process\pgf at ctrl@iv{#4}%
+    % Use the non-degenerate tangents for the simplicity check
+    \pgf at offset@compute at tangents{\pgf at ctrl@i}{\pgf at ctrl@ii}{\pgf at ctrl@iii}{\pgf at ctrl@iv}%
+    \pgfextract at process\pgf at itoiv{\pgfpointdiff{\pgf at ctrl@i}{\pgf at ctrl@iv}}%
+    \pgfmathcrossproduct{\pgf at itoiv}{\pgf at tmp@tang at i}%
+    \let\firstcross\pgfmathresult
+    \pgfmathcrossproduct{\pgf at itoiv}{\pgf at tmp@tang at 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 at offset@subdividetrue%
-    \else
-      % Third simplicity check: Put a limit on the lengths of the i-ii and iii-iv vectors combined
-      \pgf at itoiv
-      \pgfmathsetmacro{\pgf at tmp@len at i@iv}{veclen(\pgf at x,\pgf at y)}
-      \pgfpointdiff{\pgf at ctrl@i}{\pgf at ctrl@ii}
-      \pgf at xb=\pgf at x\pgf at yb=\pgf at y
-      \pgfpointdiff{\pgf at ctrl@iii}{\pgf at ctrl@iv}
-      \pgfmathparse{\pgf at tmp@len at i@iv < veclen(\pgf at xb,\pgf at yb) + veclen(\pgf at x,\pgf at y)}
-      \ifnum\pgfmathresult=1\relax
+    \else%
+      % Second simplicity check: How large is the angle between the tangents in A1 and A4?
+      \pgfmathdotproduct{\pgf at tmp@tang at i}{\pgf at tmp@tang at ii}%
+      \ifdim\pgfmathresult pt<.5pt\relax%
         \pgf at offset@subdividetrue%
-      \fi
+      \else
+        % Third simplicity check: Put a limit on the lengths of the i-ii and iii-iv vectors combined
+        \pgf at itoiv
+        \pgfmathveclen@{\pgf at sys@tonumber\pgf at x}{\pgf at sys@tonumber\pgf at y}%
+        \pgf at xa=\pgfmathresult pt
+        \pgf at process{\pgfpointdiff{\pgf at ctrl@i}{\pgf at ctrl@ii}}%
+        \pgfmathveclen@{\pgf at sys@tonumber\pgf at x}{\pgf at sys@tonumber\pgf at y}%
+        \pgf at xb=\pgfmathresult pt
+        \pgf at process{\pgfpointdiff{\pgf at ctrl@iii}{\pgf at ctrl@iv}}%
+        \pgfmathveclen@{\pgf at sys@tonumber\pgf at x}{\pgf at sys@tonumber\pgf at y}%
+        \advance\pgf at xb by \pgfmathresult pt
+        % \veclen(itoiv) < veclen(ii-i) + veclen(iv-iii)
+        \ifdim\pgf at xa<\pgf at xb
+          \pgf at offset@subdividetrue
+        \fi
+      \fi%
     \fi%
-  \fi%
-  \ifpgf at offset@subdivide%
-    \ifnum\c at 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 at offset@max at recursion}%
-      % Try to offset the curve anyway. The result will not be precise,
-      % but the code is sufficiently robust to not crash
+    \ifpgf at offset@subdivide%
+      \ifnum\c at pgf@counta<0%
+        % We hit the recursion limit but the segment is not simple
+        \pgfutil at packagewarning{tikz-nfold}{Recursion limit reached, glitches may occur. %
+          Consider increasing \string\pgf at offset@max at recursion}%
+        % Try to offset the curve anyway. The result will not be precise,
+        % but the code is sufficiently robust to not crash
+        \pgf at offset@bezier at segment{\pgf at ctrl@i}{\pgf at ctrl@ii}{\pgf at ctrl@iii}{\pgf at ctrl@iv}{#5}%
+        #8{\pgf at bezier@offset at i}{\pgf at bezier@offset at ii}{\pgf at bezier@offset at iii}{\pgf at bezier@offset at iv}{#7}%
+      \else
+        % split the non-simple segment and execute recursive calls
+        \pgf at splitbezier{.5}{\pgf at ctrl@i}{\pgf at ctrl@ii}{\pgf at ctrl@iii}{\pgf at ctrl@iv}%
+        % pass on the "start of the curve flag" only to the first term
+        \pgf at subdivideandoffsetcurve{\pgf at splitbezier@i at i}{\pgf at splitbezier@i at ii}{\pgf at splitbezier@i at iii}{\pgf at splitbezier@i at iv}{#5}{\c at pgf@counta}{#7}{#8}%
+        \pgf at subdivideandoffsetcurve{\pgf at splitbezier@ii at i}{\pgf at splitbezier@ii at ii}{\pgf at splitbezier@ii at iii}{\pgf at splitbezier@ii at iv}{#5}{\c at pgf@counta}{1}{#8}%
+      \fi%
+    \else%
+      % curve is simple
       \pgf at offset@bezier at segment{\pgf at ctrl@i}{\pgf at ctrl@ii}{\pgf at ctrl@iii}{\pgf at ctrl@iv}{#5}%
       #8{\pgf at bezier@offset at i}{\pgf at bezier@offset at ii}{\pgf at bezier@offset at iii}{\pgf at bezier@offset at iv}{#7}%
-    \else
-      % split the non-simple segment and execute recursive calls
-      \pgf at splitbezier{.5}{\pgf at ctrl@i}{\pgf at ctrl@ii}{\pgf at ctrl@iii}{\pgf at ctrl@iv}%
-      % pass on the "start of the curve flag" only to the first term
-      \pgf at subdivideandoffsetcurve{\pgf at splitbezier@i at i}{\pgf at splitbezier@i at ii}{\pgf at splitbezier@i at iii}{\pgf at splitbezier@i at iv}{#5}{\c at pgf@counta}{#7}{#8}%
-      \pgf at subdivideandoffsetcurve{\pgf at splitbezier@ii at i}{\pgf at splitbezier@ii at ii}{\pgf at splitbezier@ii at iii}{\pgf at splitbezier@ii at iv}{#5}{\c at pgf@counta}{1}{#8}%
     \fi%
-  \else%
-    % curve is simple
-    \pgf at offset@bezier at segment{\pgf at ctrl@i}{\pgf at ctrl@ii}{\pgf at ctrl@iii}{\pgf at ctrl@iv}{#5}%
-    #8{\pgf at bezier@offset at i}{\pgf at bezier@offset at ii}{\pgf at bezier@offset at iii}{\pgf at bezier@offset at 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 at xc=-\pgf at y
   \pgf at yc=\pgf at x
-  \pgfpathmoveto{\pgfpointadd{#1}{\pgfqpoint{\pgf at xc}{\pgf at yc}}}
-  \pgfpathlineto{\pgfpointadd{#2}{\pgfqpoint{\pgf at xc}{\pgf at yc}}}
+  \pgfpathmoveto{\pgfpointadd{#1}{\pgfqpoint{\pgf at xc}{\pgf at yc}}}%
+  \pgfpathlineto{\pgfpointadd{#2}{\pgfqpoint{\pgf at xc}{\pgf at yc}}}%
 }
-\newcommand{\pgfoffsetlinenomove}[3]{
-  \pgfpointscale{#3}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}
+\def\pgfoffsetlinenomove#1#2#3{%
+  \pgfpointscale{#3}{\pgfpointnormalised{\pgfpointdiff{#1}{#2}}}%
   \pgf at xc=-\pgf at y
   \pgf at yc=\pgf at x
-  \pgfpathlineto{\pgfpointadd{#2}{\pgfqpoint{\pgf at xc}{\pgf at yc}}}
+  \pgfpathlineto{\pgfpointadd{#2}{\pgfqpoint{\pgf at xc}{\pgf at yc}}}%
 }

Modified: trunk/Master/texmf-dist/tex/latex/tikz-nfold/tikzlibrarynfold.code.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/tikz-nfold/tikzlibrarynfold.code.tex	2023-04-24 20:11:42 UTC (rev 66927)
+++ trunk/Master/texmf-dist/tex/latex/tikz-nfold/tikzlibrarynfold.code.tex	2023-04-24 20:15:03 UTC (rev 66928)
@@ -16,13 +16,11 @@
 % 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 at nfold@segm at visible
-\def\ifpgfsegmentvisible#1{%
+\def\checkpgfsegmentvisible#1{%
   \pgf at nfold@segm at visiblefalse%
-  \ifx#1\pgfdecorationinputsegmentlineto%
-  \pgf at nfold@segm at visibletrue\fi%
-  \ifx#1\pgfdecorationinputsegmentcurveto%
-  \pgf at nfold@segm at visibletrue\fi%
-  \ifx#1\pgfdecorationinputsegmentclosepath%
-  \pgf at nfold@segm at visibletrue\fi%
-  \ifpgf at nfold@segm at visible%
+  \ifx#1\pgf at nfold@inputsegmentlineto%
+    \pgf at nfold@segm at visibletrue%
+  \else\ifx#1\pgf at nfold@inputsegmentcurveto%
+    \pgf at nfold@segm at visibletrue
+  \else\ifx#1\pgf at nfold@inputsegmentclosepath%
+    \pgf at nfold@segm at 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 at nfold@miterjoin{
-  % - Although it isn't obvious, this code is invariant under
-  %   \pgf at nfold@deltaphi -> \pgf at 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 at decorate@inputsegment at first}%
+    {\pgf at nfold@inputsegment at first}%
     {\pgfpointpolar%
-      {\pgf at nfold@cached at endangle+.5*\pgf at nfold@deltaphi+90}%
-      {\pgf at shiftdec@amount/cos(.5*\pgf at nfold@deltaphi)}%
+      {\pgf at nfold@previous at endangle+.5*\pgf at nfold@deltaphi at start+90}% do not change
+      {\pgf at nfold@shiftamount/cos(.5*\pgf at nfold@deltaphi at start)}%
     }%
   \pgfpathlineto{}%
 }
 
 \def\pgf at nfold@beveljoin{
-  % The subsequent formula needs deltaphi to be between -180 and 180
-  \pgfmathsetmacro{\pgf at nfold@deltaphi}{Mod(\pgf at 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 at nfold@shortenforjoin - .5*\pgflinewidth*abs(tan(.25*\pgf at nfold@deltaphi))}
+  \pgfmathsetlengthmacro{\bevelouterprotrusion}%
+    {\pgf at nfold@shortenstartjoin - .5*\pgflinewidth*abs(tan(.25*\pgf at nfold@deltaphi at 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 at nfold@deltaphi))}
-  % This theshold decides if the inside line is a bevel or a miter
-  \pgfmathparse{\bevelshorten < abs(sin(.5*\pgf at nfold@deltaphi))}
+  \pgfmathsetmacro{\bevelshorten}{2*\insidepercentage*abs(tan(.25*\pgf at nfold@deltaphi at start))}
+  % This threshold decides if the inside line has a bevel or a miter join
+  \pgfmathparse{\bevelshorten < abs(sin(.5*\pgf at nfold@deltaphi at start))}
   \ifnum\pgfmathresult=1\relax
-    \pgfmathsetmacro{\bevelextension}{\bevelouterprotrusion-\bevelshorten*\pgf at decoration@nfold at hwidth}
-    \pgfpointadd{\pgf at nfold@offset at start}{\pgfpointpolar{\pgf at nfold@cached at endangle}{\bevelextension}}
+    \pgfmathsetlengthmacro{\bevelextension}{\bevelouterprotrusion-\bevelshorten*\pgf at nfold@hwidth}
+    \pgfpointadd{\pgf at nfold@join at start}{\pgfpointpolar{\pgf at nfold@previous at endangle}{\bevelextension}}
     \pgfpathlineto{}
-    \pgfpointadd{\pgf at nfold@offset at end}{\pgfpointpolar{\pgfdecoratedinputsegmentstartangle}{-\bevelextension}}
+    \pgfpointadd{\pgf at nfold@join at end}{\pgfpointpolar{\pgf at nfold@cur at startangle}{-\bevelextension}}
     \pgfpathlineto{}
   \else
     \pgf at nfold@miterjoin
@@ -129,19 +130,19 @@
 \def\pgf at nfold@roundjoin{
   % The outer half of the lines get arcs, the others get miters
   \ifdim\insidepercentage pt<.5pt\relax
-    \pgfpointadd{\pgf at nfold@offset at start}{\pgfpointpolar{\pgf at nfold@cached at endangle}{\pgf at nfold@shortenforjoin}}
+    \pgfpointadd{\pgf at nfold@join at start}{\pgfpointpolar{\pgf at nfold@previous at endangle}{\pgf at 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 at nfold@cached at endangle)}
+    \pgfmathparse{\turnindicator*(\pgf at nfold@cur at startangle-\pgf at nfold@previous at endangle)}
     \ifdim\pgfmathresult pt>0pt\relax
-      \pgfmathsetmacro{\targetang}{\pgfdecoratedinputsegmentstartangle-\turnindicator*360}
+      \pgfmathsetmacro{\targetang}{\pgf at nfold@cur at startangle-\turnindicator*360}
     \else
-      \let\targetang\pgfdecoratedinputsegmentstartangle
+      \let\targetang\pgf at nfold@cur at startangle
     \fi
     \pgfpatharc%
-      {\pgf at nfold@cached at endangle+90*\turnindicator}%
+      {\pgf at nfold@previous at endangle+90*\turnindicator}%
       {\targetang+90*\turnindicator}%
-      {abs(\pgf at shiftdec@fraction)*\pgf at decoration@nfold at hwidth}%
+      {abs(\pgf at nfold@shift at fraction)*\pgf at nfold@hwidth}%
   \else
     \pgf at nfold@miterjoin
   \fi
@@ -148,30 +149,44 @@
 }
 
 \def\pgf at nfold@make at join{
-  % The code must be invariant under deltaphi -> deltaphi + 360, which can be verified experimentally
-  \pgfmathsetmacro{\pgf at nfold@deltaphi}{\pgfdecoratedinputsegmentstartangle-\pgf at nfold@cached at endangle}
-  % Offset the start and end of this segment
-  \pgfpointadd{\pgf at nfold@cached at endpoint}{\pgfpointpolar{\pgf at nfold@cached at endangle+90}{\pgf at shiftdec@amount}}
-  \pgfstorepoint\pgf at nfold@offset at start
-  \pgfpointadd{\pgf at nfold@segment at start}{\pgfpointpolar{\pgfdecoratedinputsegmentstartangle+90}{\pgf at shiftdec@amount}}
-  \pgfstorepoint\pgf at nfold@offset at end
-  \pgfpointdiff{\pgf at nfold@offset at start}{\pgf at nfold@offset at 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 at x) + abs(\pgf at y)}
-  \ifdim\pgfmathresult pt>0.1pt\relax
-    % First step: Check if left or right turn (-1 = left, 1=right)
-    \pgfmathsetmacro{\turnindicator}{sign(sin(\pgf at nfold@cached at endangle-\pgfdecoratedinputsegmentstartangle)}
-    % between 0 and 1; 0=no distance to cover in the join, 1=maximum distance to cover
-    \pgfmathsetmacro{\insidepercentage}{-.5*\turnindicator*\pgf at shiftdec@fraction+.5}
-    \if m\pgf at cached@linejoin % because both are only one character, \ifx is not needed
+  % Offset the start and end of this join
+  \pgfextract at process\pgf at nfold@join at start{%
+    \pgfpointadd{\pgf at nfold@previous at joinend}
+                {\pgfpointpolar{\pgf at nfold@previous at endangle+90}{\pgf at nfold@shiftamount}}}%
+  \pgfextract at process\pgf at nfold@join at end{%
+    \pgfpointadd{\pgf at nfold@segment at start}
+                {\pgfpointpolar{\pgf at nfold@cur at startangle+90}{\pgf at nfold@shiftamount}}}%
+  \pgf at process{\pgfpointdiff{\pgf at nfold@join at start}{\pgf at nfold@join at 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 at xa
+  \ifdim\pgf at xa>0.1pt\relax
+    % First step: Check if left or right turn (-1=left, 1=right)
+    \ifdim\pgf at nfold@deltaphi at 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 at xa=\pgf at nfold@shift at fraction pt\relax
+    \pgf at xa=\turnindicator\pgf at xa
+    \advance\pgf at xa by-1pt\relax
+    \pgf at xa=-.5\pgf at xa
+    % \insidepercentage  = .5 * (1 - \turnindicator*\pgf at nfold@shift at fraction)
+    \edef\insidepercentage{\pgf at sys@tonumber\pgf at xa}
+    \if m\pgf at 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 at nfold@deltaphi))}
-      \pgfmathparse{\mitercosine*\pgf at nfold@cached at miterlimit}
-      \ifdim\pgfmathresult pt>1pt\relax
+      \pgf at xa=\pgf at nfold@deltaphi at start pt\relax
+      \pgf at xa=.5\pgf at xa
+      \pgfmathcos@{\pgf at sys@tonumber\pgf at xa}
+      \pgf at xa=\pgfmathresult pt\relax
+      \pgf at xa=\pgf at nfold@cached at miterlimit\pgf at xa
+      % Check if miterlimit*cos(.5*deltaphi) > 1;
+      % cos(.5*deltaphi) >= 0 because -180 <= deltaphi <= 180
+      \ifdim\pgf at xa>1pt\relax
         \pgf at nfold@miterjoin
       \else
         \pgf at 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 at nfold@offset at 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 at nfold@segment at end
+    % and \pgf at nfold@segment at start exchange places *and* we are on the outside of the join 
+    % (implying that we are on the inside of the next join), \pgf at nfold@join at end will be located behind the next join.
+    % In this case we instead connect to the offset of \pgf at nfold@segment at end (which comes *before* \pgf at nfold@segment at start).
+    \pgf at nfold@join at end% default point to connect to
+    \ifpgf at nfold@closejoinsedgecase%
+      \ifdim\insidepercentage pt<.5pt\relax%
+        \pgf at process{\pgfpointadd%
+          {\pgf at nfold@segment at end}%
+          {\pgfpointpolar{\pgf at nfold@cur at startangle+90}{\pgf at nfold@shiftamount}}}
+      \fi%
+    \fi
+    \pgfpathlineto{}
   \fi
 }
 
 
 %
-% Internal offset decoration
-% --------------------------
+% Main rendering pipeline
+% -----------------------
 %
-% This internal tikz decoration offsets a given path (but doesn't draw it multiple times).
-%
 
-% pgfkeys interface, used in the decorations below
-\newcount\pgf at nfold@dec at order
-\pgf at nfold@dec at 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 at nfold@dec at order=#1
-    \ifnum\pgf at nfold@dec at order>\tikz at arrow@intersec at numcached
-      \ifdefined\pgfintersectionofpaths\else
-        \pgferror{For 'nfold' larger than \tikz at arrow@intersec at numcached\space you need to say \string\usetikzlibrary{intersections}}
-      \fi
-    \fi
-  },
-  /pgf/decoration/nfold width/.code={\pgfmathsetlengthmacro{\pgf at decoration@nfold at hwidth}{.5*#1}},
-  % integer between 1 and \pgf at nfold@dec at order
-  /pgf/decoration/nfold index/.store in=\pgf at nfold@index
-}
-
-% In here we cache the end angle of the previous decorated segment, which is otherwise inaccessible
-\newdimen\pgf at nfold@cached at endangle
 % This stores whether the current segment should begin with a moveto to its offset
 \newif\ifpgf at nfold@continuesegment
+% This stores whether we are in some edge case of very close joins, see below for details
+\newif\ifpgf at nfold@closejoinsedgecase
+% This stores whether we are in an error case where we need to avoid dividing by zero
+\newif\ifpgf at nfold@angletoosharp
+% This stores whether we need the intersections library for an arrow tip but it is not loaded
+\newif\ifpgf at nfold@intersectionsnotloaded
 
-% some required computations for the current segment
-\def\pgf at nfold@shift at 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 at shiftdec@fraction}%
-    {-1+2*(\pgf at nfold@index-1)/(\pgf at nfold@dec at order-1)}
-  \pgfmathsetlengthmacro{\pgf at shiftdec@amount}{\pgf at decoration@nfold at hwidth*\pgf at shiftdec@fraction}
+
+\def\pgf at nfold@handlesegment{%
+  %%% Step 0: Preparation
   % Set some default values; they might get overwritten later
-  \let\pgf at nfold@segment at start\pgf at decorate@inputsegment at first
-  \let\pgf at nfold@segment at end\pgf at decorate@inputsegment at last
+  \let\pgf at nfold@segment at start\pgf at nfold@inputsegment at first
+  \let\pgf at nfold@segment at end\pgf at nfold@inputsegment at last
   % Set a default value for \ifpgf at nfold@continuesegment.
   % It may be overwritten by \pgf at nfold@extendtotip
-  \ifx\pgfdecorationpreviousinputsegment\pgfdecorationinputsegmentmoveto
+  \ifx\pgf at nfold@previousinputsegment\pgf at nfold@inputsegmentmoveto
     \pgf at nfold@continuesegmentfalse
   \else
     \pgf at nfold@continuesegmenttrue
   \fi
-}
-
-% draw the current segment including a join at the start (if present)
-\def\pgf at nfold@shift at 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 at nfold@shortenforjoin{0pt}
+  \def\pgf at nfold@shortenstartjoin{0pt}
+  \def\pgf at nfold@shortenendjoin{0pt}
+  \pgf at nfold@closejoinsedgecasefalse
+  \pgf at 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 at nfold@currentinputsegment
+  \ifpgf at nfold@segm at visible%
+    % Step 1.1: Make space for the join at the start if needed
+    \ifx\pgf at nfold@previousinputsegment\pgf at nfold@inputsegmentmoveto\else
+      \pgf at xb=\pgf at nfold@deltaphi at start pt\relax
+      \ifdim\pgf at xb<0pt\relax
+        \pgf at xb=-\pgf at xb
       \fi
-      \pgf at xa=\pgfdecoratedinputsegmentstartangle pt\relax
-      \advance\pgf at xa by-\pgf at nfold@cached at endangle\relax
-      \pgfmathparse{abs(cos(0.5*\pgf at 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 at xb>178pt\relax
+        % we go full backwards, don't relocate the start and disable the join to avoid division by zero
+        \pgfutil at packagewarning{tikz-nfold}{Angle too sharp, expect visual errors}
+        \pgf at nfold@angletoosharptrue
       \else
-        \pgfmathsetlengthmacro{\pgf at nfold@shortenforjoin}{\pgf at decoration@nfold at hwidth*abs(tan(0.5*\pgf at xa))}
-        \pgfpointadd{\pgf at decorate@inputsegment at first}{\pgfpointpolar{\pgfdecoratedinputsegmentstartangle}{\pgf at nfold@shortenforjoin}}
-        \pgfstorepoint\pgf at nfold@segment at start
+        \ifdim\pgf at xb>0.5pt\relax
+           % make space for the start join if the angle is nonzero
+          \pgf at yb=.5\pgf at xb
+          \pgfmathtan@{\pgf at sys@tonumber\pgf at yb}
+          \pgf at yb=\pgf at nfold@hwidth\relax
+          \pgf at yb=\pgfmathresult\pgf at yb
+          % shortenstartjoin = hwidth*tan(.5*abs(deltaphi at start))
+          \edef\pgf at nfold@shortenstartjoin{\the\pgf at yb}
+          \pgfextract at process\pgf at nfold@segment at start{%
+            \pgfpointadd{\pgf at nfold@inputsegment at first}%
+                        {\pgfqpointpolar{\pgf at nfold@cur at startangle}{\pgf at yb}}}%
+          \ifx\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentcurveto
+            % For a curve we need to relocate the @supporta point as well, as otherwise the @first point
+            % might overtake it
+            \pgf at process{\pgfpointdiff{\pgf at nfold@inputsegment at first}{\pgf at nfold@inputsegment at supporta}}
+            \pgfmathveclen@{\pgf at sys@tonumber\pgf at x}{\pgf at sys@tonumber\pgf at 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 at sys@tonumber\pgf at yb}
+              \pgfextract at process\pgf at nfold@inputsegment at supporta{\pgfpointadd%
+                {\pgf at nfold@inputsegment at first}%
+                {\pgfqpointpolar{\pgf at nfold@cur at 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 at nfold@inputsegment at supporta\pgf at nfold@segment at 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 at xa=\pgfdecoratedangle pt\relax
-      \advance\pgf at xa by \pgfdecoratedangletonextinputsegment\relax
-      \advance\pgf at xa by -\pgfdecoratedinputsegmentendangle pt\relax
-      \pgfmathparse{abs(cos(0.5*\pgf at 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 at nfold@next at segmenttype
+    \ifpgf at nfold@segm at visible%
+      \pgf at xb=\pgf at nfold@deltaphi at end pt\relax
+      \ifdim\pgf at xb<0pt\relax
+        \pgf at xb=-\pgf at xb
+      \fi
+      \ifdim\pgf at xb>178pt\relax
+        \pgfutil at packagewarning{tikz-nfold}{Angle too sharp, expect visual errors}
+        \pgf at nfold@angletoosharptrue
       \else
-        \pgfmathparse{-\pgf at decoration@nfold at hwidth*abs(tan(0.5*\pgf at xa))}
-        \pgfpointadd{\pgf at decorate@inputsegment at last}{\pgfpointpolar{\pgfdecoratedinputsegmentendangle}{\pgfmathresult pt}}
-        \pgfstorepoint\pgf at nfold@segment at end
+        \ifdim\pgf at xb>0.5pt\relax
+           % make space for the start join if the angle is nonzero
+          \pgf at yb=.5\pgf at xb
+          \pgfmathtan@{\pgf at sys@tonumber\pgf at yb}
+          \pgf at yb=\pgf at nfold@hwidth\relax
+          \pgf at yb=\pgfmathresult\pgf at yb
+          % shortenendjoin = hwidth*tan(.5*abs(deltaphi at end))
+          \edef\pgf at nfold@shortenendjoin{\the\pgf at yb}
+          \pgf at yb=-\pgf at yb
+          \pgfextract at process\pgf at nfold@segment at end{%
+            \pgfpointadd{\pgf at nfold@inputsegment at last}%
+                        {\pgfqpointpolar{\pgf at nfold@cur at endangle}{\pgf at yb}}}%
+          \ifx\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentcurveto
+            % Same procedure as above: relocate supportb if we have a curve
+            \pgf at process{\pgfpointdiff{\pgf at nfold@inputsegment at supportb}{\pgf at nfold@inputsegment at last}}
+            \pgfmathveclen@{\pgf at sys@tonumber\pgf at x}{\pgf at sys@tonumber\pgf at y}
+            \ifdim\pgfmathresult pt>0.1pt\relax
+              \pgfmathveclen@{\pgfmathresult}{\pgf at sys@tonumber\pgf at yb}
+              % can use qpoint and minus because \pgfmathresult is guaranteed to be positive
+              \pgfextract at process\pgf at nfold@inputsegment at supportb{\pgfpointadd%
+                {\pgf at nfold@inputsegment at last}%
+                {\pgfqpointpolar{\pgf at nfold@cur at endangle}{-\pgfmathresult pt}}}%
+            \else
+              \let\pgf at nfold@inputsegment at supportb\pgf at nfold@segment at 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 at nfold@currentinputsegment\pgf at nfold@inputsegmentlineto
+        % No need to check for \pgf at 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 at nfold@inputsegment at first}{\pgf at nfold@inputsegment at last}
+        \pgfmathveclen@{\pgf at sys@tonumber\pgf at x}{\pgf at sys@tonumber\pgf at y}
+        \pgf at xa=\pgf at nfold@shortenstartjoin\relax
+        \advance\pgf at xa by\pgf at nfold@shortenendjoin\relax
+        \ifdim\pgf at xa>\pgfmathresult pt\relax
+          \pgf at 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 at nfold@make at join
+    \ifx\pgf at nfold@previousinputsegment\pgf at nfold@inputsegmentmoveto
+      \ifpgf at 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 at nfold@segment at start since the start and end
+        % are reversed in the edge case.
+        \pgfpointadd%
+          {\pgf at nfold@segment at start}%
+          {\pgfpointpolar{\pgf at nfold@cur at startangle+90}{\pgf at 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 at nfold@angletoosharp\else
+        \pgf at nfold@make at 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 at nfold@previous at joinend\pgf at nfold@segment at end
   %
-  % Step 4: Draw the new segment.
+  % Step 3: Draw the new segment.
   %
   % The value of \ifpgf at nfold@continuesegment decides whether we start with a moveto.
-  \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentlineto
-    \ifpgf at nfold@continuesegment
-      \pgfoffsetlinenomove{\pgf at nfold@segment at start}{\pgf at nfold@segment at end}{\pgf at shiftdec@amount}
-    \else
-      \pgfoffsetline{\pgf at nfold@segment at start}{\pgf at nfold@segment at end}{\pgf at shiftdec@amount}
+  \ifx\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentlineto
+    \ifpgf at nfold@closejoinsedgecase\else
+      \ifpgf at nfold@continuesegment
+        \pgfoffsetlinenomove{\pgf at nfold@segment at start}{\pgf at nfold@segment at end}{\pgf at nfold@shiftamount}
+      \else
+        \pgfoffsetline{\pgf at nfold@segment at start}{\pgf at nfold@segment at end}{\pgf at nfold@shiftamount}
+      \fi
     \fi
   \fi
-  \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentclosepath
-    \pgfwarning{'nfold': The option 'cycle' is not yet properly supported.}
+  \ifx\pgf at nfold@currentinputsegment\pgf at 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 at packagewarning{tikz-nfold}{The option `cycle' is not yet properly supported}
+    \pgfpathclose
+  \fi
+  \ifx\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentcurveto
     \ifpgf at nfold@continuesegment
-      \pgfoffsetlinenomove{\pgf at nfold@segment at start}{\pgf at nfold@segment at end}{\pgf at shiftdec@amount}
+      \pgfoffsetcurvenomove{\pgf at nfold@segment at start}{\pgf at nfold@inputsegment at supporta}{\pgf at nfold@inputsegment at supportb}{\pgf at nfold@segment at end}{\pgf at nfold@shiftamount}
     \else
-      \pgfoffsetline{\pgf at nfold@segment at start}{\pgf at nfold@segment at end}{\pgf at shiftdec@amount}
+      \pgfoffsetcurve{\pgf at nfold@segment at start}{\pgf at nfold@inputsegment at supporta}{\pgf at nfold@inputsegment at supportb}{\pgf at nfold@segment at end}{\pgf at nfold@shiftamount}
     \fi
   \fi
-  \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentcurveto
-    \ifpgf at nfold@continuesegment
-      \pgfoffsetcurvenomove{\pgf at nfold@segment at start}{\pgf at decorate@inputsegment at supporta}{\pgf at decorate@inputsegment at supportb}{\pgf at nfold@segment at end}{\pgf at shiftdec@amount}
-    \else
-      \pgfoffsetcurve{\pgf at nfold@segment at start}{\pgf at decorate@inputsegment at supporta}{\pgf at decorate@inputsegment at supportb}{\pgf at nfold@segment at end}{\pgf at shiftdec@amount}
+  \ifx\pgf at nfold@currentinputsegment\pgf at 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 at nfold@previousinputsegment\pgf at nfold@inputsegmentfirst
+      % Draw the tip extension at the start (if present)
+      \ifnum\pgf at nfold@start at arrowcode=1
+        \pgf at nfold@extendtotip{s}%
+      \fi%
     \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 at nfold@cached at endangle=\pgfdecoratedinputsegmentendangle pt\relax
-  \ifx\pgfdecorationnextinputsegmentobject\pgfdecorationinputsegmentmoveto\else
-    \global\let\pgf at nfold@cached at endpoint\pgf at nfold@segment at end
-  \fi
-  % Step 5: Extend into the arrow tip at the end (if present)
-  \ifx\pgfdecorationnextinputsegmentobject\pgfdecorationinputsegmentlast%
-    \pgf at nfold@check at tip@end%
-    \ifpgf at nfold@tip at implies%
+  % Step 4: Extend into the arrow tip at the end (if present)
+  \ifx\pgf at nfold@next at segmenttype\pgf at nfold@inputsegmentlast%
+    \ifnum\pgf at nfold@end at arrowcode=1
       \pgf at nfold@extendtotip{e}%
     \fi%
   \fi%
 }
 
-\pgfdeclaredecoration{pgf at nfold@shift}{start}{%
-  \state{start}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{%
-    \egroup\begingroup
-    \pgf at nfold@shift at prepare@segment
-    \pgf at nfold@check at tip@start%
-    \ifpgf at nfold@tip at implies%
-      \pgf at nfold@extendtotip{s}%
-    \fi
-    \pgf at nfold@shift at handle@segment
-    \endgroup\bgroup%
-  }
-  \state{segment}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{%
-    \egroup\begingroup
-    \pgf at nfold@shift at prepare@segment
-    \pgf at nfold@shift at handle@segment
-    \endgroup\bgroup%
-  }%
-}%
 
+% Rendering arrow tips
+% --------------------
 
-
-% Internal pre-pass decoration
-% ----------------------------
+% Precomputed intersections
 %
-% 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.
-%
+% 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 at nfold@intersec at cache@2 at 3\endcsname{\pgfqpoint{2pt}{0pt}}
+\expandafter\def\csname pgf at nfold@intersec at cache@2 at 4\endcsname{\pgfqpoint{0.94063pt}{-0.33333pt}}
+\expandafter\def\csname pgf at nfold@intersec at cache@3 at 4\endcsname{\pgfqpoint{0.94063pt}{0.33333pt}}
+\expandafter\def\csname pgf at nfold@intersec at cache@2 at 5\endcsname{\pgfqpoint{0.64167pt}{-0.5pt}}
+\expandafter\def\csname pgf at nfold@intersec at cache@3 at 5\endcsname{\pgfqpoint{2pt}{0pt}}
+\expandafter\def\csname pgf at nfold@intersec at cache@4 at 5\endcsname{\pgfqpoint{0.64167pt}{0.5pt}}
+% intersections are precomputed up to this order
+\def\pgf at nfold@intersec at numcached{5}
 
-\def\pgf at nfold@storedata at handle@segment{%
-  \let\pgf at nfold@segment at end\pgf at decorate@inputsegment at last%
-  \ifx\pgfdecorationnextinputsegmentobject\pgfdecorationinputsegmentlast%
-    % Implementing shorten > and making space for the arrow tip (if present)
-    \pgf at nfold@check at tip@end%
-    \ifpgf at nfold@tip at implies%
-      \pgfmathparse{-\pgf at shorten@end at additional-2.06*\pgf at decoration@nfold at hwidth-0.5*\pgflinewidth}
+% This macro extends the arrow body to the tips
+% parameter: s=start, e=end
+\def\pgf at nfold@extendtotip#1{
+  \ifpgf at nfold@intersectionsnotloaded
+    \pgfutil at packageerror{tikz-nfold}{%
+        If `nfold' is larger than \pgf at nfold@intersec at 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 at nfold@index>1\relax\ifnum\pgf at nfold@index<\pgf at nfold@order\relax%
+      % Step 1: Find the intersection of the arrow's path with the head
+      \ifcsname pgf at nfold@intersec at cache@\the\pgf at nfold@index @\the\pgf at nfold@order\endcsname
+        \pgfextract at process\pgf at nfold@arrowintersect
+          {\csname pgf at nfold@intersec at cache@\the\pgf at nfold@index @\the\pgf at 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 at nfold@shift at fraction pt}}
+          \pgfpathlineto{\pgfqpoint{3pt}{\pgf at nfold@shift at fraction pt}}
+        }
+        \ifnum\pgfintersectionsolutions>0
+          \pgfextract at process\pgf at nfold@arrowintersect{\pgfpointintersectionsolution{1}}%
+          \immediate\write17{tikz-nfold: computed intersection cache@\the\pgf at nfold@index @\the\pgf at nfold@order: \string\pgfqpoint{\the\pgf at x}{\the\pgf at y}^^J}
+          % add the new intersection to the cache
+          \expandafter\xdef\csname pgf at nfold@intersec at cache@\the\pgf at nfold@index @\the\pgf at nfold@order\endcsname{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}
+        \else
+          % this is a failsafe and should never be reached
+          \pgfutil at packagewarning{tikz-nfold}{did not find intersection}
+          \pgfextract at process\pgf at nfold@arrowintersect{\pgfqpoint{0pt}{\pgf at nfold@shift at 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 at nfold@segment at start}
+        \pgftransformrotate{\pgf at nfold@next at startangle}
+        \pgftransformxscale{-1}
+      \else
+        \pgftransformshift{\pgf at nfold@segment at end}
+        \pgftransformrotate{\pgf at nfold@cur at endangle}
+      \fi
+      % we don't want to undo the shift by .42\pgflinewidth after the scaling
+      \pgfutil at tempdima=\pgf at nfold@hwidth
+      \pgfutil at tempdima=\pgf at nfold@shift at fraction\pgfutil at tempdima
+      \pgfextract at process\pgf at nfold@startofextension
+        {\pgfpointtransformed{\pgfqpoint{0pt}{\pgfutil at tempdima}}}
+      % 0.5 - 0.06 = 0.42
+      \pgftransformshift{\pgfqpoint{.42\pgflinewidth}{0pt}}
+      \pgftransformscale{\pgf at nfold@hwidth}
+      \pgfextract at process\pgf at nfold@arrowintersect{\pgfpointtransformed{\pgf at nfold@arrowintersect}}
+      \global\let\pgf at nfold@startofextension\pgf at nfold@startofextension
+      \global\let\pgf at nfold@arrowintersect\pgf at nfold@arrowintersect
+    \endgroup
+    \if#1s
+      \pgfpathmoveto{\pgf at nfold@arrowintersect}
+      % This is precisely the start of the body, shifted vertically
+      \pgfpathlineto{\pgf at nfold@startofextension}
+      % hack: We make the next segment believe that this segment was a lineto
+      % so the path does not get interrupted
+      \let\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentlineto
+      \let\pgf at nfold@cur at endangle\pgf at nfold@next at startangle
+    \else\if#1e
+      \pgfpathlineto{\pgf at nfold@arrowintersect}
     \else
-      \pgfmathparse{-\pgf at shorten@end at additional}
-    \fi
-    % This is the point where the arrow body ends
-    \pgfpointadd%
-      {\pgf at decorate@inputsegment at last}%
-      {\pgfpointpolar{\pgfdecoratedinputsegmentendangle}{\pgfmathresult pt}}%
-    \pgfstorepoint\pgf at nfold@segment at end%
-    % This is the tip of the arrow, required for drawing the arrow head
-    \pgfpointadd%
-      {\pgf at decorate@inputsegment at last}%
-      {\pgfpointpolar%
-        {\pgfdecoratedinputsegmentendangle}%
-        {-\pgf at shorten@end at additional}}%
-    \pgfglobalstorepoint\pgf at nfold@original at last%
-  \fi%
-  \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentmoveto%
-    \pgfpathmoveto{\pgf at nfold@segment at end}%
-  \fi%
-  \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentlineto%
-    \pgfpathlineto{\pgf at nfold@segment at end}%
-  \fi%
-  \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentclosepath%
-    \pgfpathlineto{\pgf at nfold@segment at end}%
-  \fi%
-  \ifx\pgfdecorationcurrentinputsegment\pgfdecorationinputsegmentcurveto%
-    \pgfpathcurveto%
-      {\pgf at decorate@inputsegment at supporta}%
-      {\pgf at decorate@inputsegment at supportb}%
-      {\pgf at nfold@segment at end}%
-  \fi%
+      \pgfutil at packageerror{tikz-nfold}{Invalid argument to \string\pgf at nfold@extendtotip: \meaning#1}{}
+    \fi\fi
+    \pgftransformreset
+    \fi\fi% if 1 < i < nArrows
+  \fi% if intersections is needed and not loaded
 }
 
-\pgfdeclaredecoration{pgf at nfold@storedata}{start}{%
-  \state{start}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{%
-    \egroup\begingroup
-    \pgftransformreset
-    \iftikz at mode@double\else
-      \pgferror{Must enable /tikz/double to use 'nfold' (e.g. using [double distance=5pt]).}
-    \fi
-    % Cache arrow settings
-    \pgf at nfold@storearrows
-    % Cache inner and outer line width from the '/tikz/double' settings. These are stored in
-    % the macro \tikz at double@setup. Internally, the value of \pgfinnerlinewidth determines whether 
-    % double stroke is enabled or not. Calling \tikz at 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 at double@setup and then restore \pgflinewidth.
-    \tikz at double@setup
-    \pgfmathsetlengthmacro{\pgf at nfold@temp at fullwidth}{.25*(\pgflinewidth+\pgfinnerlinewidth)}
-    \global\edef\pgf at decoration@nfold at hwidth{\pgf at nfold@temp at fullwidth}
-    % We can either store \pgflinewidth before calling \tikz at 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 at decorate@inputsegment at first}%
-      {\pgfpointpolar{\pgfdecoratedinputsegmentstartangle}{\pgf at shorten@start at additional}}
-    % store the tip of the arrow
-    \pgfglobalstorepoint\pgf at nfold@original at first%
-    % make space if applicable
-    \pgf at nfold@check at tip@start%
-    \ifpgf at nfold@tip at implies%
-      \pgfmathparse{\pgf at shorten@start at additional+2.06*\pgf at decoration@nfold at hwidth+0.5*\pgflinewidth}
+% 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 at start@tip at sequence. If the user specifies Implies[] manually,
+% we find that
+%   pgf at start@tip at sequence=\pgf at arrow@handle{Implies}{...}
+% However, in other cases (like tikz-cd) we may find 
+%   \pgf at arrow@handle at shorthand@empty {\csname pgf at ar@means at tikzcd implies cap\endcsname }
+% In such cases we must expand the first parameter once and then match as above.
+
+% Set global defaults
+\def\pgf at nfold@start at arrowcode{0}
+\def\pgf at nfold@end at arrowcode{0}
+
+\def\pgf at nfold@parsearrows{
+  \ifpgfutil at tempswa% this is set in \pgfusepath and stores whether we draw arrow tips at all
+    \expandafter\pgf at nfold@parsearrowmacro\pgf at start@tip at sequence\relax
+    \let\pgf at nfold@start at arrowcode\pgf at nfold@detectedarrow
+    \expandafter\pgf at nfold@parsearrowmacro\pgf at end@tip at sequence\relax
+    \let\pgf at nfold@end at arrowcode\pgf at nfold@detectedarrow
+  \else
+    \def\pgf at nfold@start at arrowcode{0}
+    \def\pgf at nfold@end at arrowcode{0}
+  \fi
+}
+
+\def\pgf at nfold@parsearrowmacro#1{%
+  \def\pgf at nfold@detectedarrow{0}
+  \ifx#1\relax
+    \let\pgf at next\relax
+  \else
+    \ifx#1\pgf at arrow@handle
+      % found \pgf at arrow@handle{...}, now parse the first parameter
+      \let\pgf at next\pgf at nfold@parse at arrow@handle
     \else
-      \pgfmathparse{\pgf at shorten@start at additional}
+      \ifx#1\pgf at arrow@handle at shorthand@empty
+        \let\pgf at next\pgf at nfold@parse at shorthandempty
+      \else
+        % found nothing
+        \let\pgf at next\pgfutil at gobble@until at relax
+      \fi
     \fi
-    \pgfpointadd%
-      {\pgf at decorate@inputsegment at first}%
-      {\pgfpointpolar{\pgfdecoratedinputsegmentstartangle}{\pgfmathresult pt}}
-    %
-    \pgfpathmoveto{}
-    \pgf at nfold@storedata at handle@segment
-    \endgroup\bgroup
-  }
-  \state{segment}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{%
-    \egroup\begingroup
-    \pgftransformreset
-    \pgf at nfold@storedata at handle@segment
-    \endgroup\bgroup
-  }%
-  \state{final}{}
-}%
+  \fi
+  \pgf at next
+}
 
+\def\pgf at nfold@param at Implies{Implies}
 
-% Detecting arrow tips
-% --------------------
+\def\pgf at nfold@parse at arrow@handle#1{%
+  \def\pgf at tmp{#1}
+  \ifx\pgf at tmp\pgf at nfold@param at Implies
+    \def\pgf at nfold@detectedarrow{1}
+  \fi
+  \pgfutil at gobble@until at relax
+}
 
-% Old arrow matching
+\def\pgf at nfold@parse at shorthandempty#1{
+  % Expand #1 once (\pgf at arrow@handle at shorthand@empty is just an identity operator)
+  \expandafter\def\expandafter\pgf at tmp\expandafter{#1}
+  \expandafter\pgf at nfold@parsearrowmacro\pgf at tmp\relax
+  % still need to gobble the rest of the orginal arrow definition
+  \pgfutil at gobble@until at relax
+}
+
+
 %
-% This is the content of \pgf at arrow@tip at sequence for arrows | and Implies, respectively
+% Hooking into pgf's rendering pipeline
+% -------------------------------------
 %
-%\edef\pgf at arrow@macro at verticalline{\noexpand\pgf at arrow@handle at shorthand@empty {\expandafter\noexpand\csname pgf at ar@means@|\endcsname }}
-%\def\pgf at arrow@macro at Implies{\pgf at arrow@handle {Implies}{}}
-% The next one is wrong; use \csname pgf at ar@means at tikzcd implies\endcsname
-%\def\tikzcd at arrow@implies{\pgf at arrow@handle at shorthand@empty {\pgf at ar@means at tikzcd implies }}
+% The new code has to be injected into \pgfusepath (pgfcorepathusage.code.tex). For rendering the new paths,
+% \pgf at stroke@inner at 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 at stroke@inner at line. The macro before \pgf at stroke@inner at line
+% is either \pgf at path@check at proper or \pgf at prepare@start at 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 at invokecurrentpath has no effect), then we
+% restore and offset the cached softpath in \pgf at stroke@inner at line.
+%
+% The macros \pgf at path@check at proper and \pgf at prepare@start at of@path are also used in \pgf at up@draw at 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 at add@arrow at at@start and
+% \pgf at add@arrow at at@end, which do not change their behaviour even if we modify the paths.
+%
 
-% Intercept the arrows at definition
-\let\pgf at nfold@cachedarrows\pgfutil at empty % default value
-\let\pgf at nfold@oldsetarrows\pgfsetarrows
-\def\pgfsetarrows#1{%
-  \def\pgf at nfold@cachedarrows{#1}%
-  \pgf at nfold@oldsetarrows{#1}%
+\newcount\pgf at nfold@order
+\pgf at nfold@order=1
+
+\def\pgf at nfold@preparenfoldpath{%
+  \ifnum\pgf at nfold@order>1\relax
+    \ifdim\pgfinnerlinewidth>0pt\relax
+      % Hack the rendering pipeline: There is a \pgfsyssoftpath at 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 at stroke@inner at line
+      \pgfsyssoftpath at getcurrentpath\cachedpath%
+      \pgfsyssoftpath at setcurrentpath\pgfutil at empty%
+    \else
+      \pgfutil at packageerror{tikz-nfold}{Must set \string\pgfinnerlinewidth\space to use nfold, e.g. by setting /tikz/double distance}{}
+    \fi
+  \fi
 }
 
-\let\pgf at nfold@cached at arrow@start\pgfutil at empty
-\let\pgf at nfold@cached at arrow@end\pgfutil at empty
-\def\pgf at nfold@storearrows{%
-  \ifx\pgf at nfold@cachedarrows\pgfutil at 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 at nfold@cached at arrow@start\pgfutil at empty
-    \global\let\pgf at nfold@cached at arrow@end\pgfutil at empty
-  \else%
-    \expandafter\pgf at nfold@parsearrows@\pgf at nfold@cachedarrows\pgf at stop%
+\let\pgf at nfold@old at path@check at proper\pgf at path@check at proper
+\def\pgf at path@check at proper{%
+  \pgf at nfold@old at path@check at proper%
+  \ifpgfutil at tempswa\else%
+    % if \pgfutil at tempswa is false, this is the last macro we can overwrite before the draw call.
+    % Otherwise, we inject into \pgf at prepare@start at of@path%
+    \pgf at nfold@preparenfoldpath%
   \fi%
 }
-\def\pgf at nfold@parsearrows@#1-#2\pgf at stop{%
-  % These must be set globally because \pgf at nfold@storearrows is called from within a decoration;
-  % also, this cached value must survive subsequent calls of \pgfsetarrows{} which disable arrows
-  \gdef\pgf at nfold@cached at arrow@start{#1}%
-  \gdef\pgf at nfold@cached at 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 at nfold@nonetip at i{tikzcd implies cap}
-\def\pgf at nfold@impliestip at i{Implies}
-\def\pgf at nfold@impliestip at ii{tikzcd implies}
-\def\pgf at nfold@mapstotip at i{|}
-\def\pgf at nfold@mapstotip at ii{tikzcd implies bar}
-\def\pgf at nfold@mapstotip at iii{Bar}
-\newif\ifpgf at nfold@tip at implies
-\newif\ifpgf at nfold@tip at mapsto
-
-\def\pgf at nfold@check at tip@start{%
-\pgf at nfold@check at tip{\pgf at nfold@cached at arrow@start}%
+\let\pgf at nfold@old at prepare@start at of@path\pgf at prepare@start at of@path
+\def\pgf at prepare@start at of@path{%
+  \pgf at nfold@old at prepare@start at of@path%
+  \pgf at nfold@preparenfoldpath%
 }
-\def\pgf at nfold@check at tip@end{%
-\pgf at nfold@check at tip{\pgf at nfold@cached at arrow@end}%
-}
 
-\def\pgf at nfold@check at tip#1{%
-  \pgf at nfold@tip at impliesfalse%
-  \pgf at nfold@tip at mapstofalse%
-  \ifx#1\pgfutil at empty%
-    % no tip
-  \else
-    \ifx#1\pgf at nfold@nonetip at i%
-      % no tip
-    \else
-      \ifx#1\pgf at nfold@impliestip at i%
-        \pgf at nfold@tip at impliestrue%
-      \else
-        \ifx#1\pgf at nfold@impliestip at ii%
-          \pgf at nfold@tip at impliestrue%
-        \else
-          \ifx#1\pgf at nfold@mapstotip at i%
-            \pgf at nfold@tip at mapstotrue%
-          \else
-            \ifx#1\pgf at nfold@mapstotip at ii%
-              \pgf at nfold@tip at mapstotrue%
-            \else
-              \ifx#1\pgf at nfold@mapstotip at iii%
-                \pgf at nfold@tip at mapstotrue%
-               \else
-                 \pgfwarning{'nfold': Unsupported arrow tip "#1"}
-              \fi
-            \fi
-          \fi
-        \fi
-      \fi
-    \fi
-  \fi
+\let\pgf at nfold@old at stroke@inner at line\pgf at stroke@inner at line
+\def\pgf at stroke@inner at line{%
+  \ifnum\pgf at nfold@order>1\relax%
+    \pgf at nfold@render at cached@softpath%
+  \else%
+    % Old behaviour
+    \pgf at nfold@old at stroke@inner at line%
+  \fi%
 }
 
-% Future: In principle, we can dereference aliases (means=...) the following way:
+
 %
-%    \ifcsname pgf at ar@means at tikzcd implies bar\endcsname
-%      \expandafter\let\expandafter\tempmacro\csname pgf at ar@means at tikzcd implies bar\endcsname
-%      \pgfwarning{\meaning\tempmacro}
-%    \fi
+% Parsing the soft path
+% ---------------------
 %
-% 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.
+% 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.
 %
-% For now this means that redefining /tikz/commutative diagrams/tikzcd implies cap will not
-% have the desired effect.
+%
 
+\def\pgf at nfold@parsesoftpath#1#2{%
+  \def\pgf at nfold@inputsegmentobjectsmacro{#2}%
+  \let\pgf at nfold@inputsegmentobjects\pgfutil at empty%
+  \pgfutil at tempdima0pt\relax%
+  \edef\pgfpoint at nfold@lastparsed{\pgf at x\the\pgf at path@lastx\pgf at y\the\pgf at path@lasty}%
+  \let\pgfpoint at nfold@lastnonmovetoparsed\pgfpoint at origin%
+  \let\pgf at nfold@queueinputsegmentobject\pgfutil at empty%
+  \let\pgfpoint at nfoldd@firstparsed\pgfutil at empty%
+  \expandafter\pgf at nfold@@parsesoftpath#1\pgf at stop%
+}%
 
-% Rendering arrow tips
-% --------------------
+\def\pgf at nfold@@parsesoftpath#1{%
+  \ifx#1\pgf at stop%
+    \let\pgf at nfold@queueinputsegmentobject\pgfutil at empty% <- removes final moveto (may not be desirable).
+    \pgf at nfold@addtoinputsegmentobjects{\pgf at nfold@inputsegmentobject at endofinputsegments}%
+		% probably so that the last segment also has a well-defined next segment
+    \pgf at nfold@addtoinputsegmentobjects{\pgf at nfold@inputsegmentobject at endofinputsegments}%
+    \expandafter\let\pgf at nfold@inputsegmentobjectsmacro\pgf at nfold@inputsegmentobjects%
+    \let\pgf at next\relax%
+  \else%
+    \ifx#1\pgfsyssoftpath at movetotoken%
+      \let\pgf at next\pgf at nfold@parsemoveto%
+    \else%
+      \ifx#1\pgfsyssoftpath at linetotoken%
+        \let\pgf at next\pgf at nfold@parselineto%
+      \else%
+        \ifx#1\pgfsyssoftpath at curvetosupportatoken%
+          \let\pgf at next\pgf at nfold@parsecurveto%
+        \else%
+          \ifx#1\pgfsyssoftpath at closepathtoken%
+            \let\pgf at next\pgf at nfold@parseclosepath%
+          \else%
+            \ifx#1\pgfsyssoftpath at rectcornertoken%
+              \let\pgf at next\pgf at nfold@parserect%
+            \else%
+              \pgfutil at packageerror{tikz-nfold}{Unrecognised soft path token `#1'}{}%
+            \fi%
+          \fi%
+        \fi%
+      \fi%
+    \fi%
+  \fi%
+  \pgf at next}%
 
-% Precomputed intersections
+\def\pgf at nfold@parsemoveto#1#2{%
+  \def\pgf at nfold@queueinputsegmentobject{\pgf at nfold@inputsegmentobject at moveto{\pgf at x#1\pgf at y#2}}%
+  \def\pgfpoint at nfold@lastparsed{\pgf at x#1\pgf at y#2}%
+  \pgf at nfold@@parsesoftpath%
+}%
+
+% Convert \pgfsyssoftpath at linetotoken{<X>}{<Y>} into the following
+% representation:
 %
-% 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 at arrow@intersec at cache@2 at 3\endcsname{\pgfqpoint{2pt}{0pt}}
-\expandafter\def\csname tikz at arrow@intersec at cache@2 at 4\endcsname{\pgfqpoint{0.94063pt}{-0.33333pt}}
-\expandafter\def\csname tikz at arrow@intersec at cache@3 at 4\endcsname{\pgfqpoint{0.94063pt}{0.33333pt}}
-\expandafter\def\csname tikz at arrow@intersec at cache@2 at 5\endcsname{\pgfqpoint{0.64167pt}{-0.5pt}}
-\expandafter\def\csname tikz at arrow@intersec at cache@3 at 5\endcsname{\pgfqpoint{2pt}{0pt}}
-\expandafter\def\csname tikz at arrow@intersec at cache@4 at 5\endcsname{\pgfqpoint{0.64167pt}{0.5pt}}
-% intersections are precomputed up to this order
-\def\tikz at arrow@intersec at numcached{5}
+% \pgf at nfold@inputsegmentobject at lineto{<length>}{\pgf at x <Last X> \pgf at y <Last Y>}{\pgf at x <X> \pgf at y <Y>}
+%
+\def\pgf at nfold@parselineto#1#2{%
+  % remove degenerate line segments (reduces glitches)
+  \pgf at process{\pgfpointdiff{\pgfpoint at nfold@lastparsed}{\pgf at x#1\pgf at y#2}}
+  \pgfpointtaxicabnorm\pgf at xa
+  \ifdim\pgf at xa>.1pt\relax
+    \edef\pgf at nfold@temp{%
+      \noexpand\pgf at nfold@inputsegmentobject at lineto{\pgfpoint at nfold@lastparsed}{\pgf at x#1\pgf at y#2}%
+    }%
+    \edef\pgfpoint at nfold@lastparsed{\pgf at x#1\pgf at y#2}%
+    \let\pgfpoint at nfold@lastnonmovetoparsed\pgfpoint at nfold@lastparsed%
+    \expandafter\pgf at nfold@addtoinputsegmentobjects\expandafter{\pgf at nfold@temp}%
+  \fi
+  \pgf at nfold@@parsesoftpath%
+}%
 
-% Decoration for the start tip
-\pgfdeclaredecoration{pgf at nfold@tip at start}{start}{%
-  \state{start}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{%
-    \egroup\begingroup%
-    \pgf at nfold@check at tip@start%
-    \pgf at nfold@setup at start@tip%
-    \ifpgf at nfold@tip at implies%
-      \pgf at nfold@setupimplies%
-      \pgf at nfold@drawimplies%
-    \fi%
-    \ifpgf at nfold@tip at mapsto%
-      \pgf at nfold@setupmapsto%
-      \pgf at nfold@drawmapsto%
-    \fi%
-    \endgroup\bgroup%
+\def\pgf at nfold@parsecurveto#1#2\pgfsyssoftpath at curvetosupportbtoken#3#4\pgfsyssoftpath at curvetotoken#5#6{%
+  \edef\pgf at nfold@temp{%
+    \noexpand\pgf at nfold@inputsegmentobject at curveto{\pgfpoint at nfold@lastparsed}%
+      {\pgf at x#1\pgf at y#2}{\pgf at x#3\pgf at y#4}{\pgf at x#5\pgf at y#6}%
   }%
-  \state{segment}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{}%
+  \expandafter\pgf at nfold@addtoinputsegmentobjects\expandafter{\pgf at nfold@temp}%
+  \edef\pgfpoint at nfold@lastparsed{\pgf at x#5\pgf at y#6}%
+  \let\pgfpoint at nfold@lastnonmovetoparsed\pgfpoint at nfold@lastparsed%
+  \pgf at nfold@@parsesoftpath%
 }%
 
-% Decoration for the end tip
-\pgfdeclaredecoration{pgf at nfold@tip at end}{segment}{%
-  \state{segment}[width=\pgfdecoratedinputsegmentremainingdistance, next state=segment]{}
-  \state{final}{%
-    \egroup\begingroup%
-    \pgf at nfold@check at tip@end% this step already checks for unsupported arrows
-    \pgf at nfold@setup at end@tip
-    \ifpgf at nfold@tip at implies%
-      \pgf at nfold@setupimplies%
-      \pgf at nfold@drawimplies%
-    \fi%
-    \ifpgf at nfold@tip at mapsto
-      \pgf at nfold@setupmapsto
-      \pgf at nfold@drawmapsto
-    \fi
-    \endgroup\bgroup%
-  }
+\def\pgf at nfold@parseclosepath#1#2{%
+  \edef\pgf at nfold@temp{%
+    \noexpand\pgf at nfold@inputsegmentobject at closepath{\pgfpoint at nfold@lastparsed}{\pgf at x#1\pgf at y#2}%
+  }%
+  \let\pgfpoint at nfold@lastnonmovetoparsed\pgfpoint at nfold@lastparsed%
+  \expandafter\pgf at nfold@addtoinputsegmentobjects\expandafter{\pgf at nfold@temp}%
+  \pgf at nfold@@parsesoftpath%
 }%
 
+% Mostly for the sake of completeness; using TikZ' "\path (0,0) rectangle (1,1);" does not call this code
+\def\pgf at nfold@parserect#1#2\pgfsyssoftpath at rectsizetoken#3#4{%
+  \let\pgf at nfold@orig@@parsesoftpath\pgf at nfold@@parsesoftpath%
+  \let\pgf at nfold@@parsesoftpath\relax%
+  \pgf at nfold@parsemoveto{#1}{#2}%
+  \pgf at xb=#1\relax
+  \pgf at yb=#2\relax
+  \pgf at xc=#3\relax
+  \pgf at yc=#4\relax
+    \advance\pgf at yb\pgf at yc%
+    \edef\pgf at temp{{\the\pgf at xb}{\the\pgf at yb}}%
+    \expandafter\pgf at nfold@parselineto\pgf at temp%
+    \advance\pgf at xb\pgf at xc%
+    \edef\pgf at temp{{\the\pgf at xb}{\the\pgf at yb}}%
+    \expandafter\pgf at nfold@parselineto\pgf at temp%
+    \advance\pgf at yb-\pgf at yc%
+    \edef\pgf at temp{{\the\pgf at xb}{\the\pgf at yb}}%
+    \expandafter\pgf at nfold@parselineto\pgf at temp%
+    \advance\pgf at xb-\pgf at xc%
+    \edef\pgf at temp{{\the\pgf at xb}{\the\pgf at yb}}%
+    \expandafter\pgf at nfold@parselineto\pgf at temp%
+  \let\pgf at nfold@@parsesoftpath\pgf at nfold@orig@@parsesoftpath%
+  \edef\pgf at marshal{\noexpand\pgf at nfold@parsemoveto{\the\pgf at xb}{\the\pgf at yb}}%
+  \pgf at marshal%
+}
 
-\def\pgf at nfold@setup at start@tip{
+\def\pgf at nfold@addtoinputsegmentobjects#1{%
+  %
+  % If there is an input segment object waiting (i.e. a moveto), insert it here.
+  %
+  \ifx\pgf at nfold@queueinputsegmentobject\pgfutil at empty%
+  \else%
+    \let\pgf at nfold@temp\pgf at nfold@queueinputsegmentobject%
+    \let\pgf at nfold@queueinputsegmentobject\pgfutil at empty%
+    \expandafter\pgf at nfold@addtoinputsegmentobjects\expandafter{\pgf at nfold@temp}%
+  \fi%
+  \ifx\pgfpoint at nfold@firstparsed\pgfutil at empty%
+    #1%
+    \let\pgfpoint at nfold@firstparsed\pgf at nfold@inputsegment at first%
+  \fi%
+  \expandafter\def\expandafter\pgf at nfold@inputsegmentobjects\expandafter%
+    {\pgf at nfold@inputsegmentobjects{#1}}%
+}%
+
+
+\def\pgf at nfold@inputsegmentfirst{first}%
+\def\pgf at nfold@inputsegmentmoveto{moveto}%
+\def\pgf at nfold@inputsegmentlineto{lineto}%
+\def\pgf at nfold@inputsegmentcurveto{curveto}%
+\def\pgf at nfold@inputsegmentclosepath{closepath}%
+\def\pgf at nfold@inputsegmentlast{last}%
+
+
+\def\pgf at nfold@inputsegmentobject at moveto#1{%
+  \def\pgf at nfold@inputsegment at first{#1}%
+  \def\pgf at nfold@inputsegment at supporta{#1}%
+  \def\pgf at nfold@inputsegment at supportb{#1}%
+  \def\pgf at nfold@inputsegment at last{#1}%
+  \edef\pgf at nfold@lastmoveto{#1}%
+  \let\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentmoveto%
+}%
+
+\def\pgf at nfold@inputsegmentobject at lineto#1#2{%
+  \def\pgf at nfold@inputsegment at first{#1}%
+  \def\pgf at nfold@inputsegment at last{#2}%
+  \let\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentlineto%
+}%
+
+\def\pgf at nfold@inputsegmentobject at curveto#1#2#3#4{%
+  \def\pgf at nfold@inputsegment at first{#1}%
+  \def\pgf at nfold@inputsegment at supporta{#2}%
+  \def\pgf at nfold@inputsegment at supportb{#3}%
+  \def\pgf at nfold@inputsegment at last{#4}%
+  \let\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentcurveto%
+}%
+
+\def\pgf at nfold@inputsegmentobject at closepath#1#2{%
+  \def\pgf at nfold@inputsegment at first{#1}%
+  \def\pgf at nfold@inputsegment at last{#2}%
+  \let\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentclosepath%
+}%
+
+\def\pgf at nfold@inputsegmentobject at endofinputsegments{%
+  \let\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentlast%
+}%
+
+
+\def\pgf at nfold@traversepath{%
+  % Transformations are already baked into the path; without this call, they would be applied twice
   \pgftransformreset
-  \pgftransformshift{\pgf at nfold@original at first}
-  \pgftransformrotate{\pgfdecoratedinputsegmentstartangle}
-  \pgftransformshift{\pgfpoint{.5*\pgflinewidth}{0pt}}
-  \pgftransformscale{\pgf at decoration@nfold at hwidth}
-  \pgftransformxscale{-1.}
+  % could likely also use \let\pgf at nfold@currentinputsegmentobjects\parsedsoftpath here
+  \let\pgf at nfold@currentinputsegmentobjects\pgf at nfold@inputsegmentobjects%
+%  \let\pgf at nfold@transformtoinputsegment\pgfutil at empty% we may need this one for closepath
+  \pgf at nfold@getnextinputsegmentobject\pgf at nfold@nextinputsegmentobject%
+  \let\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentfirst%
+  \def\pgf at nfold@cur at endangle{0.0}% put in some default value so \pgf at nfold@previous at endangle is not undefined
+  \pgf at nfold@traversepath@
 }
-\def\pgf at nfold@setup at end@tip{
-  \pgftransformreset
-  \pgftransformshift{\pgf at nfold@original at last}
-  \pgftransformrotate{\pgf at nfold@cached at endangle}
-  \pgftransformshift{\pgfpoint{-.5*\pgflinewidth}{0pt}}
-  \pgftransformscale{\pgf at decoration@nfold at hwidth}
+
+
+\def\pgf at nfold@traversepath@{
+  \pgf at nfold@processnextinputsegmentobject%
+  \ifx\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentlast\else
+    \pgf at nfold@handlesegment%
+  \fi%
+  \ifx\pgf at nfold@currentinputsegmentobjects\pgfutil at empty%
+    \let\pgf at next\relax%
+  \else%
+    \let\pgf at next\pgf at nfold@traversepath@%
+  \fi%
+  \pgf at next%
 }
 
-\def\pgf at nfold@setupimplies{%
-  \pgfsetroundjoin%
-  \pgfsetroundcap%
+
+\def\pgf at nfold@computeangles{
+  \ifx\pgf at nfold@currentinputsegment\pgf at nfold@inputsegmentcurveto%
+    \pgf at offset@compute at tangents{\pgf at nfold@inputsegment at first}{\pgf at nfold@inputsegment at supporta}{\pgf at nfold@inputsegment at supportb}{\pgf at nfold@inputsegment at last}
+    \let\pgf at nfold@cur at startangle\pgf at tmp@angle at i
+    \let\pgf at nfold@cur at endangle\pgf at tmp@angle at ii
+  \else%
+    \pgfmathanglebetweenpoints{\pgf at nfold@inputsegment at first}{\pgf at nfold@inputsegment at last}%
+    \edef\pgf at nfold@cur at startangle{\pgfmathresult}
+    \edef\pgf at nfold@cur at endangle{\pgfmathresult}
+  \fi%
 }
-\def\pgf at 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 at 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 at tempdima=\pgfmathresult pt
+    \advance\pgfutil at tempdima by 360pt
+    \edef\pgfmathresult{\pgf at sys@tonumber\pgfutil at tempdima}
+  \else\ifdim\pgfmathresult pt>180pt\relax
+    \pgfutil at tempdima=\pgfmathresult pt
+    \advance\pgfutil at tempdima by -360pt
+    \edef\pgfmathresult{\pgf at sys@tonumber\pgfutil at tempdima}
+  \fi\fi
 }
-\def\pgf at nfold@setupmapsto{%
-  \pgfsetbuttcap%
+
+\def\pgf at nfold@getnextinputsegmentobject#1{%
+  \ifx\pgf at nfold@currentinputsegmentobjects\pgfutil at empty%
+    \let\pgf at next\relax%
+  \else%
+    \def\pgf at nfold@temp{#1}%
+    \let\pgf at next\pgf at nfold@@getnextinputsegmentobject%
+  \fi%
+  \pgf at next%
+}%
+\def\pgf at nfold@@getnextinputsegmentobject{%
+  \expandafter\pgf at nfold@@@getnextinputsegmentobject\pgf at nfold@currentinputsegmentobjects\pgf at stop}%
+\def\pgf at nfold@@@getnextinputsegmentobject#1#2\pgf at stop{%
+  \expandafter\def\pgf at nfold@temp{#1}%
+  \def\pgf at nfold@currentinputsegmentobjects{#2}}%
+
+\def\pgf at nfold@processnextinputsegmentobject{%
+  \let\pgf at nfold@previousinputsegment\pgf at nfold@currentinputsegment%
+  \let\pgf at nfold@previous at endangle\pgf at nfold@cur at endangle%
+  \let\pgf at nfold@currentinputsegmentobject\pgf at nfold@nextinputsegmentobject%
+  \pgf at nfold@getnextinputsegmentobject\pgf at nfold@nextinputsegmentobject%
+  \pgf at nfold@nextinputsegmentobject% parse the *next* input segment so we can compute its angles
+  \pgf at nfold@computeangles%
+  \let\pgf at nfold@next at first\pgf at nfold@inputsegment at first%
+  \let\pgf at nfold@next at supporta\pgf at nfold@inputsegment at supporta%
+  \let\pgf at nfold@next at supportb\pgf at nfold@inputsegment at supportb%
+  \let\pgf at nfold@next at last\pgf at nfold@inputsegment at last%
+  \let\pgf at nfold@next at segmenttype\pgf at nfold@currentinputsegment%
+  \let\pgf at nfold@next at startangle\pgf at nfold@cur at startangle%
+  \pgf at nfold@currentinputsegmentobject% get the current segment into \pgf at nfold@inputsegment at first etc.
+  \pgf at 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 at nfold@cur at startangle}{\pgf at nfold@previous at endangle}
+  \pgf at nfold@clampangle
+  \edef\pgf at nfold@deltaphi at start{\pgfmathresult}
+  \pgfmathsubtract@{\pgf at nfold@next at startangle}{\pgf at nfold@cur at endangle}
+  \pgf at nfold@clampangle
+  \edef\pgf at nfold@deltaphi at end{\pgfmathresult}
 }
-\def\pgf at 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 at nfold@index
+\def\pgf at nfold@run at loop{%
+  \pgf at nfold@index=\pgf at nfold@order%
+  \pgf at nfold@run at loop@%
 }
 
-% This macro extends the arrow body to the tips
-% parameter: s=start, e=end
-\def\pgf at nfold@extendtotip#1{
-  % Do not extend the arrow for index=1 and index=order, it already ends in the right place
-  \ifnum\pgf at nfold@index>1\ifnum\pgf at nfold@index<\pgf at nfold@dec at 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 at arrow@intersec at cache@\pgf at nfold@index @\the\pgf at nfold@dec at order\endcsname
-      \csname tikz at arrow@intersec at cache@\pgf at nfold@index @\the\pgf at nfold@dec at order\endcsname
-      \pgfstorepoint\pgf at nfold@arrowintersect
-    \else
-      % the intersection has not been precomputed, thus compute on the fly here
-      \pgfintersectionofpaths{
-        % specify the tip
-        \pgf at nfold@drawimplies
-      }{
-        % extend the body to intersect the tip
-        \pgfpathmoveto{\pgfqpoint{-3pt}{\pgf at shiftdec@fraction pt}}
-        \pgfpathlineto{\pgfqpoint{3pt}{\pgf at shiftdec@fraction pt}}
-      }
-      \ifnum\pgfintersectionsolutions>0
-        \pgfpointintersectionsolution{1}
-        \pgfstorepoint\pgf at nfold@arrowintersect
-        \makeatother
-        \typeout{tikz-nfold: computed intersection cache@\pgf at nfold@index @\the\pgf at nfold@dec at order: \string\pgfqpoint{\the\pgf at x}{\the\pgf at y}^^J}
-        \makeatletter
-      \else
-        % this is a failsafe and should never be reached
-        \pgfwarning{'nfold': did not find intersection}
-        \pgfqpoint{0pt}{\pgf at shiftdec@fraction pt}
-        \pgfstorepoint\pgf at 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 at nfold@original at first}
-    \pgftransformrotate{\pgfdecoratedinputsegmentstartangle}
-    \pgftransformxscale{-1}
-  \else
-    \pgftransformshift{\pgf at nfold@original at last}
-    \pgftransformrotate{\pgfdecoratedinputsegmentendangle}
-  \fi
-  \pgftransformshift{\pgfpoint{-2*\pgf at decoration@nfold at hwidth-.5*\pgflinewidth}{0pt}}
-  \pgftransformscale{\pgf at decoration@nfold at hwidth}
-  \if#1s
-    \pgfpathmoveto{\pgf at nfold@arrowintersect}
-    % This is precisely the start of the body, shifted vertically
-    \pgfpathlineto{\pgfqpoint{-0.06pt}{\pgf at shiftdec@fraction pt}}
-    \pgf at nfold@continuesegmenttrue
-  \else\if#1e
-    \pgfpathlineto{\pgf at nfold@arrowintersect}
-  \else
-    \pgferror{Invalid argument to \string\pgf at nfold@extendtotip: \meaning#1}
-  \fi\fi
-  \pgftransformreset
-\fi\fi% if 1 < i < nArrows
+\def\pgf at nfold@run at loop@{%
+  \pgf at nfold@loop at inner%
+  \advance\pgf at nfold@index by -1\relax
+  \ifnum\pgf at nfold@index>0\relax%
+    \pgf at nfold@run at loop@%
+  \fi%
 }
 
+\def\pgf at nfold@loop at inner{%
+  \pgfmathsetmacro{\pgf at nfold@shift at fraction}%
+    {-1+2*(\pgf at nfold@index-1)/(\pgf at nfold@order-1)}
+  \pgfmathsetlengthmacro{\pgf at nfold@shiftamount}{\pgf at nfold@hwidth*\pgf at nfold@shift at fraction}
+  \pgf at nfold@traversepath%
+  \pgfsyssoftpath at flushcurrentpath%
+  \pgf at up@action%
+}
 
+% Computes both the width of the component lines into \pgf at x and the distance
+% from the center to the outermost line centers into \pgf at y
+% from the current values of \pgflinewidth and \pgfinnerlinewidth.
+\def\pgf at nfold@compute at widths@from at double{
+    \pgf at x=\pgflinewidth\relax%
+    \pgf at y=\pgf at x\relax%
+    \advance\pgf at x-\pgfinnerlinewidth\relax%
+    \advance\pgf at y+\pgfinnerlinewidth\relax%
+    \pgf at x=.5\pgf at x\relax%
+    \pgf at y=.25\pgf at y\relax%
+}
+
+\def\pgf at nfold@render at cached@softpath{%
+  \pgfscope% must use a scope, otherwise we break the arrow tips
+    \pgfprocessround{\cachedpath}{\cachedpath}% remove tokens from the soft path
+    \pgf at nfold@parsesoftpath{\cachedpath}{\parsedsoftpath}
+    \pgf at nfold@parsearrows
+    % Compute the full and constituent part line widths
+    \pgf at nfold@compute at widths@from at double%
+    \pgfsetlinewidth\pgf at x%
+    \edef\pgf at nfold@hwidth{\the\pgf at y}
+    \pgf at nfold@run at 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 at nfold@loop at index
-\def\tikz at nfold@run at loop#1{%
-  \edef\tikz at nfold@postaction{%
-    \tikz at 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 at nfold@shift, nfold order=#1, nfold index=\the\tikz at nfold@loop at index%
-      }%
-    },%
-  }
-  \advance\tikz at nfold@loop at index by -1\relax
-  \ifnum\tikz at nfold@loop at index>0%
-    \tikz at nfold@run at loop{#1}%
-  \fi%
+% Outputs a provided soft path in #1 offset by a distance provided in #2.
+\def\pgfoffsetpath#1#2{%
+  \begingroup
+    \pgfmathsetlengthmacro\pgf at nfold@hwidth{#2}
+    % \pgf at nfold@hwidth must always be positive
+    \pgf at x=\pgf at nfold@hwidth\relax
+    \ifdim\pgf at x<0pt\relax
+      \pgf at x=-\pgf at x
+      \def\pgf at nfold@shift at fraction{-1}
+    \else
+      \def\pgf at nfold@shift at fraction{1}
+    \fi
+    \edef\pgf at nfold@hwidth{\the\pgf at x}
+    \pgfoffsetpathqfraction{#1}{\pgf at nfold@hwidth}{\pgf at nfold@shift at 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 at nfold@hwidth{#2}
+    \pgfmathsetmacro\pgf at nfold@shift at fraction{#3}
+    \pgfoffsetpathqfraction{#1}{\pgf at nfold@hwidth}{\pgf at nfold@shift at fraction}
+  \endgroup
+}
+
+% This has the same output as the #3-th segment of nfold=#4.
+\def\pgfoffsetpathindex#1#2#3#4{%
+  \begingroup
+    \pgfmathsetmacro\pgf at nfold@shift at fraction{-1+2*(#3-1)/(#4-1)}
+    \pgfoffsetpathqfraction{#1}{#2}{\pgf at nfold@shift at 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 at nfold@parsesoftpath{\cachedpath}{\parsedsoftpath}
+    \pgf at x=#2\relax
+    \edef\pgf at nfold@hwidth{\the\pgf at x}
+    \edef\pgf at nfold@shift at fraction{#3}
+    \pgf at x=\pgf at nfold@shift at fraction\pgf at x\relax
+    \edef\pgf at nfold@shiftamount{\the\pgf at x}
+    \pgf at nfold@traversepath%
+  \endgroup
+}
+
+
+\pgfkeys{
+  /pgf/nfold/.code={%
+    \pgf at nfold@order=#1\relax%
+    \ifnum\pgf at nfold@order<1\relax%
+      \pgfutil at packageerror{tikz-nfold}{The key /pgf/nfold must take a value of at least 1, got \the\pgf at 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 at nfold@order>\pgf at nfold@intersec at numcached\relax
+      \ifdefined\pgfintersectionofpaths\else
+        \pgf at 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 at nfold@dec at order=#1\relax
-    \ifnum\pgf at nfold@dec at order<2
-      \pgferror{'nfold' must take a value of at least 2, got \the\pgf at nfold@dec at order}
-    \fi
-    \tikz at nfold@loop at index=\pgf at nfold@dec at order
-    % The final style has the following form:
-    % [draw=none,
-    %  postaction={
-    %   draw=none,decorate,decoration=pgf at nfold@storedata,
-    %   postaction={draw,arrows=-,decorate,decoration={pgf at nfold@shift,nfold order=#1,nfold index=1}},
-    %   [...],
-    %   postaction={draw,arrows=-,decorate,decoration={pgf at nfold@shift,nfold order=#1,nfold index=#1}}
-    %  }
-    % ]
-    % We build the postactions piece by piece in the macro \tikz at nfold@postaction. Then we apply it
-    % using \tikzset and .expand once, so potential future changes to \tikz at nfold@postaction do not 
-    % affect the settings in tikz.
-    %
-    \def\tikz at nfold@postaction{}
-    \tikz at nfold@run at loop{#1}
-    \edef\tikz at nfold@postaction{%
-        draw=none,decorate,decoration=pgf at nfold@storedata,%
-        \tikz at nfold@postaction%
-        postaction={
-          draw,arrows=-,shorten <=0pt,shorten >=0pt,
-          decorate,decoration=pgf at nfold@tip at start},%
-        postaction={
-          draw,arrows=-,shorten <=0pt,shorten >=0pt,
-          decorate,decoration=pgf at nfold@tip at end},%
+    \edef\pgf at tmp{\noexpand\pgfkeys{/pgf/nfold=#1}}
+    % patch \tikz at double@setup to set /pgf/nfold=#1 as well
+    \expandafter\expandafter\expandafter\def%
+      \expandafter\expandafter\expandafter\tikz at double@setup%
+      \expandafter\expandafter\expandafter{\expandafter\tikz at double@setup\pgf at tmp}
+  },
+  nfold/.default=2,
+  scaling nfold/.code={%
+    \pgfscope% scope to contain \tikz at double@setup
+      \tikz at double@setup
+      % extract double distance between line centers into \pgf at x
+      \pgf at nfold@compute at widths@from at double
+      \pgf at y=2\pgf at y
+      % store (order-1)*\pgf at x in \pgf at xa
+      \c at pgf@counta=#1
+      \advance\c at pgf@counta by -1\relax
+      \global\pgf at y=\c at pgf@counta\pgf at y
+    \endpgfscope
+    \tikzset{
+      double distance between line centers=\pgf at y,
+      nfold=#1
     }
-    \tikzset{postaction/.expand once=\tikz at nfold@postaction}
   },
-  nfold/.default=2
+  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 at double@setup
+      \tikz at double@setup
+      % extract double distance between line centers into \pgf at x
+      \pgf at nfold@compute at widths@from at double
+      % store (order-1)*\pgf at y in \pgf at ya
+      \c at pgf@counta=#1
+      \advance\c at pgf@counta by -1\relax
+      \pgf at ya=\c at pgf@counta\pgf at y
+      % compute the label offset, which is (order-2)*\pgf at y + .5*\pgf at x
+      \advance\c at pgf@counta by -1\relax
+      \pgf at xa=\c at pgf@counta\pgf at y
+      \advance\pgf at xa by .5\pgf at x
+      % save the results in \pgf at x and \pgf at y
+      \global\pgf at x=\pgf at xa
+      \global\pgf at y=2\pgf at ya
+    \endpgfscope
+    \tikzset{
+      commutative diagrams/every label/.append style/.expanded={outer sep=\the\pgf at x},
+      double distance between line centers=\pgf at y,
+      nfold=#1
+    }
+  },
+  commutative diagrams/scaling nfold/.default=2
 }
 
 \endinput



More information about the tex-live-commits mailing list.